BLOG-8 Set up gitea action #22
35
.gitea/workflows/frontend-ci.yaml
Normal file
35
.gitea/workflows/frontend-ci.yaml
Normal file
@ -0,0 +1,35 @@
|
||||
name: Frontend CI
|
||||
on: [push]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./frontend
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Install node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: frontend/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: ESLint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
@ -9,6 +9,7 @@
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"packageManager": "pnpm@10.0.0",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||
|
@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
import { useTagDispatch, useTagSelector } from "@/lib/home/presenter/tagHooks";
|
||||
import { tagStartedAction, tagStoppedAction } from "@/lib/home/presenter/tagSlice";
|
||||
import { useTagDispatch, useTagSelector } from "@/lib/home/presenter/tagHook";
|
||||
import { tagShuffledAction } from "@/lib/home/presenter/tagReducer";
|
||||
import tagStore from "@/lib/home/presenter/tagStore";
|
||||
|
||||
export default function SelfTags() {
|
||||
@ -16,25 +16,35 @@ export default function SelfTags() {
|
||||
}
|
||||
|
||||
function SelfTagsProvided() {
|
||||
const tags = useTagSelector((state) => state.tag.tags);
|
||||
const tags = useTagSelector((state) => state.tags);
|
||||
const dispatch = useTagDispatch();
|
||||
|
||||
// Initialize with placeholder to prevent flickering
|
||||
const [tagsToShow, setTagsToShow] = useState<string[]>([""]);
|
||||
const [isTagsVisible, setIsTagsVisible] = useState(false);
|
||||
|
||||
const timer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
|
||||
// On mount
|
||||
useEffect(() => {
|
||||
dispatch(tagStartedAction({ interval: 4000 }));
|
||||
timer.current = setInterval(() => {
|
||||
dispatch(tagShuffledAction());
|
||||
}, 4000);
|
||||
|
||||
return () => {
|
||||
dispatch(tagStoppedAction());
|
||||
setIsTagsVisible(false);
|
||||
clearInterval(timer.current);
|
||||
timer.current = undefined;
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
// On tags changed
|
||||
useEffect(() => {
|
||||
if (tags.length === 0) return;
|
||||
setIsTagsVisible(true);
|
||||
setIsTagsVisible(false);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsTagsVisible(false);
|
||||
}, 3500);
|
||||
setTagsToShow(tags);
|
||||
setIsTagsVisible(true);
|
||||
}, 500);
|
||||
}, [tags]);
|
||||
|
||||
return (
|
||||
@ -43,7 +53,7 @@ function SelfTagsProvided() {
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-transparent via-60% to-white" />
|
||||
<div className="flex flex-row items-center gap-x-2 overflow-hidden">
|
||||
{tags.map((tag) => (
|
||||
{tagsToShow.map((tag) => (
|
||||
<Hashtag key={tag} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
|
55
frontend/src/lib/home/presenter/tagReducer.ts
Normal file
55
frontend/src/lib/home/presenter/tagReducer.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { createAction, createReducer } from "@reduxjs/toolkit";
|
||||
|
||||
import shuffleArray from "@/lib/util/shuffleArray";
|
||||
|
||||
export const tagShuffledAction = createAction("tag/shuffled");
|
||||
|
||||
const tagReducer = createReducer<TagState>(
|
||||
() => ({
|
||||
tags: shuffleArray(tagsCollection),
|
||||
}),
|
||||
(builder) => {
|
||||
builder.addCase(tagShuffledAction, (state) => {
|
||||
state.tags = shuffleArray(tagsCollection);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default tagReducer;
|
||||
export type TagAction = ReturnType<typeof tagShuffledAction>;
|
||||
|
||||
export interface TagState {
|
||||
tags: string[];
|
||||
timer?: NodeJS.Timeout;
|
||||
}
|
||||
|
||||
const tagsCollection = [
|
||||
"APP",
|
||||
"C++",
|
||||
"Design Pattern",
|
||||
"Docker",
|
||||
"Flutter",
|
||||
"Go",
|
||||
"Java",
|
||||
"LINER",
|
||||
"Linux",
|
||||
"Python",
|
||||
"Squid",
|
||||
"TypeScript",
|
||||
"中央大學",
|
||||
"全端",
|
||||
"分享",
|
||||
"前端",
|
||||
"後端",
|
||||
"教學",
|
||||
"暴肝",
|
||||
"知識",
|
||||
"碼農",
|
||||
"科技",
|
||||
"科普",
|
||||
"程式設計",
|
||||
"資工系",
|
||||
"軟體工程",
|
||||
"遊戲",
|
||||
"魷魚",
|
||||
];
|
@ -1,73 +0,0 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
import tagStore from "@/lib/home/presenter/tagStore";
|
||||
import shuffleArray from "@/lib/util/shuffleArray";
|
||||
|
||||
export interface TagState {
|
||||
tags: string[];
|
||||
timer?: NodeJS.Timeout;
|
||||
}
|
||||
|
||||
export interface TagStartedActionPayload {
|
||||
interval: number;
|
||||
}
|
||||
|
||||
export const tagSlice = createSlice({
|
||||
name: "tag",
|
||||
initialState: {
|
||||
tags: [],
|
||||
timer: undefined,
|
||||
} as TagState,
|
||||
reducers: {
|
||||
started: (state, action: PayloadAction<TagStartedActionPayload>) => {
|
||||
state.tags = shuffleArray(tagsCollection);
|
||||
state.timer = setInterval(() => {
|
||||
tagStore.dispatch(tagSlice.actions.shuffled());
|
||||
}, action.payload.interval);
|
||||
},
|
||||
shuffled: (state) => {
|
||||
state.tags = shuffleArray(tagsCollection);
|
||||
},
|
||||
stopped: (state) => {
|
||||
clearInterval(state.timer);
|
||||
state.timer = undefined;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const tagStartedAction = tagSlice.actions.started;
|
||||
export const tagStoppedAction = tagSlice.actions.stopped;
|
||||
|
||||
const tagReducer = tagSlice.reducer;
|
||||
export default tagReducer;
|
||||
|
||||
const tagsCollection = [
|
||||
"APP",
|
||||
"C++",
|
||||
"Design Pattern",
|
||||
"Docker",
|
||||
"Flutter",
|
||||
"Go",
|
||||
"Java",
|
||||
"LINER",
|
||||
"Linux",
|
||||
"Python",
|
||||
"Squid",
|
||||
"TypeScript",
|
||||
"中央大學",
|
||||
"全端",
|
||||
"分享",
|
||||
"前端",
|
||||
"後端",
|
||||
"教學",
|
||||
"暴肝",
|
||||
"知識",
|
||||
"碼農",
|
||||
"科技",
|
||||
"科普",
|
||||
"程式設計",
|
||||
"資工系",
|
||||
"軟體工程",
|
||||
"遊戲",
|
||||
"魷魚",
|
||||
];
|
@ -1,11 +1,7 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
|
||||
import tagReducer from "@/lib/home/presenter/tagSlice";
|
||||
import tagReducer, { TagAction, TagState } from "@/lib/home/presenter/tagReducer";
|
||||
|
||||
const tagStore = configureStore({
|
||||
reducer: {
|
||||
tag: tagReducer,
|
||||
},
|
||||
export default configureStore<TagState, TagAction>({
|
||||
reducer: tagReducer,
|
||||
});
|
||||
|
||||
export default tagStore;
|
||||
|
Loading…
x
Reference in New Issue
Block a user