feat: build account dashboard and settings workspace
This commit is contained in:
140
src/store/account.ts
Normal file
140
src/store/account.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { createStore } from "zustand/vanilla";
|
||||
import { persist, createJSONStorage } from "zustand/middleware";
|
||||
import type { CourseKind, RecordItem, RecordType } from "~/service/wk";
|
||||
import type { CourseType } from "~/types/Course";
|
||||
import type { userInfoType } from "~/types/Userinfo";
|
||||
|
||||
export type AccountAuth = {
|
||||
password: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
export type AccountItem = {
|
||||
id: string;
|
||||
username: string;
|
||||
host: string;
|
||||
status: CourseKind;
|
||||
sessionId: string;
|
||||
auth: AccountAuth;
|
||||
user: userInfoType;
|
||||
courses: CourseType[];
|
||||
};
|
||||
|
||||
type AccountState = {
|
||||
accounts: AccountItem[];
|
||||
selectedAccountId: string;
|
||||
expandedAccountId: string;
|
||||
selectedCourseId: number | null;
|
||||
recordType: RecordType;
|
||||
records: RecordItem[];
|
||||
studyLogsMap: Record<string, string[]>;
|
||||
runningStudyMap: Record<string, boolean>;
|
||||
setSelectedAccountId: (accountId: string) => void;
|
||||
setExpandedAccountId: (accountId: string) => void;
|
||||
setSelectedCourseId: (courseId: number | null) => void;
|
||||
setRecordType: (recordType: RecordType) => void;
|
||||
setRecords: (records: RecordItem[]) => void;
|
||||
setAccountRunningStudy: (accountId: string, value: boolean) => void;
|
||||
appendStudyLog: (accountId: string, message: string) => void;
|
||||
clearStudyLogs: (accountId: string) => void;
|
||||
upsertAccount: (account: AccountItem) => void;
|
||||
removeAccount: (accountId: string) => void;
|
||||
};
|
||||
|
||||
export const accountStore = createStore<AccountState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
accounts: [],
|
||||
selectedAccountId: "",
|
||||
expandedAccountId: "",
|
||||
selectedCourseId: null,
|
||||
recordType: "",
|
||||
records: [],
|
||||
studyLogsMap: {},
|
||||
runningStudyMap: {},
|
||||
setSelectedAccountId: (accountId) =>
|
||||
set({ selectedAccountId: accountId }),
|
||||
setExpandedAccountId: (accountId) =>
|
||||
set({ expandedAccountId: accountId }),
|
||||
setSelectedCourseId: (courseId) => set({ selectedCourseId: courseId }),
|
||||
setRecordType: (recordType) => set({ recordType }),
|
||||
setRecords: (records) => set({ records }),
|
||||
setAccountRunningStudy: (accountId, value) =>
|
||||
set((state) => ({
|
||||
runningStudyMap: {
|
||||
...state.runningStudyMap,
|
||||
[accountId]: value,
|
||||
},
|
||||
})),
|
||||
appendStudyLog: (accountId, message) =>
|
||||
set((state) => ({
|
||||
studyLogsMap: {
|
||||
...state.studyLogsMap,
|
||||
[accountId]: [...(state.studyLogsMap[accountId] ?? []), message],
|
||||
},
|
||||
})),
|
||||
clearStudyLogs: (accountId) =>
|
||||
set((state) => ({
|
||||
studyLogsMap: {
|
||||
...state.studyLogsMap,
|
||||
[accountId]: [],
|
||||
},
|
||||
})),
|
||||
upsertAccount: (account) =>
|
||||
set((state) => ({
|
||||
accounts: [
|
||||
account,
|
||||
...state.accounts.filter((item) => item.id !== account.id),
|
||||
],
|
||||
selectedAccountId: account.id,
|
||||
expandedAccountId: account.id,
|
||||
})),
|
||||
removeAccount: (accountId) =>
|
||||
set((state) => {
|
||||
const nextAccounts = state.accounts.filter(
|
||||
(item) => item.id !== accountId,
|
||||
);
|
||||
return {
|
||||
accounts: nextAccounts,
|
||||
selectedAccountId:
|
||||
state.selectedAccountId === accountId
|
||||
? ""
|
||||
: state.selectedAccountId,
|
||||
expandedAccountId:
|
||||
state.expandedAccountId === accountId
|
||||
? ""
|
||||
: state.expandedAccountId,
|
||||
selectedCourseId:
|
||||
state.selectedAccountId === accountId
|
||||
? null
|
||||
: state.selectedCourseId,
|
||||
records: state.selectedAccountId === accountId ? [] : state.records,
|
||||
studyLogsMap: Object.fromEntries(
|
||||
Object.entries(state.studyLogsMap).filter(
|
||||
([key]) => key !== accountId,
|
||||
),
|
||||
),
|
||||
runningStudyMap: Object.fromEntries(
|
||||
Object.entries(state.runningStudyMap).filter(
|
||||
([key]) => key !== accountId,
|
||||
),
|
||||
),
|
||||
};
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: "account-storage",
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state) => ({
|
||||
accounts: state.accounts,
|
||||
selectedAccountId: state.selectedAccountId,
|
||||
expandedAccountId: state.expandedAccountId,
|
||||
selectedCourseId: state.selectedCourseId,
|
||||
recordType: state.recordType,
|
||||
records: state.records,
|
||||
studyLogsMap: state.studyLogsMap,
|
||||
runningStudyMap: state.runningStudyMap,
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
127
src/store/settings.ts
Normal file
127
src/store/settings.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { createStore } from "zustand/vanilla";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
|
||||
export type HostOption = {
|
||||
label: string;
|
||||
host: string;
|
||||
source: "local" | "remote";
|
||||
};
|
||||
|
||||
type CacheSection = "accounts" | "records" | "logs";
|
||||
type DensityMode = "comfortable" | "compact";
|
||||
|
||||
type SettingsState = {
|
||||
persistAccounts: boolean;
|
||||
persistRecords: boolean;
|
||||
persistLogs: boolean;
|
||||
autoScrollLogs: boolean;
|
||||
showLogTimestamps: boolean;
|
||||
densityMode: DensityMode;
|
||||
logFontSize: number;
|
||||
sidebarWidth: number;
|
||||
localHosts: HostOption[];
|
||||
remoteHosts: HostOption[];
|
||||
setPersistSection: (section: CacheSection, value: boolean) => void;
|
||||
clearPersistedSection: (section: CacheSection) => void;
|
||||
clearAllPersistedData: () => void;
|
||||
setAutoScrollLogs: (value: boolean) => void;
|
||||
setShowLogTimestamps: (value: boolean) => void;
|
||||
setDensityMode: (value: DensityMode) => void;
|
||||
setLogFontSize: (value: number) => void;
|
||||
setSidebarWidth: (value: number) => void;
|
||||
addLocalHost: (host: Omit<HostOption, "source">) => void;
|
||||
removeLocalHost: (host: string) => void;
|
||||
setRemoteHosts: (hosts: Omit<HostOption, "source">[]) => void;
|
||||
};
|
||||
|
||||
const accountStorageKey = "account-storage";
|
||||
|
||||
const uniqueHosts = (hosts: HostOption[]) => {
|
||||
const map = new Map<string, HostOption>();
|
||||
|
||||
for (const item of hosts) {
|
||||
if (!map.has(item.host)) {
|
||||
map.set(item.host, item);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(map.values());
|
||||
};
|
||||
|
||||
export const getMergedHosts = (
|
||||
localHosts: HostOption[],
|
||||
remoteHosts: HostOption[],
|
||||
) => {
|
||||
return uniqueHosts([...localHosts, ...remoteHosts]);
|
||||
};
|
||||
|
||||
export const settingsStore = createStore<SettingsState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
persistAccounts: true,
|
||||
persistRecords: true,
|
||||
persistLogs: true,
|
||||
autoScrollLogs: true,
|
||||
showLogTimestamps: false,
|
||||
densityMode: "comfortable",
|
||||
logFontSize: 12,
|
||||
sidebarWidth: 320,
|
||||
localHosts: [
|
||||
{ label: "默认站点", host: "cqcst.leykeji.com", source: "local" },
|
||||
],
|
||||
remoteHosts: [],
|
||||
setPersistSection: (section, value) => {
|
||||
if (section === "accounts") set({ persistAccounts: value });
|
||||
if (section === "records") set({ persistRecords: value });
|
||||
if (section === "logs") set({ persistLogs: value });
|
||||
},
|
||||
clearPersistedSection: (section) => {
|
||||
if (section === "accounts") {
|
||||
localStorage.removeItem(accountStorageKey);
|
||||
}
|
||||
|
||||
if (section === "records") {
|
||||
set({ persistRecords: false });
|
||||
queueMicrotask(() => set({ persistRecords: true }));
|
||||
}
|
||||
|
||||
if (section === "logs") {
|
||||
set({ persistLogs: false });
|
||||
queueMicrotask(() => set({ persistLogs: true }));
|
||||
}
|
||||
},
|
||||
clearAllPersistedData: () => {
|
||||
localStorage.removeItem(accountStorageKey);
|
||||
get().clearPersistedSection("records");
|
||||
get().clearPersistedSection("logs");
|
||||
},
|
||||
setAutoScrollLogs: (value) => set({ autoScrollLogs: value }),
|
||||
setShowLogTimestamps: (value) => set({ showLogTimestamps: value }),
|
||||
setDensityMode: (value) => set({ densityMode: value }),
|
||||
setLogFontSize: (value) => set({ logFontSize: value }),
|
||||
setSidebarWidth: (value) => set({ sidebarWidth: value }),
|
||||
addLocalHost: (host) =>
|
||||
set((state) => ({
|
||||
localHosts: uniqueHosts([
|
||||
{ ...host, source: "local" },
|
||||
...state.localHosts,
|
||||
...state.remoteHosts,
|
||||
]).filter((item) => item.source === "local"),
|
||||
})),
|
||||
removeLocalHost: (host) =>
|
||||
set((state) => ({
|
||||
localHosts: state.localHosts.filter((item) => item.host !== host),
|
||||
})),
|
||||
setRemoteHosts: (hosts) =>
|
||||
set({
|
||||
remoteHosts: uniqueHosts(
|
||||
hosts.map((item) => ({ ...item, source: "remote" as const })),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: "settings-storage",
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
},
|
||||
),
|
||||
);
|
||||
Reference in New Issue
Block a user