chore: update frontend folder

This commit is contained in:
Steven
2023-08-23 09:13:42 +08:00
parent 40814a801a
commit f5817c575c
129 changed files with 104 additions and 1648 deletions

View File

@ -0,0 +1,17 @@
import { configureStore } from "@reduxjs/toolkit";
import { TypedUseSelectorHook, useSelector } from "react-redux";
import globalReducer from "./modules/global";
import shortcutReducer from "./modules/shortcut";
const store = configureStore({
reducer: {
global: globalReducer,
shortcut: shortcutReducer,
},
});
type AppState = ReturnType<typeof store.getState>;
export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector;
export default store;

View File

@ -0,0 +1,19 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
type State = {
workspaceProfile: WorkspaceProfile;
};
const globalSlice = createSlice({
name: "global",
initialState: {} as State,
reducers: {
setGlobalState: (_, action: PayloadAction<State>) => {
return action.payload;
},
},
});
export const { setGlobalState } = globalSlice.actions;
export default globalSlice.reducer;

View File

@ -0,0 +1,51 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface State {
shortcutList: Shortcut[];
}
const shortcutSlice = createSlice({
name: "shortcut",
initialState: {
shortcutList: [],
} as State,
reducers: {
setShortcuts: (state, action: PayloadAction<Shortcut[]>) => {
return {
...state,
shortcutList: action.payload,
};
},
createShortcut: (state, action: PayloadAction<Shortcut>) => {
return {
...state,
shortcutList: state.shortcutList.concat(action.payload).sort((a, b) => b.createdTs - a.createdTs),
};
},
patchShortcut: (state, action: PayloadAction<Partial<Shortcut>>) => {
return {
...state,
shortcutList: state.shortcutList.map((s) => {
if (s.id === action.payload.id) {
return {
...s,
...action.payload,
};
} else {
return s;
}
}),
};
},
deleteShortcut: (state, action: PayloadAction<ShortcutId>) => {
return {
...state,
shortcutList: [...state.shortcutList].filter((shortcut) => shortcut.id !== action.payload),
};
},
},
});
export const { setShortcuts, createShortcut, patchShortcut, deleteShortcut } = shortcutSlice.actions;
export default shortcutSlice.reducer;

View File

@ -0,0 +1,41 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import * as api from "../../helpers/api";
interface FaviconState {
cache: {
[key: string]: string;
};
getOrFetchUrlFavicon: (url: string) => Promise<string>;
}
const useFaviconStore = create<FaviconState>()(
persist(
(set, get) => ({
cache: {},
getOrFetchUrlFavicon: async (url: string) => {
const cache = get().cache;
if (cache[url]) {
return cache[url];
}
try {
const { data: favicon } = await api.getUrlFavicon(url);
if (favicon) {
cache[url] = favicon;
set(cache);
return favicon;
}
} catch (error) {
// do nothing
}
return "";
},
}),
{
name: "favicon_cache",
}
)
);
export default useFaviconStore;

View File

@ -0,0 +1,38 @@
import { create } from "zustand";
import * as api from "../../helpers/api";
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
return {
...shortcut,
createdTs: shortcut.createdTs * 1000,
updatedTs: shortcut.updatedTs * 1000,
};
};
interface ShortcutState {
shortcutMapById: Record<ShortcutId, Shortcut>;
getOrFetchShortcutById: (id: ShortcutId) => Promise<Shortcut>;
getShortcutById: (id: ShortcutId) => Shortcut;
}
const useShortcutStore = create<ShortcutState>()((set, get) => ({
shortcutMapById: {},
getOrFetchShortcutById: async (id: ShortcutId) => {
const shortcutMap = get().shortcutMapById;
if (shortcutMap[id]) {
return shortcutMap[id] as Shortcut;
}
const { data } = await api.getShortcutById(id);
const shortcut = convertResponseModelShortcut(data);
shortcutMap[id] = shortcut;
set(shortcutMap);
return shortcut;
},
getShortcutById: (id: ShortcutId) => {
const shortcutMap = get().shortcutMapById;
return shortcutMap[id] as Shortcut;
},
}));
export default useShortcutStore;

View File

@ -0,0 +1,88 @@
import { create } from "zustand";
import * as api from "../../helpers/api";
const convertResponseModelUser = (user: User): User => {
return {
...user,
createdTs: user.createdTs * 1000,
updatedTs: user.updatedTs * 1000,
};
};
interface UserState {
userMapById: Record<UserId, User>;
currentUserId?: UserId;
fetchUserList: () => Promise<User[]>;
fetchCurrentUser: () => Promise<User>;
getOrFetchUserById: (id: UserId) => Promise<User>;
getUserById: (id: UserId) => User;
getCurrentUser: () => User;
createUser: (userCreate: UserCreate) => Promise<User>;
patchUser: (userPatch: UserPatch) => Promise<void>;
deleteUser: (id: UserId) => Promise<void>;
}
const useUserStore = create<UserState>()((set, get) => ({
userMapById: {},
fetchUserList: async () => {
const { data: userList } = await api.getUserList();
const userMap = get().userMapById;
userList.forEach((user) => {
userMap[user.id] = convertResponseModelUser(user);
});
set(userMap);
return userList;
},
fetchCurrentUser: async () => {
const { data } = await api.getMyselfUser();
const user = convertResponseModelUser(data);
const userMap = get().userMapById;
userMap[user.id] = user;
set({ userMapById: userMap, currentUserId: user.id });
return user;
},
getOrFetchUserById: async (id: UserId) => {
const userMap = get().userMapById;
if (userMap[id]) {
return userMap[id] as User;
}
const { data } = await api.getUserById(id);
const user = convertResponseModelUser(data);
userMap[id] = user;
set(userMap);
return user;
},
createUser: async (userCreate: UserCreate) => {
const { data } = await api.createUser(userCreate);
const user = convertResponseModelUser(data);
const userMap = get().userMapById;
userMap[user.id] = user;
set(userMap);
return user;
},
patchUser: async (userPatch: UserPatch) => {
const { data } = await api.patchUser(userPatch);
const user = convertResponseModelUser(data);
const userMap = get().userMapById;
userMap[user.id] = user;
set(userMap);
},
deleteUser: async (userId: UserId) => {
await api.deleteUser(userId);
const userMap = get().userMapById;
delete userMap[userId];
set(userMap);
},
getUserById: (id: UserId) => {
const userMap = get().userMapById;
return userMap[id] as User;
},
getCurrentUser: () => {
const userMap = get().userMapById;
const currentUserId = get().currentUserId;
return userMap[currentUserId as UserId];
},
}));
export default useUserStore;

View File

@ -0,0 +1,116 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
export interface Filter {
tab?: string;
tag?: string;
visibility?: Visibility;
search?: string;
}
export interface Order {
field: "name" | "createdTs" | "updatedTs" | "view";
direction: "asc" | "desc";
}
export type DisplayStyle = "full" | "compact";
interface ViewState {
filter: Filter;
order: Order;
displayStyle: DisplayStyle;
setFilter: (filter: Partial<Filter>) => void;
getOrder: () => Order;
setOrder: (order: Partial<Order>) => void;
setDisplayStyle: (displayStyle: DisplayStyle) => void;
}
const useViewStore = create<ViewState>()(
persist(
(set, get) => ({
filter: {},
order: {
field: "name",
direction: "asc",
},
displayStyle: "full",
setFilter: (filter: Partial<Filter>) => {
set({ filter: { ...get().filter, ...filter } });
},
getOrder: () => {
return {
field: get().order.field || "name",
direction: get().order.direction || "asc",
};
},
setOrder: (order: Partial<Order>) => {
set({ order: { ...get().order, ...order } });
},
setDisplayStyle: (displayStyle: DisplayStyle) => {
set({ displayStyle });
},
}),
{
name: "view",
}
)
);
export const getFilteredShortcutList = (shortcutList: Shortcut[], filter: Filter, currentUser: User) => {
const { tab, tag, visibility, search } = filter;
const filteredShortcutList = shortcutList.filter((shortcut) => {
if (tag) {
if (!shortcut.tags.includes(tag)) {
return false;
}
}
if (visibility) {
if (shortcut.visibility !== visibility) {
return false;
}
}
if (search) {
if (
!shortcut.name.toLowerCase().includes(search.toLowerCase()) &&
!shortcut.description.toLowerCase().includes(search.toLowerCase()) &&
!shortcut.tags.some((tag) => tag.toLowerCase().includes(search.toLowerCase())) &&
!shortcut.link.toLowerCase().includes(search.toLowerCase())
) {
return false;
}
}
if (tab) {
if (tab === "tab:mine") {
return shortcut.creatorId === currentUser.id;
} else if (tab.startsWith("tag:")) {
const tag = tab.split(":")[1];
return shortcut.tags.includes(tag);
}
}
return true;
});
return filteredShortcutList;
};
export const getOrderedShortcutList = (shortcutList: Shortcut[], order: Order) => {
const { field, direction } = {
field: order.field || "name",
direction: order.direction || "asc",
};
const orderedShortcutList = shortcutList.sort((a, b) => {
if (field === "name") {
return direction === "asc" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
} else if (field === "createdTs") {
return direction === "asc" ? a.createdTs - b.createdTs : b.createdTs - a.createdTs;
} else if (field === "updatedTs") {
return direction === "asc" ? a.updatedTs - b.updatedTs : b.updatedTs - a.updatedTs;
} else if (field === "view") {
return direction === "asc" ? a.view - b.view : b.view - a.view;
} else {
return 0;
}
});
return orderedShortcutList;
};
export default useViewStore;