Template
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
137 lines
2.9 KiB
TypeScript
137 lines
2.9 KiB
TypeScript
import { create } from "zustand";
|
|
import { persist } from "zustand/middleware";
|
|
import { appConfig } from "@/app.config";
|
|
import type { GuideDialogState } from "../types/dialog.types";
|
|
|
|
const getDialogKey = (courseId: string, dialogId: string) =>
|
|
`${courseId}:${dialogId}`;
|
|
|
|
const getInitialState = () => ({
|
|
open: false,
|
|
courseId: null,
|
|
dialogId: null,
|
|
steps: [],
|
|
current: 0,
|
|
showOnce: true,
|
|
});
|
|
|
|
export const useDialogStore = create<GuideDialogState>()(
|
|
persist(
|
|
(set, get) => ({
|
|
...getInitialState(),
|
|
completedDialogMap: {},
|
|
|
|
startDialog: ({
|
|
courseId,
|
|
dialogId,
|
|
steps,
|
|
showOnce = true,
|
|
initialStep = 0,
|
|
}) => {
|
|
if (steps.length === 0) return;
|
|
|
|
const key = getDialogKey(courseId, dialogId);
|
|
if (showOnce && get().completedDialogMap[key]) {
|
|
return;
|
|
}
|
|
|
|
set({
|
|
open: true,
|
|
courseId,
|
|
dialogId,
|
|
steps,
|
|
current: Math.min(Math.max(initialStep, 0), steps.length - 1),
|
|
showOnce,
|
|
});
|
|
},
|
|
|
|
closeDialog: () => {
|
|
set(getInitialState());
|
|
},
|
|
|
|
nextStep: () => {
|
|
const { current, steps, finishDialog } = get();
|
|
if (current >= steps.length - 1) {
|
|
finishDialog();
|
|
return;
|
|
}
|
|
set({ current: current + 1 });
|
|
},
|
|
|
|
prevStep: () => {
|
|
const { current } = get();
|
|
set({ current: Math.max(current - 1, 0) });
|
|
},
|
|
|
|
setStep: (index) => {
|
|
const { steps } = get();
|
|
if (steps.length === 0) return;
|
|
set({ current: Math.min(Math.max(index, 0), steps.length - 1) });
|
|
},
|
|
|
|
selectOption: (optionId) => {
|
|
const { current, steps, setStep, finishDialog, nextStep } = get();
|
|
const step = steps[current];
|
|
const option = step?.options?.find((item) => item.id === optionId);
|
|
if (!option) return;
|
|
|
|
if (option.finish) {
|
|
finishDialog();
|
|
return;
|
|
}
|
|
|
|
if (option.nextId) {
|
|
const nextIndex = steps.findIndex(
|
|
(item) => item.id === option.nextId,
|
|
);
|
|
if (nextIndex >= 0) {
|
|
setStep(nextIndex);
|
|
return;
|
|
}
|
|
}
|
|
|
|
nextStep();
|
|
},
|
|
|
|
finishDialog: () => {
|
|
const { courseId, dialogId, completedDialogMap } = get();
|
|
if (!courseId || !dialogId) {
|
|
set(getInitialState());
|
|
return;
|
|
}
|
|
|
|
const key = getDialogKey(courseId, dialogId);
|
|
set({
|
|
...getInitialState(),
|
|
completedDialogMap: {
|
|
...completedDialogMap,
|
|
[key]: true,
|
|
},
|
|
});
|
|
},
|
|
|
|
skipDialog: () => {
|
|
get().finishDialog();
|
|
},
|
|
|
|
resetDialog: (courseId, dialogId) => {
|
|
const key = getDialogKey(courseId, dialogId);
|
|
const { [key]: _removed, ...rest } = get().completedDialogMap;
|
|
const isCurrentDialog =
|
|
get().courseId === courseId && get().dialogId === dialogId;
|
|
|
|
set({
|
|
...(isCurrentDialog ? getInitialState() : {}),
|
|
completedDialogMap: rest,
|
|
});
|
|
},
|
|
}),
|
|
{
|
|
name: `${appConfig.storage.prefix}-dialog-storage`,
|
|
partialize: (state) => ({
|
|
completedDialogMap: state.completedDialogMap,
|
|
}),
|
|
},
|
|
),
|
|
);
|