mirror of
https://github.com/aykhans/slash-e.git
synced 2025-07-04 12:26:19 +00:00
chore: update frontend folder
This commit is contained in:
17
frontend/web/src/stores/index.ts
Normal file
17
frontend/web/src/stores/index.ts
Normal 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;
|
19
frontend/web/src/stores/modules/global.ts
Normal file
19
frontend/web/src/stores/modules/global.ts
Normal 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;
|
51
frontend/web/src/stores/modules/shortcut.ts
Normal file
51
frontend/web/src/stores/modules/shortcut.ts
Normal 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;
|
41
frontend/web/src/stores/v1/favicon.ts
Normal file
41
frontend/web/src/stores/v1/favicon.ts
Normal 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;
|
38
frontend/web/src/stores/v1/shortcut.ts
Normal file
38
frontend/web/src/stores/v1/shortcut.ts
Normal 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;
|
88
frontend/web/src/stores/v1/user.ts
Normal file
88
frontend/web/src/stores/v1/user.ts
Normal 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;
|
116
frontend/web/src/stores/v1/view.ts
Normal file
116
frontend/web/src/stores/v1/view.ts
Normal 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;
|
Reference in New Issue
Block a user