116 lines
4.2 KiB
TypeScript
116 lines
4.2 KiB
TypeScript
import { createContext } from "preact";
|
|
import { useContext, useMemo, useReducer } from "preact/hooks";
|
|
|
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
|
|
export interface Story {
|
|
id: string;
|
|
title: string;
|
|
text: string;
|
|
}
|
|
|
|
// ─── State ───────────────────────────────────────────────────────────────────
|
|
|
|
interface IState {
|
|
stories: Story[];
|
|
currentStoryId: string | null;
|
|
}
|
|
|
|
// ─── Actions ─────────────────────────────────────────────────────────────────
|
|
|
|
type Action =
|
|
| { type: 'CREATE_STORY'; title: string }
|
|
| { type: 'RENAME_STORY'; id: string; title: string }
|
|
| { type: 'EDIT_STORY'; id: string; text: string }
|
|
| { type: 'DELETE_STORY'; id: string }
|
|
| { type: 'SELECT_STORY'; id: string };
|
|
|
|
// ─── Initial State ───────────────────────────────────────────────────────────
|
|
|
|
const DEFAULT_STATE: IState = {
|
|
stories: [],
|
|
currentStoryId: null,
|
|
};
|
|
|
|
// ─── Reducer ─────────────────────────────────────────────────────────────────
|
|
|
|
function reducer(state: IState, action: Action): IState {
|
|
switch (action.type) {
|
|
case 'CREATE_STORY': {
|
|
const story: Story = {
|
|
id: crypto.randomUUID(),
|
|
title: action.title,
|
|
text: '',
|
|
};
|
|
return {
|
|
...state,
|
|
stories: [...state.stories, story],
|
|
currentStoryId: story.id,
|
|
};
|
|
}
|
|
case 'RENAME_STORY': {
|
|
return {
|
|
...state,
|
|
stories: state.stories.map(s =>
|
|
s.id === action.id ? { ...s, title: action.title } : s
|
|
),
|
|
};
|
|
}
|
|
case 'EDIT_STORY': {
|
|
return {
|
|
...state,
|
|
stories: state.stories.map(s =>
|
|
s.id === action.id ? { ...s, text: action.text } : s
|
|
),
|
|
};
|
|
}
|
|
case 'DELETE_STORY': {
|
|
const remaining = state.stories.filter(s => s.id !== action.id);
|
|
const deletingCurrent = state.currentStoryId === action.id;
|
|
return {
|
|
...state,
|
|
stories: remaining,
|
|
currentStoryId: deletingCurrent ? null : state.currentStoryId,
|
|
};
|
|
}
|
|
case 'SELECT_STORY': {
|
|
return {
|
|
...state,
|
|
currentStoryId: action.id,
|
|
};
|
|
}
|
|
default:
|
|
return state;
|
|
}
|
|
}
|
|
|
|
// ─── Context ─────────────────────────────────────────────────────────────────
|
|
|
|
interface IStateContext {
|
|
stories: Story[];
|
|
currentStory: Story | null;
|
|
dispatch: (action: Action) => void;
|
|
}
|
|
|
|
const StateContext = createContext<IStateContext>({} as IStateContext);
|
|
|
|
export const useAppState = () => useContext(StateContext);
|
|
|
|
// ─── Provider ────────────────────────────────────────────────────────────────
|
|
|
|
export const StateContextProvider = ({ children }: { children?: any }) => {
|
|
const [state, dispatch] = useReducer(reducer, DEFAULT_STATE);
|
|
|
|
const value = useMemo<IStateContext>(() => ({
|
|
stories: state.stories,
|
|
currentStory: state.stories.find(s => s.id === state.currentStoryId) ?? null,
|
|
dispatch,
|
|
}), [state]);
|
|
|
|
return (
|
|
<StateContext.Provider value={value}>
|
|
{children}
|
|
</StateContext.Provider>
|
|
);
|
|
};
|