@weasel-js/editor 1.15.0 → 1.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -5
- package/dist/index.d.ts +144 -3
- package/dist/index.js +140 -3
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @weasel-js/editor
|
|
2
2
|
|
|
3
|
-
The glue a weasel editor app needs around its canvas:
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The frame and glue a weasel editor app needs around its canvas: a shell with
|
|
4
|
+
the kit's providers, command keys that work before any canvas is pressed, a
|
|
5
|
+
menu bar, a topbar, and a preferences dialog; saving the document as it changes
|
|
6
|
+
and restoring it on reload, opening and saving files, a document of several
|
|
7
|
+
pages, fitting the view to a page, and the page's paper and grid.
|
|
6
8
|
|
|
7
9
|
Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
|
|
8
10
|
scene-graph canvas kit for React. See the
|
|
@@ -14,18 +16,39 @@ scene-graph canvas kit for React. See the
|
|
|
14
16
|
npm install @weasel-js/editor
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
It runs beside `@weasel-js/core`, `@weasel-js/geom`, `@weasel-js/guides
|
|
18
|
-
`@weasel-js/ui`, which it takes as peers.
|
|
19
|
+
It runs beside `@weasel-js/core`, `@weasel-js/geom`, `@weasel-js/guides`,
|
|
20
|
+
`@weasel-js/prefs`, and `@weasel-js/ui`, which it takes as peers.
|
|
19
21
|
|
|
20
22
|
## What is in it
|
|
21
23
|
|
|
22
24
|
| Area | Exports |
|
|
23
25
|
|---|---|
|
|
26
|
+
| Shell | `EditorShell`, `EDITOR_ACTIONS`, `EDITOR_PREFERENCES_KEY`, `useEditorKeys` |
|
|
27
|
+
| Preferences | `useEditorPrefs`, `useOpenPreferences`, `EditorPrefsDialog` |
|
|
28
|
+
| Menu bar | `editorMenuBar`, `menu`, `menuRun`, `EDITOR_MENU_TITLES` |
|
|
24
29
|
| Session | `useSessionPersistence`, `useAutosave`, `readStored`, `writeSession` |
|
|
25
30
|
| Snapshots | `docFromSnapshot`, `nodeSpecsFromSnapshot`, `serializeReplacer`, `reviveTypedArrays` |
|
|
26
31
|
| Files | `downloadBlob`, `pickFile`, `describeFileWarnings`, `reportFileWarnings` |
|
|
32
|
+
| File commands | `useNewCommand`, `useOpenCommand`, `useSaveCommand`, `FILE_KEYS` |
|
|
27
33
|
| Pages | `usePages`, `usePageFit`, `usePaperLayer`, `usePageGrid` |
|
|
28
34
|
| Text editing | `TextEditDepPublisher` |
|
|
29
35
|
|
|
36
|
+
```tsx
|
|
37
|
+
<EditorShell
|
|
38
|
+
prefs={{ schema: PREFS, storage: localStorageAdapter, prefix: 'myapp.prefs/' }}
|
|
39
|
+
menus={{ file: { save: menuRun({ save: 'file.save' }) }, edit: { history: menuRun({ undo: 'undo', redo: 'redo' }) } }}
|
|
40
|
+
topbar={<MyToolbar />}
|
|
41
|
+
>
|
|
42
|
+
<MyWorkspace />
|
|
43
|
+
</EditorShell>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`EditorShell` mounts the kit's providers where none is in scope, and a
|
|
47
|
+
dispatcher that runs the keys of the commands registered on its registry while
|
|
48
|
+
no canvas holds the keys; once a canvas on that registry does, the canvas runs
|
|
49
|
+
them. It registers `editor.preferences` (Mod+,) to open the preferences dialog,
|
|
50
|
+
and draws the menu bar from `menus` on its own row, or, with `menuBar="inline"`,
|
|
51
|
+
hands it to a `topbar` function to place.
|
|
52
|
+
|
|
30
53
|
A saved snapshot keeps a path's typed arrays tagged (`serializeReplacer`), and
|
|
31
54
|
loading reads the tags back, along with the untagged shapes older saves wrote.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { PlacedPage, RenderLayer, SavedScene, SceneRegistry, SpecsFromSerializedOptions, AddNodeSpec, View, Scene, UseSceneTextEditReturn, SnapSource } from '@weasel-js/core';
|
|
1
|
+
import { CommandOptions, PlacedPage, RenderLayer, SavedScene, SceneRegistry, SpecsFromSerializedOptions, AddNodeSpec, View, Scene, UseSceneTextEditReturn, SnapSource, BoundGesture } from '@weasel-js/core';
|
|
2
2
|
import { typedArrayReplacer } from '@weasel-js/geom';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
import { PrefGroup, PrefsStore, openPrefsSync } from '@weasel-js/prefs';
|
|
6
|
+
import { PrefsDialogProps } from '@weasel-js/ui/components/Prefs';
|
|
3
7
|
|
|
4
8
|
/** Trigger a browser download of `blob` as a file named `filename`. */
|
|
5
9
|
declare function downloadBlob(blob: Blob, filename: string): void;
|
|
@@ -9,6 +13,42 @@ declare function downloadBlob(blob: Blob, filename: string): void;
|
|
|
9
13
|
*/
|
|
10
14
|
declare function pickFile(accept: string): Promise<File | null>;
|
|
11
15
|
|
|
16
|
+
/** The keys a file command binds unless its caller names others: Cmd/Ctrl+N, O and S. */
|
|
17
|
+
declare const FILE_KEYS: {
|
|
18
|
+
readonly new: {
|
|
19
|
+
readonly kind: "key";
|
|
20
|
+
readonly key: "n";
|
|
21
|
+
readonly mods: {
|
|
22
|
+
readonly mod: true;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
readonly open: {
|
|
26
|
+
readonly kind: "key";
|
|
27
|
+
readonly key: "o";
|
|
28
|
+
readonly mods: {
|
|
29
|
+
readonly mod: true;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
readonly save: {
|
|
33
|
+
readonly kind: "key";
|
|
34
|
+
readonly key: "s";
|
|
35
|
+
readonly mods: {
|
|
36
|
+
readonly mod: true;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/** A file command's options. Leaving `keys` out binds its `FILE_KEYS` entry; `null` binds none. */
|
|
41
|
+
interface FileCommandOptions extends Omit<CommandOptions, 'keys'> {
|
|
42
|
+
keys?: CommandOptions['keys'] | null;
|
|
43
|
+
}
|
|
44
|
+
type FileRun = (params?: Readonly<Record<string, unknown>>) => void;
|
|
45
|
+
/** `useCommand` for a document's New, bound to Cmd/Ctrl+N by default. */
|
|
46
|
+
declare function useNewCommand(id: string, label: string, run: FileRun, options?: FileCommandOptions): void;
|
|
47
|
+
/** `useCommand` for a document's Open, bound to Cmd/Ctrl+O by default. */
|
|
48
|
+
declare function useOpenCommand(id: string, label: string, run: FileRun, options?: FileCommandOptions): void;
|
|
49
|
+
/** `useCommand` for a document's Save, bound to Cmd/Ctrl+S by default. */
|
|
50
|
+
declare function useSaveCommand(id: string, label: string, run: FileRun, options?: FileCommandOptions): void;
|
|
51
|
+
|
|
12
52
|
/** What a file operation did, as a toast's title says it. */
|
|
13
53
|
type FileDeed = 'Opened' | 'Imported' | 'Saved' | 'Exported';
|
|
14
54
|
/** What a toast says about what a file could not carry: how many, and the first few once each. */
|
|
@@ -19,6 +59,7 @@ declare function describeFileWarnings(did: FileDeed, warnings: readonly string[]
|
|
|
19
59
|
/** Tells the user what a file operation left out, in one toast under `id`: the next one replaces it. Nothing for no warnings. */
|
|
20
60
|
declare function reportFileWarnings(did: FileDeed, warnings: readonly string[], id: string): void;
|
|
21
61
|
|
|
62
|
+
/** A page's size, in world units. */
|
|
22
63
|
interface PageSize {
|
|
23
64
|
width: number;
|
|
24
65
|
height: number;
|
|
@@ -64,6 +105,7 @@ interface SessionWrite {
|
|
|
64
105
|
primary: SessionEntry;
|
|
65
106
|
dependents?: readonly SessionEntry[];
|
|
66
107
|
}
|
|
108
|
+
/** When `useAutosave` saves, and what it reports after. */
|
|
67
109
|
interface AutosaveOptions {
|
|
68
110
|
/** How long after a change the save runs. Defaults to 300 ms. */
|
|
69
111
|
delayMs?: number;
|
|
@@ -120,8 +162,11 @@ declare function usePages<TData, TLayer extends string, TPose>(sceneIn: Scene<TD
|
|
|
120
162
|
height: number;
|
|
121
163
|
}, setView: (view: View) => void, padding?: number): DocPages;
|
|
122
164
|
|
|
165
|
+
/** Where `useSessionPersistence` keeps a scene and its undo stack, and what it reports after a save. */
|
|
123
166
|
interface SessionPersistenceOptions {
|
|
167
|
+
/** The local-storage key the scene is saved under. */
|
|
124
168
|
sceneKey: string;
|
|
169
|
+
/** The local-storage key the undo stack is saved under. */
|
|
125
170
|
historyKey: string;
|
|
126
171
|
/** Read the saved undo snapshot; defaults to the parsed `historyKey` entry. Return null for none. */
|
|
127
172
|
loadHistory?: () => unknown;
|
|
@@ -144,11 +189,11 @@ declare function useSessionPersistence<D, L extends string, P>(scene: Scene<D, L
|
|
|
144
189
|
* SceneCanvas's tree, after the internal component that registers the stub —
|
|
145
190
|
* so a child registration overrides it, and a *parent* one would not.
|
|
146
191
|
*/
|
|
147
|
-
|
|
148
192
|
declare function TextEditDepPublisher({ edit }: {
|
|
149
193
|
edit: UseSceneTextEditReturn;
|
|
150
194
|
}): null;
|
|
151
195
|
|
|
196
|
+
/** The page a `usePageGrid` grid covers, its pitch and look, and whether it shows and snaps. */
|
|
152
197
|
interface PageGridOptions {
|
|
153
198
|
/** The page the grid covers; its lines start at the page's corner, default world `(0, 0)`. */
|
|
154
199
|
page: PageSize & {
|
|
@@ -172,4 +217,100 @@ declare function usePageGrid(opts: PageGridOptions): {
|
|
|
172
217
|
source: SnapSource | undefined;
|
|
173
218
|
};
|
|
174
219
|
|
|
175
|
-
|
|
220
|
+
/** The ids of the commands `<EditorShell>` registers. */
|
|
221
|
+
declare const EDITOR_ACTIONS: {
|
|
222
|
+
readonly preferences: "editor.preferences";
|
|
223
|
+
};
|
|
224
|
+
/** How the shell registers one of its commands. */
|
|
225
|
+
interface EditorCommandOptions extends Pick<CommandOptions, 'icon' | 'group' | 'tags'> {
|
|
226
|
+
label?: string;
|
|
227
|
+
/** The gestures that run it, in place of its default; `null` binds none. */
|
|
228
|
+
keys?: BoundGesture | readonly BoundGesture[] | null;
|
|
229
|
+
}
|
|
230
|
+
/** `editor.preferences`'s key unless `keys` says otherwise: Mod+,. */
|
|
231
|
+
declare const EDITOR_PREFERENCES_KEY: BoundGesture;
|
|
232
|
+
/**
|
|
233
|
+
* The app's keys with no canvas holding them: a dispatcher on the registry in scope that runs what is registered
|
|
234
|
+
* there. It listens first, so a canvas in a scope below does not run the same binding as well, and it hears nothing
|
|
235
|
+
* while a canvas on this registry itself holds the keys, which then runs them with its own views and deps.
|
|
236
|
+
*/
|
|
237
|
+
declare function useEditorKeys(): void;
|
|
238
|
+
|
|
239
|
+
/** What `openPrefsSync` takes besides the schema. */
|
|
240
|
+
type EditorPrefsOptions = Parameters<typeof openPrefsSync>[1];
|
|
241
|
+
/** An editor's preferences: a store already open, or what `openPrefsSync` needs to open one. Options are read once,
|
|
242
|
+
* on mount, and the store they open is kept for the page's life; pass a store where the shell can remount. */
|
|
243
|
+
type EditorPrefs<S extends PrefGroup = PrefGroup> = PrefsStore<S> | ({
|
|
244
|
+
schema: S;
|
|
245
|
+
} & EditorPrefsOptions);
|
|
246
|
+
/** What the preferences dialog takes past the store's own binding: `layout`, `renderers` for the app's own kinds,
|
|
247
|
+
* `headerExtra`, `showHidden`, and the rest of `PrefsDialog`'s props. `layout` defaults to `'rail'`. */
|
|
248
|
+
type EditorPrefsDialogProps = Partial<Omit<PrefsDialogProps, 'isOpen' | 'onOpenChange' | 'schema' | 'values' | 'onChange' | 'auto' | 'onAutoChange'>>;
|
|
249
|
+
/** The preferences store of the `<EditorShell>` above, or null outside one or when it was given none. `S` is the
|
|
250
|
+
* schema the caller knows the store holds. */
|
|
251
|
+
declare function useEditorPrefs<S extends PrefGroup = PrefGroup>(): PrefsStore<S> | null;
|
|
252
|
+
/** Opens the `<EditorShell>`'s preferences dialog. Does nothing outside one, or when it was given no prefs. */
|
|
253
|
+
declare function useOpenPreferences(): () => void;
|
|
254
|
+
/** `PrefsDialog` bound to `store`: each change is written at once, and turning a leaf's Auto on resets it. */
|
|
255
|
+
declare function EditorPrefsDialog({ store, open, onClose, dialog }: {
|
|
256
|
+
store: PrefsStore<PrefGroup>;
|
|
257
|
+
open: boolean;
|
|
258
|
+
onClose: () => void;
|
|
259
|
+
dialog?: EditorPrefsDialogProps;
|
|
260
|
+
}): react.JSX.Element;
|
|
261
|
+
|
|
262
|
+
/** A menu's runs: each is a group of items, set off from the next by a separator. */
|
|
263
|
+
type MenuRuns = Record<string, PrefGroup>;
|
|
264
|
+
/** What each menu title holds, by the title's key. A title with no entry is left off the bar unless it is `always`. */
|
|
265
|
+
type MenuContributions = Readonly<Record<string, MenuRuns | undefined>>;
|
|
266
|
+
/** A title on the menu bar. */
|
|
267
|
+
interface MenuTitle {
|
|
268
|
+
/** The key its contribution is filed under. */
|
|
269
|
+
key: string;
|
|
270
|
+
title: string;
|
|
271
|
+
/** The access letter that opens it. */
|
|
272
|
+
access?: string;
|
|
273
|
+
/** Shown even when nothing is contributed to it. */
|
|
274
|
+
always?: boolean;
|
|
275
|
+
}
|
|
276
|
+
/** File and Edit, always shown; then View, Format, Arrange, and Page, each shown once something is contributed. */
|
|
277
|
+
declare const EDITOR_MENU_TITLES: readonly MenuTitle[];
|
|
278
|
+
/** A run of menu items from `{ key: actionId }`. A bare id lists every variant its action has. */
|
|
279
|
+
declare function menuRun(ids: Readonly<Record<string, string>>): PrefGroup;
|
|
280
|
+
/** A menu title over `runs`. */
|
|
281
|
+
declare function menu(title: string, runs: MenuRuns, access?: string): PrefGroup;
|
|
282
|
+
/** The menu bar `MenuBar` draws: a menu per title in `titles` order, from what `contributions` files under each.
|
|
283
|
+
* A contribution whose key no title names is left out. */
|
|
284
|
+
declare function editorMenuBar(contributions: MenuContributions, titles?: readonly MenuTitle[]): PrefGroup;
|
|
285
|
+
|
|
286
|
+
/** Props for {@link EditorShell}. */
|
|
287
|
+
interface EditorShellProps<S extends PrefGroup = PrefGroup> {
|
|
288
|
+
/** The app's preferences. With them the shell registers `editor.preferences` and draws its dialog. */
|
|
289
|
+
prefs?: EditorPrefs<S>;
|
|
290
|
+
/** The preferences dialog's own props: its layout, renderers for the app's kinds, a header switch. */
|
|
291
|
+
prefsDialog?: EditorPrefsDialogProps;
|
|
292
|
+
/** How `editor.preferences` registers; `null` registers none. Ignored without `prefs`. */
|
|
293
|
+
preferences?: EditorCommandOptions | null;
|
|
294
|
+
/** What each menu holds, by title key. Without it there is no menu bar. Pass a new value to change the menus. */
|
|
295
|
+
menus?: MenuContributions;
|
|
296
|
+
/** The menu titles and their order. Default `EDITOR_MENU_TITLES`. */
|
|
297
|
+
menuTitles?: readonly MenuTitle[];
|
|
298
|
+
/**
|
|
299
|
+
* Where the menu bar goes. `'row'` (default): on its own row, above the topbar. `'inline'`: in the topbar. A
|
|
300
|
+
* `topbar` function is handed the bar to place; a plain one is drawn after it.
|
|
301
|
+
*/
|
|
302
|
+
menuBar?: 'row' | 'inline';
|
|
303
|
+
menuBarClassName?: string;
|
|
304
|
+
/** The row of controls under the menu bar. A function receives the menu bar when it is inline, else `null`. */
|
|
305
|
+
topbar?: ReactNode | ((menuBar: ReactNode) => ReactNode);
|
|
306
|
+
children?: ReactNode;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* The frame of an editor app: the kit's providers where none is in scope already (deps, actions, the active tool,
|
|
310
|
+
* the selection, and the pointer), a dispatcher that runs the app's command keys with no canvas holding them, the
|
|
311
|
+
* menu bar and topbar, the preferences store and dialog, and the context menu host. `children` render below the
|
|
312
|
+
* topbar. Draws no element of its own: what it renders is laid out by the app's container.
|
|
313
|
+
*/
|
|
314
|
+
declare function EditorShell<S extends PrefGroup = PrefGroup>(props: EditorShellProps<S>): react.JSX.Element;
|
|
315
|
+
|
|
316
|
+
export { type AutosaveOptions, type DocPages, EDITOR_ACTIONS, EDITOR_MENU_TITLES, EDITOR_PREFERENCES_KEY, type EditorCommandOptions, type EditorPrefs, EditorPrefsDialog, type EditorPrefsDialogProps, type EditorPrefsOptions, EditorShell, type EditorShellProps, FILE_KEYS, type FileCommandOptions, type FileDeed, type MenuContributions, type MenuRuns, type MenuTitle, type PageGridOptions, type PageSize, type SessionEntry, type SessionPersistenceOptions, type SessionWrite, TextEditDepPublisher, describeFileWarnings, docFromSnapshot, downloadBlob, editorMenuBar, menu, menuRun, nodeSpecsFromSnapshot, pickFile, readStored, reportFileWarnings, reviveTypedArrays, serializeReplacer, useAutosave, useEditorKeys, useEditorPrefs, useNewCommand, useOpenCommand, useOpenPreferences, usePageFit, usePageGrid, usePages, usePaperLayer, useSaveCommand, useSessionPersistence, writeSession };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { pagesLayer, specsFromSerialized, useLatest, fitViewToBounds, pagesOf, placedPagesOf, pageInView, resizePage, renamePage, movePage, removePage, nextPageId, addPage, adjacentPage, defaultCommitAdapter, useDepSource, contrastLineColor, useActionsRegistry, useGestureDispatcher, WeaselProvider, fitViewToPage, useCommand } from '@weasel-js/core';
|
|
1
2
|
import { toast } from '@weasel-js/ui/components/Toast';
|
|
2
|
-
import {
|
|
3
|
-
import { useMemo, useRef, useEffect, useCallback, useState } from 'react';
|
|
3
|
+
import { createContext, useMemo, useRef, useEffect, useCallback, useContext, useState } from 'react';
|
|
4
4
|
import { typedArrayReplacer, untagTypedArray } from '@weasel-js/geom';
|
|
5
5
|
import { createGridLayer, gridSnapSource } from '@weasel-js/guides';
|
|
6
|
+
import { ContextMenuHost } from '@weasel-js/ui/components/ContextMenu';
|
|
7
|
+
import { MenuBar } from '@weasel-js/ui/components/MenuBar';
|
|
8
|
+
import { pref, openPrefsSync } from '@weasel-js/prefs';
|
|
9
|
+
import { usePrefsValues } from '@weasel-js/prefs/react';
|
|
10
|
+
import { PrefsDialog } from '@weasel-js/ui/components/Prefs';
|
|
11
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
12
|
|
|
7
13
|
// src/fileIO.ts
|
|
8
14
|
function downloadBlob(blob, filename) {
|
|
@@ -31,6 +37,24 @@ function pickFile(accept) {
|
|
|
31
37
|
input.click();
|
|
32
38
|
});
|
|
33
39
|
}
|
|
40
|
+
var FILE_KEYS = {
|
|
41
|
+
new: { kind: "key", key: "n", mods: { mod: true } },
|
|
42
|
+
open: { kind: "key", key: "o", mods: { mod: true } },
|
|
43
|
+
save: { kind: "key", key: "s", mods: { mod: true } }
|
|
44
|
+
};
|
|
45
|
+
function useFileCommand(slot, id, label, run, options) {
|
|
46
|
+
const { keys, ...rest } = options ?? {};
|
|
47
|
+
useCommand(id, label, run, keys === null ? rest : { ...rest, keys: keys ?? FILE_KEYS[slot] });
|
|
48
|
+
}
|
|
49
|
+
function useNewCommand(id, label, run, options) {
|
|
50
|
+
useFileCommand("new", id, label, run, options);
|
|
51
|
+
}
|
|
52
|
+
function useOpenCommand(id, label, run, options) {
|
|
53
|
+
useFileCommand("open", id, label, run, options);
|
|
54
|
+
}
|
|
55
|
+
function useSaveCommand(id, label, run, options) {
|
|
56
|
+
useFileCommand("save", id, label, run, options);
|
|
57
|
+
}
|
|
34
58
|
var QUOTED = 3;
|
|
35
59
|
function describeFileWarnings(did, warnings) {
|
|
36
60
|
const distinct = [...new Set(warnings)];
|
|
@@ -278,7 +302,120 @@ function usePageGrid(opts) {
|
|
|
278
302
|
const source = useMemo(() => gridSnapSource({ spacing, origin: { x, y } }), [spacing, x, y]);
|
|
279
303
|
return { layer: visible ? layer : void 0, source: snap ? source : void 0 };
|
|
280
304
|
}
|
|
305
|
+
var EDITOR_ACTIONS = { preferences: "editor.preferences" };
|
|
306
|
+
var EDITOR_PREFERENCES_KEY = { kind: "key", key: ",", mods: { mod: true } };
|
|
307
|
+
function PreferencesCommand({ options = {}, open }) {
|
|
308
|
+
const { label = "Preferences\u2026", keys = EDITOR_PREFERENCES_KEY, ...rest } = options;
|
|
309
|
+
useCommand(EDITOR_ACTIONS.preferences, label, open, { ...rest, ...keys !== null ? { keys } : {} });
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
var NO_ELEMENT = { current: null };
|
|
313
|
+
var NO_ENTRIES = /* @__PURE__ */ new Map();
|
|
314
|
+
var CHANNELS = { contextMenu: false, ingest: false };
|
|
315
|
+
function useEditorKeys() {
|
|
316
|
+
const registry = useActionsRegistry();
|
|
317
|
+
if (!registry) throw new Error("useEditorKeys: no <ActionsProvider> in scope");
|
|
318
|
+
useGestureDispatcher({
|
|
319
|
+
canvasRef: NO_ELEMENT,
|
|
320
|
+
actions: registry,
|
|
321
|
+
entriesById: NO_ENTRIES,
|
|
322
|
+
channels: CHANNELS,
|
|
323
|
+
keyboard: "first",
|
|
324
|
+
hears: "own",
|
|
325
|
+
yieldKeys: true
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
var EditorPrefsContext = createContext(null);
|
|
329
|
+
var isStore = (prefs) => !("storage" in prefs);
|
|
330
|
+
function useOpenedPrefs(prefs) {
|
|
331
|
+
const [opened] = useState(() => prefs && !isStore(prefs) ? openPrefsSync(prefs.schema, prefs) : null);
|
|
332
|
+
return prefs && isStore(prefs) ? prefs : opened;
|
|
333
|
+
}
|
|
334
|
+
function useEditorPrefs() {
|
|
335
|
+
return useContext(EditorPrefsContext)?.store ?? null;
|
|
336
|
+
}
|
|
337
|
+
var NOTHING = () => {
|
|
338
|
+
};
|
|
339
|
+
function useOpenPreferences() {
|
|
340
|
+
return useContext(EditorPrefsContext)?.open ?? NOTHING;
|
|
341
|
+
}
|
|
342
|
+
function EditorPrefsDialog({ store, open, onClose, dialog }) {
|
|
343
|
+
const { values, set, unset, reset } = usePrefsValues(store);
|
|
344
|
+
return /* @__PURE__ */ jsx(
|
|
345
|
+
PrefsDialog,
|
|
346
|
+
{
|
|
347
|
+
layout: "rail",
|
|
348
|
+
...dialog,
|
|
349
|
+
isOpen: open,
|
|
350
|
+
onOpenChange: (next) => {
|
|
351
|
+
if (!next) onClose();
|
|
352
|
+
},
|
|
353
|
+
schema: store.schema,
|
|
354
|
+
values,
|
|
355
|
+
onChange: set,
|
|
356
|
+
auto: unset,
|
|
357
|
+
onAutoChange: (path, next) => next ? reset(path) : set(path, store.get(path))
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
var EDITOR_MENU_TITLES = [
|
|
362
|
+
{ key: "file", title: "File", access: "f", always: true },
|
|
363
|
+
{ key: "edit", title: "Edit", access: "e", always: true },
|
|
364
|
+
{ key: "view", title: "View", access: "v" },
|
|
365
|
+
{ key: "format", title: "Format", access: "o" },
|
|
366
|
+
{ key: "arrange", title: "Arrange", access: "a" },
|
|
367
|
+
{ key: "page", title: "Page", access: "p" }
|
|
368
|
+
];
|
|
369
|
+
function menuRun(ids) {
|
|
370
|
+
return pref.group("", Object.fromEntries(Object.entries(ids).map(([key, id]) => [key, pref.action(id)])));
|
|
371
|
+
}
|
|
372
|
+
function menu(title, runs, access) {
|
|
373
|
+
return pref.group(title, runs, { as: "menu", ...access !== void 0 ? { access } : {} });
|
|
374
|
+
}
|
|
375
|
+
function editorMenuBar(contributions, titles = EDITOR_MENU_TITLES) {
|
|
376
|
+
const menus = {};
|
|
377
|
+
for (const { key, title, access, always } of titles) {
|
|
378
|
+
const runs = contributions[key];
|
|
379
|
+
if (runs !== void 0 || always) menus[key] = menu(title, runs ?? {}, access);
|
|
380
|
+
}
|
|
381
|
+
return pref.group("Menu bar", menus, { as: "bar" });
|
|
382
|
+
}
|
|
383
|
+
function EditorShell(props) {
|
|
384
|
+
return /* @__PURE__ */ jsx(WeaselProvider, { children: /* @__PURE__ */ jsx(ShellBody, { ...props }) });
|
|
385
|
+
}
|
|
386
|
+
function ShellBody({
|
|
387
|
+
prefs,
|
|
388
|
+
prefsDialog,
|
|
389
|
+
preferences,
|
|
390
|
+
menus,
|
|
391
|
+
menuTitles,
|
|
392
|
+
menuBar = "row",
|
|
393
|
+
menuBarClassName,
|
|
394
|
+
topbar,
|
|
395
|
+
children
|
|
396
|
+
}) {
|
|
397
|
+
useEditorKeys();
|
|
398
|
+
const store = useOpenedPrefs(prefs);
|
|
399
|
+
const [prefsOpen, setPrefsOpen] = useState(false);
|
|
400
|
+
const open = useCallback(() => setPrefsOpen(true), []);
|
|
401
|
+
const prefsValue = useMemo(() => store ? { store, open } : null, [store, open]);
|
|
402
|
+
const schema = useMemo(() => menus ? editorMenuBar(menus, menuTitles) : null, [menus, menuTitles]);
|
|
403
|
+
const bar = schema && /* @__PURE__ */ jsx(MenuBar, { schema, ...menuBarClassName ? { className: menuBarClassName } : {} });
|
|
404
|
+
const inline = menuBar === "inline";
|
|
405
|
+
const top = typeof topbar === "function" ? topbar(inline ? bar : null) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
406
|
+
inline && bar,
|
|
407
|
+
topbar
|
|
408
|
+
] });
|
|
409
|
+
return /* @__PURE__ */ jsxs(EditorPrefsContext.Provider, { value: prefsValue, children: [
|
|
410
|
+
!inline && bar,
|
|
411
|
+
top,
|
|
412
|
+
children,
|
|
413
|
+
store && preferences !== null && /* @__PURE__ */ jsx(PreferencesCommand, { options: preferences, open }),
|
|
414
|
+
store && /* @__PURE__ */ jsx(EditorPrefsDialog, { store, open: prefsOpen, onClose: () => setPrefsOpen(false), dialog: prefsDialog }),
|
|
415
|
+
/* @__PURE__ */ jsx(ContextMenuHost, {})
|
|
416
|
+
] });
|
|
417
|
+
}
|
|
281
418
|
|
|
282
|
-
export { TextEditDepPublisher, describeFileWarnings, docFromSnapshot, downloadBlob, nodeSpecsFromSnapshot, pickFile, readStored, reportFileWarnings, reviveTypedArrays, serializeReplacer, useAutosave, usePageFit, usePageGrid, usePages, usePaperLayer, useSessionPersistence, writeSession };
|
|
419
|
+
export { EDITOR_ACTIONS, EDITOR_MENU_TITLES, EDITOR_PREFERENCES_KEY, EditorPrefsDialog, EditorShell, FILE_KEYS, TextEditDepPublisher, describeFileWarnings, docFromSnapshot, downloadBlob, editorMenuBar, menu, menuRun, nodeSpecsFromSnapshot, pickFile, readStored, reportFileWarnings, reviveTypedArrays, serializeReplacer, useAutosave, useEditorKeys, useEditorPrefs, useNewCommand, useOpenCommand, useOpenPreferences, usePageFit, usePageGrid, usePages, usePaperLayer, useSaveCommand, useSessionPersistence, writeSession };
|
|
283
420
|
//# sourceMappingURL=index.js.map
|
|
284
421
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fileIO.ts","../src/fileWarnings.ts","../src/paperLayer.ts","../src/snapshot.ts","../src/sessionStore.ts","../src/useAutosave.ts","../src/usePageFit.ts","../src/usePages.ts","../src/useSessionPersistence.ts","../src/TextEditDepPublisher.tsx","../src/usePageGrid.ts"],"names":["useRef","useLatest","useEffect","useMemo"],"mappings":"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","file":"index.js","sourcesContent":["/** Trigger a browser download of `blob` as a file named `filename`. */\nexport function downloadBlob(blob: Blob, filename: string): void {\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n // Defer revoke so the click has time to start the download.\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\n/**\n * Pop a hidden file input; resolve with the chosen file, or null if the user\n * cancelled. The input is created and removed per call.\n */\nexport function pickFile(accept: string): Promise<File | null> {\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.hidden = true;\n document.body.appendChild(input);\n const settle = (file: File | null): void => {\n input.remove();\n resolve(file);\n };\n input.addEventListener('change', () => settle(input.files?.[0] ?? null));\n input.addEventListener('cancel', () => settle(null));\n input.click();\n });\n}\n","import { toast } from '@weasel-js/ui/components/Toast';\n\n/** How many distinct warnings a toast quotes before it counts the rest. */\nconst QUOTED = 3;\n\n/** What a file operation did, as a toast's title says it. */\nexport type FileDeed = 'Opened' | 'Imported' | 'Saved' | 'Exported';\n\n/** What a toast says about what a file could not carry: how many, and the first few once each. */\nexport function describeFileWarnings(did: FileDeed, warnings: readonly string[]): { title: string; description: string } {\n const distinct = [...new Set(warnings)];\n const quoted = distinct.slice(0, QUOTED);\n const rest = distinct.length - quoted.length;\n return {\n title: distinct.length === 1 ? `${did} with 1 warning` : `${did} with ${distinct.length} warnings`,\n description: [...quoted, ...(rest > 0 ? [`and ${rest} more`] : [])].join('; '),\n };\n}\n\n/** Tells the user what a file operation left out, in one toast under `id`: the next one replaces it. Nothing for no warnings. */\nexport function reportFileWarnings(did: FileDeed, warnings: readonly string[], id: string): void {\n if (warnings.length === 0) return;\n const { title, description } = describeFileWarnings(did, warnings);\n toast.warn(title, { id, description });\n}\n","import { pagesLayer, type PlacedPage, type RenderLayer } from '@weasel-js/core';\nimport { useMemo } from 'react';\n\nexport interface PageSize { width: number; height: number }\n\n/** The document's pages: world-space rects filled with the document's background color, so the\n * workspace stripes show everywhere outside them. */\nexport function usePaperLayer(pages: readonly PlacedPage[], backgroundColor: string): RenderLayer<unknown> {\n return useMemo<RenderLayer<unknown>>(\n () => pagesLayer(pages, { fill: backgroundColor, id: 'paper', label: 'Paper' }),\n [pages, backgroundColor],\n );\n}\n","// Typed-array and snapshot helpers shared by every editor app's localStorage persistence.\n//\n// A path keeps its commands and coords in typed arrays, which `JSON.stringify`\n// writes as `{\"0\":1,\"1\":2,...}`. Saves tag them with `@weasel-js/geom`'s\n// encoding; loads read the tags back, and also the untagged shapes older saves\n// wrote, which is why the walk here is our own.\n\nimport type {\n AddNodeSpec, SceneRegistry, SavedScene, SpecsFromSerializedOptions,\n} from '@weasel-js/core';\nimport { specsFromSerialized } from '@weasel-js/core';\nimport { typedArrayReplacer, untagTypedArray } from '@weasel-js/geom';\n\n/** `JSON.stringify` replacer: tag typed arrays so they survive the round-trip. */\nexport const serializeReplacer = typedArrayReplacer;\n\n/** Walk a parsed snapshot and rebuild any tagged / numeric-key typed arrays in\n * place. Returns the same node for call-site convenience. */\nexport function reviveTypedArrays<T>(node: T): T {\n if (node == null || typeof node !== 'object') return node;\n const tagged = untagTypedArray(node);\n if (tagged) return tagged as unknown as T;\n const obj = node as Record<string, unknown>;\n for (const k of Object.keys(obj)) {\n if (k === 'commands' || k === 'coords') {\n const v = obj[k];\n if (v instanceof Uint8Array || v instanceof Float32Array) continue;\n if (Array.isArray(v)) {\n obj[k] = k === 'commands' ? Uint8Array.from(v as number[]) : Float32Array.from(v as number[]);\n continue;\n }\n if (v && typeof v === 'object') {\n // Recover from older saves that lost the typed-array shape.\n const src = v as Record<string, unknown>;\n if (src.__ta) { obj[k] = reviveTypedArrays(v); continue; }\n const len = Object.keys(src).filter((kk) => /^\\d+$/.test(kk)).length;\n const arr = new Array<number>(len);\n for (let i = 0; i < len; i++) arr[i] = Number(src[String(i)] ?? 0);\n obj[k] = k === 'commands' ? Uint8Array.from(arr) : Float32Array.from(arr);\n continue;\n }\n } else {\n obj[k] = reviveTypedArrays(obj[k]);\n }\n }\n return node;\n}\n\n/** The document values a persisted snapshot carries, shaped for `useScene`'s `doc` option, or\n * `undefined` for a snapshot written before it had any. */\nexport function docFromSnapshot<TData, TLayer extends string, TPose>(\n json: SavedScene<TData, TLayer, TPose>,\n): Readonly<Record<string, unknown>> | undefined {\n const { doc } = json;\n return doc !== null && typeof doc === 'object' && !Array.isArray(doc) ? doc : undefined;\n}\n\n/** Rebuild the full node list from a persisted scene snapshot, shaped for\n * `useScene`'s `initial` option: core's `specsFromSerialized`, so every field\n * a save writes comes back. A key `registry` lacks leaves the node without\n * that function rather than failing the load, unless `opts.unknownKeys` says\n * `'throw'`; `opts.onSkip` hears each. `revive` rewrites each node's data on\n * the way in; the default revives typed arrays only. */\nexport function nodeSpecsFromSnapshot<TData, TLayer extends string, TPose>(\n json: SavedScene<TData, TLayer, TPose>,\n registry: SceneRegistry<TPose, TData, TLayer> = {},\n revive: <T>(node: T) => T = reviveTypedArrays,\n opts: SpecsFromSerializedOptions<TData, TLayer, TPose> = {},\n): AddNodeSpec<TData, TLayer, TPose>[] {\n return specsFromSerialized(json, registry, { unknownKeys: 'skip', ...opts })\n .map((spec) => ({ ...spec, data: revive(spec.data) }));\n}\n","/** One local-storage entry: its key and the JSON text it holds. */\nexport type SessionEntry = readonly [key: string, json: string];\n\n/**\n * The entry under `key` read through `parse`, or `fallback()` when there is none, when storage\n * cannot be reached, or when `parse` throws on what is there.\n */\nexport function readStored<T>(key: string, parse: (raw: string) => T, fallback: () => T): T {\n try {\n const raw = localStorage.getItem(key);\n return raw ? parse(raw) : fallback();\n } catch {\n return fallback();\n }\n}\n\n/**\n * Write a document and the entries that depend on it, such as its undo stacks, best-effort.\n * A dependent that fails to save after its document did is removed along with the other\n * dependents, because an older undo stack would replay against the wrong document. When the\n * document itself fails, the previous document and its dependents stay as they are. Answers\n * whether the document was saved.\n */\nexport function writeSession(\n storage: Pick<Storage, 'setItem' | 'removeItem'>,\n primary: SessionEntry,\n dependents: readonly SessionEntry[] = [],\n): boolean {\n try {\n storage.setItem(primary[0], primary[1]);\n } catch {\n return false;\n }\n try {\n for (const [key, json] of dependents) storage.setItem(key, json);\n } catch {\n for (const [key] of dependents) {\n try { storage.removeItem(key); } catch { /* best-effort */ }\n }\n }\n return true;\n}\n","import { useLatest } from '@weasel-js/core';\nimport { useEffect, useRef } from 'react';\nimport { type SessionEntry, writeSession } from './sessionStore';\n\n/** What one save writes: the document's entry, and any entries that depend on it, as `writeSession` takes them. */\nexport interface SessionWrite {\n primary: SessionEntry;\n dependents?: readonly SessionEntry[];\n}\n\nexport interface AutosaveOptions {\n /** How long after a change the save runs. Defaults to 300 ms. */\n delayMs?: number;\n /** When given, a save is scheduled on mount and then only when it changes. Without it, every\n * render schedules one. */\n revision?: unknown;\n /** Called after every save attempt with whether the primary entry was written. */\n onSaved?: (ok: boolean) => void;\n}\n\n/**\n * Keeps a document in local storage: runs `write` a short delay after each change, and at once on\n * unmount or when the page goes away. `write` returning null skips that save.\n */\nexport function useAutosave(write: () => SessionWrite | null, opts: AutosaveOptions = {}): void {\n const writeRef = useLatest(write);\n const optsRef = useLatest(opts);\n const pendingRef = useRef<(() => void) | null>(null);\n const tracksRevision = 'revision' in opts;\n\n useEffect(() => {\n const save = (): void => {\n pendingRef.current = null;\n const { onSaved } = optsRef.current;\n try {\n const entries = writeRef.current();\n if (entries === null) return;\n const ok = writeSession(localStorage, entries.primary, entries.dependents);\n onSaved?.(ok);\n } catch {\n // No store at all, or a document that will not serialize.\n onSaved?.(false);\n }\n };\n pendingRef.current = save;\n const id = setTimeout(save, optsRef.current.delayMs ?? 300);\n return () => clearTimeout(id);\n // With a revision, only its change reschedules; a render that leaves it alone must not clear the timer.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, tracksRevision ? [opts.revision] : undefined);\n\n useEffect(() => {\n const flush = (): void => pendingRef.current?.();\n window.addEventListener('pagehide', flush);\n return () => {\n window.removeEventListener('pagehide', flush);\n flush();\n };\n }, []);\n}\n","import { fitViewToBounds, useLatest, type View } from '@weasel-js/core';\nimport { useCallback, useEffect, useRef } from 'react';\nimport type { PageSize } from './paperLayer';\n\nconst NOT_FIT = Symbol('not fit yet');\n\n/**\n * Fits the page — at the world origin unless it says where it is — to the workspace on the first measured size and whenever `fitKey` changes,\n * deliberately not on later resizes, which would hijack the user's pan and zoom. Returns the\n * refit itself, for `viewport.recenter`; its identity never changes.\n */\nexport function usePageFit(\n workspace: { width: number; height: number },\n fitKey: unknown,\n page: PageSize & { x?: number; y?: number },\n setView: (view: View) => void,\n padding = 24,\n): () => void {\n const lastFitKeyRef = useRef<unknown>(NOT_FIT);\n const workspaceRef = useLatest(workspace);\n const pageRef = useLatest(page);\n const setViewRef = useLatest(setView);\n const recenter = useCallback(() => {\n const dims = workspaceRef.current;\n if (dims.width === 0 || dims.height === 0) return;\n const p = pageRef.current;\n setViewRef.current(fitViewToBounds(\n { x: p.x ?? 0, y: p.y ?? 0, width: p.width, height: p.height },\n dims,\n { x: 0, y: 0, scale: { x: 1, y: 1 } },\n { padding },\n ));\n }, [workspaceRef, pageRef, setViewRef, padding]);\n useEffect(() => {\n if (workspace.width === 0 || workspace.height === 0) return;\n if (lastFitKeyRef.current === fitKey) return;\n lastFitKeyRef.current = fitKey;\n recenter();\n }, [fitKey, workspace.width, workspace.height, page.width, page.height, recenter]);\n return recenter;\n}\n","import {\n addPage, adjacentPage, fitViewToPage, movePage, nextPageId, pageInView, pagesOf, placedPagesOf, removePage,\n renamePage, resizePage, useLatest, type PlacedPage, type Scene, type View,\n} from '@weasel-js/core';\nimport { useMemo, useState } from 'react';\nimport type { PageSize } from './paperLayer';\n\n/** A document's pages, which one the view is on, and what can be done to them. */\nexport interface DocPages {\n placed: readonly PlacedPage[];\n /** The page the view shows most of, or `null` between pages. */\n current: string | null;\n /** The current page, else the last one that was while the view is between pages, else the first. */\n shown: PlacedPage | null;\n goTo(id: string): void;\n /** Go to the next page (`1`) or the one before (`-1`). */\n step(by: 1 | -1): void;\n /** Add a page after the current one, the same size, and go to it. */\n add(): void;\n /** Delete a page and what is on it. The last page cannot go. */\n remove(id: string): void;\n move(id: string, index: number): void;\n rename(id: string, name: string): void;\n resize(id: string, size: PageSize): void;\n}\n\ntype AnyScene = Scene<unknown, string, unknown>;\n\n/** The page the view was last on, held while it sits between pages. */\nfunction useShown(placed: readonly PlacedPage[], current: string | null): PlacedPage | null {\n const [last, setLast] = useState<string | null>(null);\n if (current !== null && current !== last) setLast(current);\n const id = current ?? last;\n return placed.find((p) => p.id === id) ?? placed[0] ?? null;\n}\n\n/**\n * A scene's pages, read from its `core:pages` value, with the view following them. Each edit is\n * one of core's page edits, so it moves what is on a page with it, in one undo entry. `padding`\n * is what fitting the view to a page leaves around it, in screen pixels.\n */\nexport function usePages<TData, TLayer extends string, TPose>(\n sceneIn: Scene<TData, TLayer, TPose>,\n view: View,\n dims: { width: number; height: number },\n setView: (view: View) => void,\n padding = 24,\n): DocPages {\n const scene = sceneIn as unknown as AnyScene;\n // The doc value is replaced on every page edit, so its identity is the memo key.\n const doc = pagesOf(scene);\n const placed = useMemo(() => (doc ? placedPagesOf(scene) : []), [scene, doc]);\n const current = dims.width > 0 && dims.height > 0 ? pageInView(placed, view, dims)?.id ?? null : null;\n const shown = useShown(placed, current);\n const latest = useLatest({ view, dims, setView, current });\n return useMemo(() => {\n const show = (page: PlacedPage | null | undefined) => {\n const { view: v, dims: d, setView: set } = latest.current;\n if (page && d.width > 0 && d.height > 0) set(fitViewToPage(page, d, v, { padding }));\n };\n const goTo = (id: string) => show(placedPagesOf(scene).find((p) => p.id === id));\n return {\n placed,\n current,\n shown,\n goTo,\n step: (by) => show(adjacentPage(placedPagesOf(scene), latest.current.current, by)),\n add() {\n const pages = pagesOf(scene)?.pages ?? [];\n const at = pages.findIndex((p) => p.id === latest.current.current);\n const like = pages[at] ?? pages[pages.length - 1];\n const id = nextPageId(pages);\n addPage(scene, { id, width: like?.width ?? 816, height: like?.height ?? 1056 }, { index: at < 0 ? pages.length : at + 1 });\n goTo(id);\n },\n remove(id) {\n if ((pagesOf(scene)?.pages.length ?? 0) > 1) removePage(scene, id);\n },\n move: (id, index) => movePage(scene, id, index),\n rename: (id, name) => renamePage(scene, id, name),\n resize: (id, size) => resizePage(scene, id, size),\n };\n }, [scene, placed, current, shown, latest, padding]);\n}\n","import { defaultCommitAdapter, type Scene, useLatest } from '@weasel-js/core';\nimport { useEffect, useRef } from 'react';\nimport { readStored } from './sessionStore';\nimport { reviveTypedArrays, serializeReplacer } from './snapshot';\nimport { useAutosave } from './useAutosave';\n\nexport interface SessionPersistenceOptions {\n sceneKey: string;\n historyKey: string;\n /** Read the saved undo snapshot; defaults to the parsed `historyKey` entry. Return null for none. */\n loadHistory?: () => unknown;\n /** Called after every save attempt with whether the scene was written. */\n onSaved?: (ok: boolean) => void;\n}\n\nconst parseHistory = (raw: string): unknown => reviveTypedArrays(JSON.parse(raw));\n\n/**\n * Keeps a scene and its undo stack in local storage: restores the stack once on mount,\n * then saves them through `useAutosave` after each render.\n */\nexport function useSessionPersistence<D, L extends string, P>(\n scene: Scene<D, L, P>,\n opts: SessionPersistenceOptions,\n): void {\n const optsRef = useLatest(opts);\n\n // After a reload, a restored entry's ops that wrote no scene state (a selection change) replay\n // against this adapter.\n useEffect(() => {\n const a = defaultCommitAdapter(scene);\n scene.setHistoryAdapter(() => a);\n return () => scene.setHistoryAdapter(null);\n }, [scene]);\n\n // `restoredRef` holds the save off until the restore has run, so it can't clobber the saved\n // history with the empty initial stack first.\n const restoredRef = useRef(false);\n useEffect(() => {\n const { historyKey, loadHistory } = optsRef.current;\n let snap: unknown = null;\n try { snap = loadHistory ? loadHistory() : readStored(historyKey, parseHistory, () => null); } catch { /* treated as none */ }\n if (snap) {\n try {\n scene.restoreHistory(snap as Parameters<typeof scene.restoreHistory>[0]);\n } catch {\n // A parseable-but-malformed snapshot must not wedge every boot:\n // drop it and start with an empty undo stack.\n try { localStorage.removeItem(historyKey); } catch { /* best-effort */ }\n }\n }\n restoredRef.current = true;\n // Run once; the scene is stable for the app lifetime.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Scene and history go under separate keys so a shape migration of one never disturbs the other.\n useAutosave(() => {\n if (!restoredRef.current) return null;\n const { sceneKey, historyKey } = optsRef.current;\n return {\n primary: [sceneKey, JSON.stringify(scene.toJSON(), serializeReplacer)],\n dependents: [[historyKey, JSON.stringify(scene.serializeHistory(), serializeReplacer)]],\n };\n }, { onSaved: (ok) => optsRef.current.onSaved?.(ok) });\n}\n","/**\n * Publishes the app's live text-edit session as the `textEdit` dep, replacing\n * `SceneCanvas`'s warn-only stub.\n *\n * **Mounted as a child of `<SceneCanvas>` on purpose.** The registry is a\n * last-write-wins map and `useDepSource` registers in an effect, so what wins\n * is whichever effect runs last. `{children}` is the final slot in\n * SceneCanvas's tree, after the internal component that registers the stub —\n * so a child registration overrides it, and a *parent* one would not.\n */\nimport { useDepSource, useLatest, type UseSceneTextEditReturn } from '@weasel-js/core';\n\nexport function TextEditDepPublisher({ edit }: { edit: UseSceneTextEditReturn }): null {\n const editRef = useLatest(edit);\n\n useDepSource('textEdit', () => ({\n startEdit: (id, opts) => editRef.current.startEdit(id, opts),\n isTextNode: (id) => editRef.current.isTextNode(id),\n }));\n\n return null;\n}\n","import { contrastLineColor, type RenderLayer, type SnapSource } from '@weasel-js/core';\nimport { createGridLayer, gridSnapSource } from '@weasel-js/guides';\nimport { useMemo } from 'react';\nimport type { PageSize } from './paperLayer';\n\nexport interface PageGridOptions {\n /** The page the grid covers; its lines start at the page's corner, default world `(0, 0)`. */\n page: PageSize & { x?: number; y?: number };\n spacing: number;\n /** The page's fill; line colors are derived from it for contrast. */\n background: string;\n /** Every Nth line is drawn as an accent. */\n accentEvery: number;\n visible: boolean;\n snap: boolean;\n}\n\n/**\n * A grid over the page: the layer to draw while `visible`, and the snap source to hand the\n * canvas while `snap` is on. Both keep their identity until their inputs change.\n */\nexport function usePageGrid(opts: PageGridOptions): { layer: RenderLayer<unknown> | undefined; source: SnapSource | undefined } {\n const { page, spacing, background, accentEvery, visible, snap } = opts;\n const x = page.x ?? 0;\n const y = page.y ?? 0;\n\n const layer = useMemo(() => createGridLayer({\n spacing,\n bounds: () => ({ x, y, width: page.width, height: page.height }),\n accentEvery,\n style: {\n line: { paint: { kind: 'solid', color: contrastLineColor(background, 0.06) }, width: 1 },\n accent: { paint: { kind: 'solid', color: contrastLineColor(background, 0.14) }, width: 1 },\n },\n }), [spacing, x, y, page.width, page.height, background, accentEvery]);\n\n const source = useMemo(() => gridSnapSource({ spacing, origin: { x, y } }), [spacing, x, y]);\n\n return { layer: visible ? layer : undefined, source: snap ? source : undefined };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/fileIO.ts","../src/fileCommands.ts","../src/fileWarnings.ts","../src/paperLayer.ts","../src/snapshot.ts","../src/sessionStore.ts","../src/useAutosave.ts","../src/usePageFit.ts","../src/usePages.ts","../src/useSessionPersistence.ts","../src/TextEditDepPublisher.tsx","../src/usePageGrid.ts","../src/editorCommands.ts","../src/editorPrefs.tsx","../src/menuBar.ts","../src/EditorShell.tsx"],"names":["useRef","useLatest","useEffect","useMemo","useCommand","useState","jsx","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["/** Trigger a browser download of `blob` as a file named `filename`. */\nexport function downloadBlob(blob: Blob, filename: string): void {\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n // Defer revoke so the click has time to start the download.\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\n/**\n * Pop a hidden file input; resolve with the chosen file, or null if the user\n * cancelled. The input is created and removed per call.\n */\nexport function pickFile(accept: string): Promise<File | null> {\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.hidden = true;\n document.body.appendChild(input);\n const settle = (file: File | null): void => {\n input.remove();\n resolve(file);\n };\n input.addEventListener('change', () => settle(input.files?.[0] ?? null));\n input.addEventListener('cancel', () => settle(null));\n input.click();\n });\n}\n","import { useCommand, type BoundGesture, type CommandOptions } from '@weasel-js/core';\n\n/** The keys a file command binds unless its caller names others: Cmd/Ctrl+N, O and S. */\nexport const FILE_KEYS = {\n new: { kind: 'key', key: 'n', mods: { mod: true } },\n open: { kind: 'key', key: 'o', mods: { mod: true } },\n save: { kind: 'key', key: 's', mods: { mod: true } },\n} as const satisfies Record<string, BoundGesture>;\n\n/** A file command's options. Leaving `keys` out binds its `FILE_KEYS` entry; `null` binds none. */\nexport interface FileCommandOptions extends Omit<CommandOptions, 'keys'> {\n keys?: CommandOptions['keys'] | null;\n}\n\ntype FileRun = (params?: Readonly<Record<string, unknown>>) => void;\n\nfunction useFileCommand(\n slot: keyof typeof FILE_KEYS, id: string, label: string, run: FileRun, options: FileCommandOptions | undefined,\n): void {\n const { keys, ...rest } = options ?? {};\n useCommand(id, label, run, keys === null ? rest : { ...rest, keys: keys ?? FILE_KEYS[slot] });\n}\n\n/** `useCommand` for a document's New, bound to Cmd/Ctrl+N by default. */\nexport function useNewCommand(id: string, label: string, run: FileRun, options?: FileCommandOptions): void {\n useFileCommand('new', id, label, run, options);\n}\n\n/** `useCommand` for a document's Open, bound to Cmd/Ctrl+O by default. */\nexport function useOpenCommand(id: string, label: string, run: FileRun, options?: FileCommandOptions): void {\n useFileCommand('open', id, label, run, options);\n}\n\n/** `useCommand` for a document's Save, bound to Cmd/Ctrl+S by default. */\nexport function useSaveCommand(id: string, label: string, run: FileRun, options?: FileCommandOptions): void {\n useFileCommand('save', id, label, run, options);\n}\n","import { toast } from '@weasel-js/ui/components/Toast';\n\n/** How many distinct warnings a toast quotes before it counts the rest. */\nconst QUOTED = 3;\n\n/** What a file operation did, as a toast's title says it. */\nexport type FileDeed = 'Opened' | 'Imported' | 'Saved' | 'Exported';\n\n/** What a toast says about what a file could not carry: how many, and the first few once each. */\nexport function describeFileWarnings(did: FileDeed, warnings: readonly string[]): { title: string; description: string } {\n const distinct = [...new Set(warnings)];\n const quoted = distinct.slice(0, QUOTED);\n const rest = distinct.length - quoted.length;\n return {\n title: distinct.length === 1 ? `${did} with 1 warning` : `${did} with ${distinct.length} warnings`,\n description: [...quoted, ...(rest > 0 ? [`and ${rest} more`] : [])].join('; '),\n };\n}\n\n/** Tells the user what a file operation left out, in one toast under `id`: the next one replaces it. Nothing for no warnings. */\nexport function reportFileWarnings(did: FileDeed, warnings: readonly string[], id: string): void {\n if (warnings.length === 0) return;\n const { title, description } = describeFileWarnings(did, warnings);\n toast.warn(title, { id, description });\n}\n","import { pagesLayer, type PlacedPage, type RenderLayer } from '@weasel-js/core';\nimport { useMemo } from 'react';\n\n/** A page's size, in world units. */\nexport interface PageSize { width: number; height: number }\n\n/** The document's pages: world-space rects filled with the document's background color, so the\n * workspace stripes show everywhere outside them. */\nexport function usePaperLayer(pages: readonly PlacedPage[], backgroundColor: string): RenderLayer<unknown> {\n return useMemo<RenderLayer<unknown>>(\n () => pagesLayer(pages, { fill: backgroundColor, id: 'paper', label: 'Paper' }),\n [pages, backgroundColor],\n );\n}\n","// Typed-array and snapshot helpers shared by every editor app's localStorage persistence.\n//\n// A path keeps its commands and coords in typed arrays, which `JSON.stringify`\n// writes as `{\"0\":1,\"1\":2,...}`. Saves tag them with `@weasel-js/geom`'s\n// encoding; loads read the tags back, and also the untagged shapes older saves\n// wrote, which is why the walk here is our own.\n\nimport type {\n AddNodeSpec, SceneRegistry, SavedScene, SpecsFromSerializedOptions,\n} from '@weasel-js/core';\nimport { specsFromSerialized } from '@weasel-js/core';\nimport { typedArrayReplacer, untagTypedArray } from '@weasel-js/geom';\n\n/** `JSON.stringify` replacer: tag typed arrays so they survive the round-trip. */\nexport const serializeReplacer = typedArrayReplacer;\n\n/** Walk a parsed snapshot and rebuild any tagged / numeric-key typed arrays in\n * place. Returns the same node for call-site convenience. */\nexport function reviveTypedArrays<T>(node: T): T {\n if (node == null || typeof node !== 'object') return node;\n const tagged = untagTypedArray(node);\n if (tagged) return tagged as unknown as T;\n const obj = node as Record<string, unknown>;\n for (const k of Object.keys(obj)) {\n if (k === 'commands' || k === 'coords') {\n const v = obj[k];\n if (v instanceof Uint8Array || v instanceof Float32Array) continue;\n if (Array.isArray(v)) {\n obj[k] = k === 'commands' ? Uint8Array.from(v as number[]) : Float32Array.from(v as number[]);\n continue;\n }\n if (v && typeof v === 'object') {\n // Recover from older saves that lost the typed-array shape.\n const src = v as Record<string, unknown>;\n if (src.__ta) { obj[k] = reviveTypedArrays(v); continue; }\n const len = Object.keys(src).filter((kk) => /^\\d+$/.test(kk)).length;\n const arr = new Array<number>(len);\n for (let i = 0; i < len; i++) arr[i] = Number(src[String(i)] ?? 0);\n obj[k] = k === 'commands' ? Uint8Array.from(arr) : Float32Array.from(arr);\n continue;\n }\n } else {\n obj[k] = reviveTypedArrays(obj[k]);\n }\n }\n return node;\n}\n\n/** The document values a persisted snapshot carries, shaped for `useScene`'s `doc` option, or\n * `undefined` for a snapshot written before it had any. */\nexport function docFromSnapshot<TData, TLayer extends string, TPose>(\n json: SavedScene<TData, TLayer, TPose>,\n): Readonly<Record<string, unknown>> | undefined {\n const { doc } = json;\n return doc !== null && typeof doc === 'object' && !Array.isArray(doc) ? doc : undefined;\n}\n\n/** Rebuild the full node list from a persisted scene snapshot, shaped for\n * `useScene`'s `initial` option: core's `specsFromSerialized`, so every field\n * a save writes comes back. A key `registry` lacks leaves the node without\n * that function rather than failing the load, unless `opts.unknownKeys` says\n * `'throw'`; `opts.onSkip` hears each. `revive` rewrites each node's data on\n * the way in; the default revives typed arrays only. */\nexport function nodeSpecsFromSnapshot<TData, TLayer extends string, TPose>(\n json: SavedScene<TData, TLayer, TPose>,\n registry: SceneRegistry<TPose, TData, TLayer> = {},\n revive: <T>(node: T) => T = reviveTypedArrays,\n opts: SpecsFromSerializedOptions<TData, TLayer, TPose> = {},\n): AddNodeSpec<TData, TLayer, TPose>[] {\n return specsFromSerialized(json, registry, { unknownKeys: 'skip', ...opts })\n .map((spec) => ({ ...spec, data: revive(spec.data) }));\n}\n","/** One local-storage entry: its key and the JSON text it holds. */\nexport type SessionEntry = readonly [key: string, json: string];\n\n/**\n * The entry under `key` read through `parse`, or `fallback()` when there is none, when storage\n * cannot be reached, or when `parse` throws on what is there.\n */\nexport function readStored<T>(key: string, parse: (raw: string) => T, fallback: () => T): T {\n try {\n const raw = localStorage.getItem(key);\n return raw ? parse(raw) : fallback();\n } catch {\n return fallback();\n }\n}\n\n/**\n * Write a document and the entries that depend on it, such as its undo stacks, best-effort.\n * A dependent that fails to save after its document did is removed along with the other\n * dependents, because an older undo stack would replay against the wrong document. When the\n * document itself fails, the previous document and its dependents stay as they are. Answers\n * whether the document was saved.\n */\nexport function writeSession(\n storage: Pick<Storage, 'setItem' | 'removeItem'>,\n primary: SessionEntry,\n dependents: readonly SessionEntry[] = [],\n): boolean {\n try {\n storage.setItem(primary[0], primary[1]);\n } catch {\n return false;\n }\n try {\n for (const [key, json] of dependents) storage.setItem(key, json);\n } catch {\n for (const [key] of dependents) {\n try { storage.removeItem(key); } catch { /* best-effort */ }\n }\n }\n return true;\n}\n","import { useLatest } from '@weasel-js/core';\nimport { useEffect, useRef } from 'react';\nimport { type SessionEntry, writeSession } from './sessionStore';\n\n/** What one save writes: the document's entry, and any entries that depend on it, as `writeSession` takes them. */\nexport interface SessionWrite {\n primary: SessionEntry;\n dependents?: readonly SessionEntry[];\n}\n\n/** When `useAutosave` saves, and what it reports after. */\nexport interface AutosaveOptions {\n /** How long after a change the save runs. Defaults to 300 ms. */\n delayMs?: number;\n /** When given, a save is scheduled on mount and then only when it changes. Without it, every\n * render schedules one. */\n revision?: unknown;\n /** Called after every save attempt with whether the primary entry was written. */\n onSaved?: (ok: boolean) => void;\n}\n\n/**\n * Keeps a document in local storage: runs `write` a short delay after each change, and at once on\n * unmount or when the page goes away. `write` returning null skips that save.\n */\nexport function useAutosave(write: () => SessionWrite | null, opts: AutosaveOptions = {}): void {\n const writeRef = useLatest(write);\n const optsRef = useLatest(opts);\n const pendingRef = useRef<(() => void) | null>(null);\n const tracksRevision = 'revision' in opts;\n\n useEffect(() => {\n const save = (): void => {\n pendingRef.current = null;\n const { onSaved } = optsRef.current;\n try {\n const entries = writeRef.current();\n if (entries === null) return;\n const ok = writeSession(localStorage, entries.primary, entries.dependents);\n onSaved?.(ok);\n } catch {\n // No store at all, or a document that will not serialize.\n onSaved?.(false);\n }\n };\n pendingRef.current = save;\n const id = setTimeout(save, optsRef.current.delayMs ?? 300);\n return () => clearTimeout(id);\n // With a revision, only its change reschedules; a render that leaves it alone must not clear the timer.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, tracksRevision ? [opts.revision] : undefined);\n\n useEffect(() => {\n const flush = (): void => pendingRef.current?.();\n window.addEventListener('pagehide', flush);\n return () => {\n window.removeEventListener('pagehide', flush);\n flush();\n };\n }, []);\n}\n","import { fitViewToBounds, useLatest, type View } from '@weasel-js/core';\nimport { useCallback, useEffect, useRef } from 'react';\nimport type { PageSize } from './paperLayer';\n\nconst NOT_FIT = Symbol('not fit yet');\n\n/**\n * Fits the page — at the world origin unless it says where it is — to the workspace on the first measured size and whenever `fitKey` changes,\n * deliberately not on later resizes, which would hijack the user's pan and zoom. Returns the\n * refit itself, for `viewport.recenter`; its identity never changes.\n */\nexport function usePageFit(\n workspace: { width: number; height: number },\n fitKey: unknown,\n page: PageSize & { x?: number; y?: number },\n setView: (view: View) => void,\n padding = 24,\n): () => void {\n const lastFitKeyRef = useRef<unknown>(NOT_FIT);\n const workspaceRef = useLatest(workspace);\n const pageRef = useLatest(page);\n const setViewRef = useLatest(setView);\n const recenter = useCallback(() => {\n const dims = workspaceRef.current;\n if (dims.width === 0 || dims.height === 0) return;\n const p = pageRef.current;\n setViewRef.current(fitViewToBounds(\n { x: p.x ?? 0, y: p.y ?? 0, width: p.width, height: p.height },\n dims,\n { x: 0, y: 0, scale: { x: 1, y: 1 } },\n { padding },\n ));\n }, [workspaceRef, pageRef, setViewRef, padding]);\n useEffect(() => {\n if (workspace.width === 0 || workspace.height === 0) return;\n if (lastFitKeyRef.current === fitKey) return;\n lastFitKeyRef.current = fitKey;\n recenter();\n }, [fitKey, workspace.width, workspace.height, page.width, page.height, recenter]);\n return recenter;\n}\n","import {\n addPage, adjacentPage, fitViewToPage, movePage, nextPageId, pageInView, pagesOf, placedPagesOf, removePage,\n renamePage, resizePage, useLatest, type PlacedPage, type Scene, type View,\n} from '@weasel-js/core';\nimport { useMemo, useState } from 'react';\nimport type { PageSize } from './paperLayer';\n\n/** A document's pages, which one the view is on, and what can be done to them. */\nexport interface DocPages {\n placed: readonly PlacedPage[];\n /** The page the view shows most of, or `null` between pages. */\n current: string | null;\n /** The current page, else the last one that was while the view is between pages, else the first. */\n shown: PlacedPage | null;\n goTo(id: string): void;\n /** Go to the next page (`1`) or the one before (`-1`). */\n step(by: 1 | -1): void;\n /** Add a page after the current one, the same size, and go to it. */\n add(): void;\n /** Delete a page and what is on it. The last page cannot go. */\n remove(id: string): void;\n move(id: string, index: number): void;\n rename(id: string, name: string): void;\n resize(id: string, size: PageSize): void;\n}\n\ntype AnyScene = Scene<unknown, string, unknown>;\n\n/** The page the view was last on, held while it sits between pages. */\nfunction useShown(placed: readonly PlacedPage[], current: string | null): PlacedPage | null {\n const [last, setLast] = useState<string | null>(null);\n if (current !== null && current !== last) setLast(current);\n const id = current ?? last;\n return placed.find((p) => p.id === id) ?? placed[0] ?? null;\n}\n\n/**\n * A scene's pages, read from its `core:pages` value, with the view following them. Each edit is\n * one of core's page edits, so it moves what is on a page with it, in one undo entry. `padding`\n * is what fitting the view to a page leaves around it, in screen pixels.\n */\nexport function usePages<TData, TLayer extends string, TPose>(\n sceneIn: Scene<TData, TLayer, TPose>,\n view: View,\n dims: { width: number; height: number },\n setView: (view: View) => void,\n padding = 24,\n): DocPages {\n const scene = sceneIn as unknown as AnyScene;\n // The doc value is replaced on every page edit, so its identity is the memo key.\n const doc = pagesOf(scene);\n const placed = useMemo(() => (doc ? placedPagesOf(scene) : []), [scene, doc]);\n const current = dims.width > 0 && dims.height > 0 ? pageInView(placed, view, dims)?.id ?? null : null;\n const shown = useShown(placed, current);\n const latest = useLatest({ view, dims, setView, current });\n return useMemo(() => {\n const show = (page: PlacedPage | null | undefined) => {\n const { view: v, dims: d, setView: set } = latest.current;\n if (page && d.width > 0 && d.height > 0) set(fitViewToPage(page, d, v, { padding }));\n };\n const goTo = (id: string) => show(placedPagesOf(scene).find((p) => p.id === id));\n return {\n placed,\n current,\n shown,\n goTo,\n step: (by) => show(adjacentPage(placedPagesOf(scene), latest.current.current, by)),\n add() {\n const pages = pagesOf(scene)?.pages ?? [];\n const at = pages.findIndex((p) => p.id === latest.current.current);\n const like = pages[at] ?? pages[pages.length - 1];\n const id = nextPageId(pages);\n addPage(scene, { id, width: like?.width ?? 816, height: like?.height ?? 1056 }, { index: at < 0 ? pages.length : at + 1 });\n goTo(id);\n },\n remove(id) {\n if ((pagesOf(scene)?.pages.length ?? 0) > 1) removePage(scene, id);\n },\n move: (id, index) => movePage(scene, id, index),\n rename: (id, name) => renamePage(scene, id, name),\n resize: (id, size) => resizePage(scene, id, size),\n };\n }, [scene, placed, current, shown, latest, padding]);\n}\n","import { defaultCommitAdapter, type Scene, useLatest } from '@weasel-js/core';\nimport { useEffect, useRef } from 'react';\nimport { readStored } from './sessionStore';\nimport { reviveTypedArrays, serializeReplacer } from './snapshot';\nimport { useAutosave } from './useAutosave';\n\n/** Where `useSessionPersistence` keeps a scene and its undo stack, and what it reports after a save. */\nexport interface SessionPersistenceOptions {\n /** The local-storage key the scene is saved under. */\n sceneKey: string;\n /** The local-storage key the undo stack is saved under. */\n historyKey: string;\n /** Read the saved undo snapshot; defaults to the parsed `historyKey` entry. Return null for none. */\n loadHistory?: () => unknown;\n /** Called after every save attempt with whether the scene was written. */\n onSaved?: (ok: boolean) => void;\n}\n\nconst parseHistory = (raw: string): unknown => reviveTypedArrays(JSON.parse(raw));\n\n/**\n * Keeps a scene and its undo stack in local storage: restores the stack once on mount,\n * then saves them through `useAutosave` after each render.\n */\nexport function useSessionPersistence<D, L extends string, P>(\n scene: Scene<D, L, P>,\n opts: SessionPersistenceOptions,\n): void {\n const optsRef = useLatest(opts);\n\n // After a reload, a restored entry's ops that wrote no scene state (a selection change) replay\n // against this adapter.\n useEffect(() => {\n const a = defaultCommitAdapter(scene);\n scene.setHistoryAdapter(() => a);\n return () => scene.setHistoryAdapter(null);\n }, [scene]);\n\n // `restoredRef` holds the save off until the restore has run, so it can't clobber the saved\n // history with the empty initial stack first.\n const restoredRef = useRef(false);\n useEffect(() => {\n const { historyKey, loadHistory } = optsRef.current;\n let snap: unknown = null;\n try { snap = loadHistory ? loadHistory() : readStored(historyKey, parseHistory, () => null); } catch { /* treated as none */ }\n if (snap) {\n try {\n scene.restoreHistory(snap as Parameters<typeof scene.restoreHistory>[0]);\n } catch {\n // A parseable-but-malformed snapshot must not wedge every boot:\n // drop it and start with an empty undo stack.\n try { localStorage.removeItem(historyKey); } catch { /* best-effort */ }\n }\n }\n restoredRef.current = true;\n // Run once; the scene is stable for the app lifetime.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Scene and history go under separate keys so a shape migration of one never disturbs the other.\n useAutosave(() => {\n if (!restoredRef.current) return null;\n const { sceneKey, historyKey } = optsRef.current;\n return {\n primary: [sceneKey, JSON.stringify(scene.toJSON(), serializeReplacer)],\n dependents: [[historyKey, JSON.stringify(scene.serializeHistory(), serializeReplacer)]],\n };\n }, { onSaved: (ok) => optsRef.current.onSaved?.(ok) });\n}\n","import { useDepSource, useLatest, type UseSceneTextEditReturn } from '@weasel-js/core';\n\n/**\n * Publishes the app's live text-edit session as the `textEdit` dep, replacing\n * `SceneCanvas`'s warn-only stub.\n *\n * **Mounted as a child of `<SceneCanvas>` on purpose.** The registry is a\n * last-write-wins map and `useDepSource` registers in an effect, so what wins\n * is whichever effect runs last. `{children}` is the final slot in\n * SceneCanvas's tree, after the internal component that registers the stub —\n * so a child registration overrides it, and a *parent* one would not.\n */\nexport function TextEditDepPublisher({ edit }: { edit: UseSceneTextEditReturn }): null {\n const editRef = useLatest(edit);\n\n useDepSource('textEdit', () => ({\n startEdit: (id, opts) => editRef.current.startEdit(id, opts),\n isTextNode: (id) => editRef.current.isTextNode(id),\n }));\n\n return null;\n}\n","import { contrastLineColor, type RenderLayer, type SnapSource } from '@weasel-js/core';\nimport { createGridLayer, gridSnapSource } from '@weasel-js/guides';\nimport { useMemo } from 'react';\nimport type { PageSize } from './paperLayer';\n\n/** The page a `usePageGrid` grid covers, its pitch and look, and whether it shows and snaps. */\nexport interface PageGridOptions {\n /** The page the grid covers; its lines start at the page's corner, default world `(0, 0)`. */\n page: PageSize & { x?: number; y?: number };\n spacing: number;\n /** The page's fill; line colors are derived from it for contrast. */\n background: string;\n /** Every Nth line is drawn as an accent. */\n accentEvery: number;\n visible: boolean;\n snap: boolean;\n}\n\n/**\n * A grid over the page: the layer to draw while `visible`, and the snap source to hand the\n * canvas while `snap` is on. Both keep their identity until their inputs change.\n */\nexport function usePageGrid(opts: PageGridOptions): { layer: RenderLayer<unknown> | undefined; source: SnapSource | undefined } {\n const { page, spacing, background, accentEvery, visible, snap } = opts;\n const x = page.x ?? 0;\n const y = page.y ?? 0;\n\n const layer = useMemo(() => createGridLayer({\n spacing,\n bounds: () => ({ x, y, width: page.width, height: page.height }),\n accentEvery,\n style: {\n line: { paint: { kind: 'solid', color: contrastLineColor(background, 0.06) }, width: 1 },\n accent: { paint: { kind: 'solid', color: contrastLineColor(background, 0.14) }, width: 1 },\n },\n }), [spacing, x, y, page.width, page.height, background, accentEvery]);\n\n const source = useMemo(() => gridSnapSource({ spacing, origin: { x, y } }), [spacing, x, y]);\n\n return { layer: visible ? layer : undefined, source: snap ? source : undefined };\n}\n","import { type BoundGesture, type CommandOptions, useActionsRegistry, useCommand, useGestureDispatcher } from '@weasel-js/core';\n\n/** The ids of the commands `<EditorShell>` registers. */\nexport const EDITOR_ACTIONS = { preferences: 'editor.preferences' } as const;\n\n/** How the shell registers one of its commands. */\nexport interface EditorCommandOptions extends Pick<CommandOptions, 'icon' | 'group' | 'tags'> {\n label?: string;\n /** The gestures that run it, in place of its default; `null` binds none. */\n keys?: BoundGesture | readonly BoundGesture[] | null;\n}\n\n/** `editor.preferences`'s key unless `keys` says otherwise: Mod+,. */\nexport const EDITOR_PREFERENCES_KEY: BoundGesture = { kind: 'key', key: ',', mods: { mod: true } };\n\n/** Registers `editor.preferences`, which calls `open`, bound to Mod+, unless `options.keys` says otherwise. */\nexport function PreferencesCommand({ options = {}, open }: { options?: EditorCommandOptions; open: () => void }): null {\n const { label = 'Preferences…', keys = EDITOR_PREFERENCES_KEY, ...rest } = options;\n useCommand(EDITOR_ACTIONS.preferences, label, open, { ...rest, ...(keys !== null ? { keys } : {}) });\n return null;\n}\n\nconst NO_ELEMENT = { current: null };\nconst NO_ENTRIES = new Map();\nconst CHANNELS = { contextMenu: false, ingest: false } as const;\n\n/**\n * The app's keys with no canvas holding them: a dispatcher on the registry in scope that runs what is registered\n * there. It listens first, so a canvas in a scope below does not run the same binding as well, and it hears nothing\n * while a canvas on this registry itself holds the keys, which then runs them with its own views and deps.\n */\nexport function useEditorKeys(): void {\n const registry = useActionsRegistry();\n if (!registry) throw new Error('useEditorKeys: no <ActionsProvider> in scope');\n useGestureDispatcher({\n canvasRef: NO_ELEMENT,\n actions: registry,\n entriesById: NO_ENTRIES,\n channels: CHANNELS,\n keyboard: 'first',\n hears: 'own',\n yieldKeys: true,\n });\n}\n","import { createContext, useContext, useState } from 'react';\nimport { openPrefsSync, type PrefGroup, type PrefsStore } from '@weasel-js/prefs';\nimport { usePrefsValues } from '@weasel-js/prefs/react';\nimport { PrefsDialog, type PrefsDialogProps } from '@weasel-js/ui/components/Prefs';\n\n/** What `openPrefsSync` takes besides the schema. */\nexport type EditorPrefsOptions = Parameters<typeof openPrefsSync>[1];\n\n/** An editor's preferences: a store already open, or what `openPrefsSync` needs to open one. Options are read once,\n * on mount, and the store they open is kept for the page's life; pass a store where the shell can remount. */\nexport type EditorPrefs<S extends PrefGroup = PrefGroup> = PrefsStore<S> | ({ schema: S } & EditorPrefsOptions);\n\n/** What the preferences dialog takes past the store's own binding: `layout`, `renderers` for the app's own kinds,\n * `headerExtra`, `showHidden`, and the rest of `PrefsDialog`'s props. `layout` defaults to `'rail'`. */\nexport type EditorPrefsDialogProps = Partial<\n Omit<PrefsDialogProps, 'isOpen' | 'onOpenChange' | 'schema' | 'values' | 'onChange' | 'auto' | 'onAutoChange'>\n>;\n\ninterface EditorPrefsValue {\n store: PrefsStore<PrefGroup>;\n open(): void;\n}\n\nexport const EditorPrefsContext = createContext<EditorPrefsValue | null>(null);\n\nconst isStore = <S extends PrefGroup>(prefs: EditorPrefs<S>): prefs is PrefsStore<S> => !('storage' in prefs);\n\n/** `prefs` as a store: itself, or the one its options opened on the first render. */\nexport function useOpenedPrefs<S extends PrefGroup>(prefs: EditorPrefs<S> | undefined): PrefsStore<S> | null {\n const [opened] = useState(() => (prefs && !isStore(prefs) ? openPrefsSync(prefs.schema, prefs) : null));\n return prefs && isStore(prefs) ? prefs : opened;\n}\n\n/** The preferences store of the `<EditorShell>` above, or null outside one or when it was given none. `S` is the\n * schema the caller knows the store holds. */\nexport function useEditorPrefs<S extends PrefGroup = PrefGroup>(): PrefsStore<S> | null {\n return (useContext(EditorPrefsContext)?.store ?? null) as PrefsStore<S> | null;\n}\n\nconst NOTHING = (): void => {};\n\n/** Opens the `<EditorShell>`'s preferences dialog. Does nothing outside one, or when it was given no prefs. */\nexport function useOpenPreferences(): () => void {\n return useContext(EditorPrefsContext)?.open ?? NOTHING;\n}\n\n/** `PrefsDialog` bound to `store`: each change is written at once, and turning a leaf's Auto on resets it. */\nexport function EditorPrefsDialog({ store, open, onClose, dialog }: {\n store: PrefsStore<PrefGroup>;\n open: boolean;\n onClose: () => void;\n dialog?: EditorPrefsDialogProps;\n}) {\n const { values, set, unset, reset } = usePrefsValues(store);\n return (\n <PrefsDialog\n layout=\"rail\"\n {...dialog}\n isOpen={open}\n onOpenChange={(next) => { if (!next) onClose(); }}\n schema={store.schema}\n values={values}\n onChange={set}\n auto={unset}\n onAutoChange={(path, next) => (next ? reset(path) : set(path, store.get(path as never)))}\n />\n );\n}\n","import { pref, type PrefGroup } from '@weasel-js/prefs';\n\n/** A menu's runs: each is a group of items, set off from the next by a separator. */\nexport type MenuRuns = Record<string, PrefGroup>;\n\n/** What each menu title holds, by the title's key. A title with no entry is left off the bar unless it is `always`. */\nexport type MenuContributions = Readonly<Record<string, MenuRuns | undefined>>;\n\n/** A title on the menu bar. */\nexport interface MenuTitle {\n /** The key its contribution is filed under. */\n key: string;\n title: string;\n /** The access letter that opens it. */\n access?: string;\n /** Shown even when nothing is contributed to it. */\n always?: boolean;\n}\n\n/** File and Edit, always shown; then View, Format, Arrange, and Page, each shown once something is contributed. */\nexport const EDITOR_MENU_TITLES: readonly MenuTitle[] = [\n { key: 'file', title: 'File', access: 'f', always: true },\n { key: 'edit', title: 'Edit', access: 'e', always: true },\n { key: 'view', title: 'View', access: 'v' },\n { key: 'format', title: 'Format', access: 'o' },\n { key: 'arrange', title: 'Arrange', access: 'a' },\n { key: 'page', title: 'Page', access: 'p' },\n];\n\n/** A run of menu items from `{ key: actionId }`. A bare id lists every variant its action has. */\nexport function menuRun(ids: Readonly<Record<string, string>>): PrefGroup {\n return pref.group('', Object.fromEntries(Object.entries(ids).map(([key, id]) => [key, pref.action(id)])));\n}\n\n/** A menu title over `runs`. */\nexport function menu(title: string, runs: MenuRuns, access?: string): PrefGroup {\n return pref.group(title, runs, { as: 'menu', ...(access !== undefined ? { access } : {}) });\n}\n\n/** The menu bar `MenuBar` draws: a menu per title in `titles` order, from what `contributions` files under each.\n * A contribution whose key no title names is left out. */\nexport function editorMenuBar(\n contributions: MenuContributions,\n titles: readonly MenuTitle[] = EDITOR_MENU_TITLES,\n): PrefGroup {\n const menus: Record<string, PrefGroup> = {};\n for (const { key, title, access, always } of titles) {\n const runs = contributions[key];\n if (runs !== undefined || always) menus[key] = menu(title, runs ?? {}, access);\n }\n return pref.group('Menu bar', menus, { as: 'bar' });\n}\n","import { useCallback, useMemo, useState, type ReactNode } from 'react';\nimport { WeaselProvider } from '@weasel-js/core';\nimport type { PrefGroup } from '@weasel-js/prefs';\nimport { ContextMenuHost } from '@weasel-js/ui/components/ContextMenu';\nimport { MenuBar } from '@weasel-js/ui/components/MenuBar';\nimport { type EditorCommandOptions, PreferencesCommand, useEditorKeys } from './editorCommands';\nimport {\n type EditorPrefs, EditorPrefsContext, EditorPrefsDialog, type EditorPrefsDialogProps, useOpenedPrefs,\n} from './editorPrefs';\nimport { editorMenuBar, type MenuContributions, type MenuTitle } from './menuBar';\n\n/** Props for {@link EditorShell}. */\nexport interface EditorShellProps<S extends PrefGroup = PrefGroup> {\n /** The app's preferences. With them the shell registers `editor.preferences` and draws its dialog. */\n prefs?: EditorPrefs<S>;\n /** The preferences dialog's own props: its layout, renderers for the app's kinds, a header switch. */\n prefsDialog?: EditorPrefsDialogProps;\n /** How `editor.preferences` registers; `null` registers none. Ignored without `prefs`. */\n preferences?: EditorCommandOptions | null;\n /** What each menu holds, by title key. Without it there is no menu bar. Pass a new value to change the menus. */\n menus?: MenuContributions;\n /** The menu titles and their order. Default `EDITOR_MENU_TITLES`. */\n menuTitles?: readonly MenuTitle[];\n /**\n * Where the menu bar goes. `'row'` (default): on its own row, above the topbar. `'inline'`: in the topbar. A\n * `topbar` function is handed the bar to place; a plain one is drawn after it.\n */\n menuBar?: 'row' | 'inline';\n menuBarClassName?: string;\n /** The row of controls under the menu bar. A function receives the menu bar when it is inline, else `null`. */\n topbar?: ReactNode | ((menuBar: ReactNode) => ReactNode);\n children?: ReactNode;\n}\n\n/**\n * The frame of an editor app: the kit's providers where none is in scope already (deps, actions, the active tool,\n * the selection, and the pointer), a dispatcher that runs the app's command keys with no canvas holding them, the\n * menu bar and topbar, the preferences store and dialog, and the context menu host. `children` render below the\n * topbar. Draws no element of its own: what it renders is laid out by the app's container.\n */\nexport function EditorShell<S extends PrefGroup = PrefGroup>(props: EditorShellProps<S>) {\n return (\n <WeaselProvider>\n <ShellBody {...(props as EditorShellProps)} />\n </WeaselProvider>\n );\n}\n\nfunction ShellBody({\n prefs, prefsDialog, preferences, menus, menuTitles, menuBar = 'row', menuBarClassName, topbar, children,\n}: EditorShellProps) {\n useEditorKeys();\n const store = useOpenedPrefs(prefs);\n const [prefsOpen, setPrefsOpen] = useState(false);\n const open = useCallback(() => setPrefsOpen(true), []);\n const prefsValue = useMemo(() => (store ? { store, open } : null), [store, open]);\n const schema = useMemo(() => (menus ? editorMenuBar(menus, menuTitles) : null), [menus, menuTitles]);\n const bar = schema && <MenuBar schema={schema} {...(menuBarClassName ? { className: menuBarClassName } : {})} />;\n const inline = menuBar === 'inline';\n const top = typeof topbar === 'function' ? topbar(inline ? bar : null) : <>{inline && bar}{topbar}</>;\n\n return (\n <EditorPrefsContext.Provider value={prefsValue}>\n {!inline && bar}\n {top}\n {children}\n {store && preferences !== null && <PreferencesCommand options={preferences} open={open} />}\n {store && (\n <EditorPrefsDialog store={store} open={prefsOpen} onClose={() => setPrefsOpen(false)} dialog={prefsDialog} />\n )}\n <ContextMenuHost />\n </EditorPrefsContext.Provider>\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/editor",
|
|
3
|
-
"version": "1.15.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.15.2",
|
|
4
|
+
"description": "The frame and glue of a weasel editor app: a shell with the kit's providers, app-wide command keys, a menu bar, a topbar, and a preferences dialog; and autosave, session persistence, snapshot revival, file download and pick, pages, page fit, and the page's paper and grid.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"react": ">=18",
|
|
20
|
-
"@weasel-js/core": "1.15.
|
|
21
|
-
"@weasel-js/geom": "1.15.
|
|
22
|
-
"@weasel-js/guides": "1.15.
|
|
23
|
-
"@weasel-js/
|
|
20
|
+
"@weasel-js/core": "1.15.2",
|
|
21
|
+
"@weasel-js/geom": "1.15.2",
|
|
22
|
+
"@weasel-js/guides": "1.15.2",
|
|
23
|
+
"@weasel-js/prefs": "1.15.2",
|
|
24
|
+
"@weasel-js/ui": "1.15.2"
|
|
24
25
|
},
|
|
25
26
|
"author": "orochi235",
|
|
26
27
|
"homepage": "https://orochi235.github.io/weasel/",
|