refactoring, linting, formatting
This commit is contained in:
246
src/workspace/useWorkspaceState.ts
Normal file
246
src/workspace/useWorkspaceState.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import type { PageRef } from "../pdf/pdfTypes";
|
||||
import type {
|
||||
WorkspaceCommand,
|
||||
WorkspaceCommandRecord,
|
||||
WorkspaceCommandState,
|
||||
} from "./workspaceCommands";
|
||||
import {
|
||||
createSnapshotCommand,
|
||||
reviveWorkspaceCommand,
|
||||
toWorkspaceCommandRecord,
|
||||
} from "./workspaceCommands";
|
||||
|
||||
function createId(prefix: string): string {
|
||||
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
||||
}
|
||||
|
||||
export function createWorkspaceId(): string {
|
||||
return createId("workspace");
|
||||
}
|
||||
|
||||
export function defaultWorkspaceNameFromPdfName(pdfName: string): string {
|
||||
return pdfName.replace(/\.pdf$/i, "") || "Untitled workspace";
|
||||
}
|
||||
|
||||
export function createPageRefId(): string {
|
||||
return createId("page");
|
||||
}
|
||||
|
||||
export function createInitialPageRefs(pageCount: number): PageRef[] {
|
||||
return Array.from({ length: pageCount }, (_, sourcePageIndex) => ({
|
||||
id: createPageRefId(),
|
||||
sourcePageIndex,
|
||||
rotation: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
export function normalizeRotation(rotation: number | undefined): number {
|
||||
return (((rotation ?? 0) % 360) + 360) % 360;
|
||||
}
|
||||
|
||||
type SetStateAction<T> = T | ((previous: T) => T);
|
||||
|
||||
interface UseWorkspaceStateOptions {
|
||||
onContentChanged?: () => void;
|
||||
}
|
||||
|
||||
interface ReplaceWorkspaceStateOptions {
|
||||
pages?: PageRef[];
|
||||
selectedPageIds?: string[];
|
||||
lastSelectedVisualIndex?: number | null;
|
||||
history?: WorkspaceCommandRecord[];
|
||||
redoHistory?: WorkspaceCommandRecord[];
|
||||
dirty?: boolean;
|
||||
message?: string | null;
|
||||
}
|
||||
|
||||
export function useWorkspaceState({
|
||||
onContentChanged,
|
||||
}: UseWorkspaceStateOptions = {}) {
|
||||
const [pages, setPagesState] = useState<PageRef[]>([]);
|
||||
const [selectedPageIds, setSelectedPageIdsState] = useState<string[]>([]);
|
||||
const [lastSelectedVisualIndex, setLastSelectedVisualIndexState] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [workspaceDirty, setWorkspaceDirty] = useState(false);
|
||||
const [workspaceMessage, setWorkspaceMessage] = useState<string | null>(null);
|
||||
const [workspaceHistory, setWorkspaceHistory] = useState<
|
||||
WorkspaceCommandRecord[]
|
||||
>([]);
|
||||
const [redoHistory, setRedoHistory] = useState<WorkspaceCommandRecord[]>([]);
|
||||
|
||||
const latestPagesRef = useRef<PageRef[]>([]);
|
||||
const selectedPageIdsRef = useRef<string[]>([]);
|
||||
const lastSelectedVisualIndexRef = useRef<number | null>(null);
|
||||
|
||||
const setPages = useCallback((action: SetStateAction<PageRef[]>) => {
|
||||
setPagesState((previous) => {
|
||||
const next = typeof action === "function" ? action(previous) : action;
|
||||
latestPagesRef.current = next;
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setSelectedPageIds = useCallback((action: SetStateAction<string[]>) => {
|
||||
setSelectedPageIdsState((previous) => {
|
||||
const next = typeof action === "function" ? action(previous) : action;
|
||||
selectedPageIdsRef.current = next;
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setLastSelectedVisualIndex = useCallback(
|
||||
(action: SetStateAction<number | null>) => {
|
||||
setLastSelectedVisualIndexState((previous) => {
|
||||
const next = typeof action === "function" ? action(previous) : action;
|
||||
lastSelectedVisualIndexRef.current = next;
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const getCurrentCommandState = useCallback(
|
||||
(): WorkspaceCommandState => ({
|
||||
pages: latestPagesRef.current,
|
||||
selectedPageIds: selectedPageIdsRef.current,
|
||||
lastSelectedVisualIndex: lastSelectedVisualIndexRef.current,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const applyCommandState = useCallback(
|
||||
(state: WorkspaceCommandState) => {
|
||||
setPages(state.pages);
|
||||
setSelectedPageIds(state.selectedPageIds);
|
||||
setLastSelectedVisualIndex(state.lastSelectedVisualIndex);
|
||||
},
|
||||
[setLastSelectedVisualIndex, setPages, setSelectedPageIds],
|
||||
);
|
||||
|
||||
const markWorkspaceChanged = useCallback(() => {
|
||||
setWorkspaceDirty(true);
|
||||
setWorkspaceMessage(null);
|
||||
onContentChanged?.();
|
||||
}, [onContentChanged]);
|
||||
|
||||
const createWorkspaceCommand = useCallback(
|
||||
(params: {
|
||||
type: string;
|
||||
label: string;
|
||||
before: WorkspaceCommandState;
|
||||
after: WorkspaceCommandState;
|
||||
details?: Record<string, unknown>;
|
||||
}): WorkspaceCommand =>
|
||||
createSnapshotCommand({
|
||||
id: createId("command"),
|
||||
type: params.type,
|
||||
label: params.label,
|
||||
before: params.before,
|
||||
after: params.after,
|
||||
details: params.details,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const executeWorkspaceCommand = useCallback(
|
||||
(command: WorkspaceCommand) => {
|
||||
const nextState = command.do(getCurrentCommandState());
|
||||
|
||||
applyCommandState(nextState);
|
||||
setWorkspaceHistory((previous) => [
|
||||
...previous,
|
||||
toWorkspaceCommandRecord(command),
|
||||
]);
|
||||
setRedoHistory([]);
|
||||
markWorkspaceChanged();
|
||||
},
|
||||
[applyCommandState, getCurrentCommandState, markWorkspaceChanged],
|
||||
);
|
||||
|
||||
const handleUndo = useCallback(() => {
|
||||
const record = workspaceHistory[workspaceHistory.length - 1];
|
||||
if (!record) return;
|
||||
|
||||
const command = reviveWorkspaceCommand(record);
|
||||
const previousState = command.undo(getCurrentCommandState());
|
||||
|
||||
applyCommandState(previousState);
|
||||
setWorkspaceHistory((previous) => previous.slice(0, -1));
|
||||
setRedoHistory((previous) => [...previous, record]);
|
||||
markWorkspaceChanged();
|
||||
}, [
|
||||
applyCommandState,
|
||||
getCurrentCommandState,
|
||||
markWorkspaceChanged,
|
||||
workspaceHistory,
|
||||
]);
|
||||
|
||||
const handleRedo = useCallback(() => {
|
||||
const record = redoHistory[redoHistory.length - 1];
|
||||
if (!record) return;
|
||||
|
||||
const command = reviveWorkspaceCommand(record);
|
||||
const nextState = command.do(getCurrentCommandState());
|
||||
|
||||
applyCommandState(nextState);
|
||||
setRedoHistory((previous) => previous.slice(0, -1));
|
||||
setWorkspaceHistory((previous) => [...previous, record]);
|
||||
markWorkspaceChanged();
|
||||
}, [
|
||||
applyCommandState,
|
||||
getCurrentCommandState,
|
||||
markWorkspaceChanged,
|
||||
redoHistory,
|
||||
]);
|
||||
|
||||
const replaceWorkspaceState = useCallback(
|
||||
(state: ReplaceWorkspaceStateOptions = {}) => {
|
||||
setPages(state.pages ?? []);
|
||||
setSelectedPageIds(state.selectedPageIds ?? []);
|
||||
setLastSelectedVisualIndex(state.lastSelectedVisualIndex ?? null);
|
||||
setWorkspaceHistory(state.history ?? []);
|
||||
setRedoHistory(state.redoHistory ?? []);
|
||||
setWorkspaceDirty(state.dirty ?? false);
|
||||
setWorkspaceMessage(state.message ?? null);
|
||||
},
|
||||
[setLastSelectedVisualIndex, setPages, setSelectedPageIds],
|
||||
);
|
||||
|
||||
const resetWorkspaceState = useCallback(() => {
|
||||
replaceWorkspaceState();
|
||||
}, [replaceWorkspaceState]);
|
||||
|
||||
return {
|
||||
pages,
|
||||
setPages,
|
||||
selectedPageIds,
|
||||
setSelectedPageIds,
|
||||
lastSelectedVisualIndex,
|
||||
setLastSelectedVisualIndex,
|
||||
latestPagesRef,
|
||||
|
||||
workspaceDirty,
|
||||
setWorkspaceDirty,
|
||||
workspaceMessage,
|
||||
setWorkspaceMessage,
|
||||
workspaceHistory,
|
||||
setWorkspaceHistory,
|
||||
redoHistory,
|
||||
setRedoHistory,
|
||||
|
||||
getCurrentCommandState,
|
||||
applyCommandState,
|
||||
createWorkspaceCommand,
|
||||
executeWorkspaceCommand,
|
||||
handleUndo,
|
||||
handleRedo,
|
||||
replaceWorkspaceState,
|
||||
resetWorkspaceState,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user