@rpgjs/map-builder 0.0.0-stage → 5.0.0
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/LICENSE +19 -0
- package/README.md +341 -2
- package/dist/authority/authority.d.ts +118 -0
- package/dist/authority/authority.d.ts.map +1 -0
- package/dist/authority/authority.js +289 -0
- package/dist/authority/authority.js.map +1 -0
- package/dist/authority/index.d.ts +3 -0
- package/dist/authority/index.d.ts.map +1 -0
- package/dist/authority/index.js +3 -0
- package/dist/authority/store.d.ts +36 -0
- package/dist/authority/store.d.ts.map +1 -0
- package/dist/authority/store.js +35 -0
- package/dist/authority/store.js.map +1 -0
- package/dist/client/index.d.ts +72 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +200 -0
- package/dist/client/index.js.map +1 -0
- package/dist/embed/client.d.ts +85 -0
- package/dist/embed/client.d.ts.map +1 -0
- package/dist/embed/client.js +255 -0
- package/dist/embed/client.js.map +1 -0
- package/dist/embed/host.d.ts +112 -0
- package/dist/embed/host.d.ts.map +1 -0
- package/dist/embed/host.js +278 -0
- package/dist/embed/host.js.map +1 -0
- package/dist/embed/index.d.ts +4 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/embed/index.js +4 -0
- package/dist/embed/protocol.d.ts +137 -0
- package/dist/embed/protocol.d.ts.map +1 -0
- package/dist/embed/protocol.js +161 -0
- package/dist/embed/protocol.js.map +1 -0
- package/dist/features/index.d.ts +66 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +157 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/skin.d.ts +9 -0
- package/dist/features/skin.d.ts.map +1 -0
- package/dist/features/skin.js +43 -0
- package/dist/features/skin.js.map +1 -0
- package/dist/features/terrain.d.ts +66 -0
- package/dist/features/terrain.d.ts.map +1 -0
- package/dist/features/terrain.js +291 -0
- package/dist/features/terrain.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/interaction/builtin-intents.d.ts +9 -0
- package/dist/interaction/builtin-intents.d.ts.map +1 -0
- package/dist/interaction/builtin-intents.js +61 -0
- package/dist/interaction/builtin-intents.js.map +1 -0
- package/dist/interaction/catalog.d.ts +24 -0
- package/dist/interaction/catalog.d.ts.map +1 -0
- package/dist/interaction/catalog.js +20 -0
- package/dist/interaction/catalog.js.map +1 -0
- package/dist/interaction/controller.d.ts +137 -0
- package/dist/interaction/controller.d.ts.map +1 -0
- package/dist/interaction/controller.js +215 -0
- package/dist/interaction/controller.js.map +1 -0
- package/dist/interaction/index.d.ts +14 -0
- package/dist/interaction/index.d.ts.map +1 -0
- package/dist/interaction/index.js +13 -0
- package/dist/interaction/input.d.ts +26 -0
- package/dist/interaction/input.d.ts.map +1 -0
- package/dist/interaction/input.js +33 -0
- package/dist/interaction/input.js.map +1 -0
- package/dist/interaction/intents.d.ts +80 -0
- package/dist/interaction/intents.d.ts.map +1 -0
- package/dist/interaction/keyboard.d.ts +26 -0
- package/dist/interaction/keyboard.d.ts.map +1 -0
- package/dist/interaction/keyboard.js +32 -0
- package/dist/interaction/keyboard.js.map +1 -0
- package/dist/interaction/picking.d.ts +33 -0
- package/dist/interaction/picking.d.ts.map +1 -0
- package/dist/interaction/picking.js +35 -0
- package/dist/interaction/picking.js.map +1 -0
- package/dist/interaction/pipeline.d.ts +45 -0
- package/dist/interaction/pipeline.d.ts.map +1 -0
- package/dist/interaction/pipeline.js +26 -0
- package/dist/interaction/pipeline.js.map +1 -0
- package/dist/interaction/placement.d.ts +76 -0
- package/dist/interaction/placement.d.ts.map +1 -0
- package/dist/interaction/placement.js +238 -0
- package/dist/interaction/placement.js.map +1 -0
- package/dist/interaction/selection-tool.d.ts +29 -0
- package/dist/interaction/selection-tool.d.ts.map +1 -0
- package/dist/interaction/selection-tool.js +190 -0
- package/dist/interaction/selection-tool.js.map +1 -0
- package/dist/interaction/steps.d.ts +62 -0
- package/dist/interaction/steps.d.ts.map +1 -0
- package/dist/interaction/steps.js +126 -0
- package/dist/interaction/steps.js.map +1 -0
- package/dist/interaction/store.d.ts +10 -0
- package/dist/interaction/store.d.ts.map +1 -0
- package/dist/interaction/store.js +28 -0
- package/dist/interaction/store.js.map +1 -0
- package/dist/interaction/tools.d.ts +26 -0
- package/dist/interaction/tools.d.ts.map +1 -0
- package/dist/interaction/tools.js +41 -0
- package/dist/interaction/tools.js.map +1 -0
- package/dist/model/assets.d.ts +36 -0
- package/dist/model/assets.d.ts.map +1 -0
- package/dist/model/assets.js +18 -0
- package/dist/model/assets.js.map +1 -0
- package/dist/model/committer.d.ts +66 -0
- package/dist/model/committer.d.ts.map +1 -0
- package/dist/model/committer.js +38 -0
- package/dist/model/committer.js.map +1 -0
- package/dist/model/core-patches.d.ts +2 -0
- package/dist/model/core-patches.d.ts.map +1 -0
- package/dist/model/core-patches.js +193 -0
- package/dist/model/core-patches.js.map +1 -0
- package/dist/model/define-map.d.ts +52 -0
- package/dist/model/define-map.d.ts.map +1 -0
- package/dist/model/define-map.js +66 -0
- package/dist/model/define-map.js.map +1 -0
- package/dist/model/definition.d.ts +57 -0
- package/dist/model/definition.d.ts.map +1 -0
- package/dist/model/definition.js +6 -0
- package/dist/model/definition.js.map +1 -0
- package/dist/model/errors.d.ts +21 -0
- package/dist/model/errors.d.ts.map +1 -0
- package/dist/model/errors.js +14 -0
- package/dist/model/errors.js.map +1 -0
- package/dist/model/history.d.ts +43 -0
- package/dist/model/history.d.ts.map +1 -0
- package/dist/model/history.js +141 -0
- package/dist/model/history.js.map +1 -0
- package/dist/model/index.d.ts +14 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +14 -0
- package/dist/model/json.d.ts +15 -0
- package/dist/model/json.d.ts.map +1 -0
- package/dist/model/json.js +35 -0
- package/dist/model/json.js.map +1 -0
- package/dist/model/model.d.ts +68 -0
- package/dist/model/model.d.ts.map +1 -0
- package/dist/model/model.js +170 -0
- package/dist/model/model.js.map +1 -0
- package/dist/model/parse.d.ts +24 -0
- package/dist/model/parse.d.ts.map +1 -0
- package/dist/model/parse.js +165 -0
- package/dist/model/parse.js.map +1 -0
- package/dist/model/patches.d.ts +100 -0
- package/dist/model/patches.d.ts.map +1 -0
- package/dist/model/patches.js +21 -0
- package/dist/model/patches.js.map +1 -0
- package/dist/model/result.d.ts +22 -0
- package/dist/model/result.d.ts.map +1 -0
- package/dist/model/result.js +10 -0
- package/dist/model/result.js.map +1 -0
- package/dist/model/source.d.ts +52 -0
- package/dist/model/source.d.ts.map +1 -0
- package/dist/model/source.js +75 -0
- package/dist/model/source.js.map +1 -0
- package/dist/protocol/index.d.ts +92 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +106 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/remote/committer.d.ts +43 -0
- package/dist/remote/committer.d.ts.map +1 -0
- package/dist/remote/committer.js +206 -0
- package/dist/remote/committer.js.map +1 -0
- package/dist/remote/index.d.ts +4 -0
- package/dist/remote/index.d.ts.map +1 -0
- package/dist/remote/index.js +4 -0
- package/dist/remote/join.d.ts +30 -0
- package/dist/remote/join.d.ts.map +1 -0
- package/dist/remote/join.js +36 -0
- package/dist/remote/join.js.map +1 -0
- package/dist/remote/memory-transport.d.ts +20 -0
- package/dist/remote/memory-transport.d.ts.map +1 -0
- package/dist/remote/memory-transport.js +48 -0
- package/dist/remote/memory-transport.js.map +1 -0
- package/dist/renderer/effects.d.ts +52 -0
- package/dist/renderer/effects.d.ts.map +1 -0
- package/dist/renderer/effects.js +111 -0
- package/dist/renderer/effects.js.map +1 -0
- package/dist/renderer/feature.d.ts +79 -0
- package/dist/renderer/feature.d.ts.map +1 -0
- package/dist/renderer/feature.js +50 -0
- package/dist/renderer/feature.js.map +1 -0
- package/dist/renderer/geometry.d.ts +59 -0
- package/dist/renderer/geometry.d.ts.map +1 -0
- package/dist/renderer/geometry.js +114 -0
- package/dist/renderer/geometry.js.map +1 -0
- package/dist/renderer/index.d.ts +7 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +7 -0
- package/dist/renderer/scene.d.ts +77 -0
- package/dist/renderer/scene.d.ts.map +1 -0
- package/dist/renderer/scene.js +125 -0
- package/dist/renderer/scene.js.map +1 -0
- package/dist/renderer/textures.d.ts +20 -0
- package/dist/renderer/textures.d.ts.map +1 -0
- package/dist/renderer/textures.js +61 -0
- package/dist/renderer/textures.js.map +1 -0
- package/dist/renderer/view.d.ts +139 -0
- package/dist/renderer/view.d.ts.map +1 -0
- package/dist/renderer/view.js +349 -0
- package/dist/renderer/view.js.map +1 -0
- package/dist/rpgjs/shared.d.ts +41 -0
- package/dist/rpgjs/shared.d.ts.map +1 -0
- package/dist/rpgjs/shared.js +77 -0
- package/dist/rpgjs/shared.js.map +1 -0
- package/dist/server/index.d.ts +64 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +205 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/storage.d.ts +16 -0
- package/dist/server/storage.d.ts.map +1 -0
- package/dist/server/storage.js +52 -0
- package/dist/server/storage.js.map +1 -0
- package/dist/skins/default.d.ts +27 -0
- package/dist/skins/default.d.ts.map +1 -0
- package/dist/skins/default.js +144 -0
- package/dist/skins/default.js.map +1 -0
- package/dist/skins/frames.d.ts +19 -0
- package/dist/skins/frames.d.ts.map +1 -0
- package/dist/skins/frames.js +49 -0
- package/dist/skins/frames.js.map +1 -0
- package/dist/skins/game.d.ts +21 -0
- package/dist/skins/game.d.ts.map +1 -0
- package/dist/skins/game.js +21 -0
- package/dist/skins/game.js.map +1 -0
- package/dist/skins/index.d.ts +5 -0
- package/dist/skins/index.d.ts.map +1 -0
- package/dist/skins/index.js +5 -0
- package/dist/skins/types.d.ts +46 -0
- package/dist/skins/types.d.ts.map +1 -0
- package/dist/skins/types.js +12 -0
- package/dist/skins/types.js.map +1 -0
- package/dist/terrain/data.d.ts +10 -0
- package/dist/terrain/data.d.ts.map +1 -0
- package/dist/terrain/data.js +83 -0
- package/dist/terrain/data.js.map +1 -0
- package/dist/terrain/define.d.ts +72 -0
- package/dist/terrain/define.d.ts.map +1 -0
- package/dist/terrain/define.js +91 -0
- package/dist/terrain/define.js.map +1 -0
- package/dist/terrain/editing.d.ts +54 -0
- package/dist/terrain/editing.d.ts.map +1 -0
- package/dist/terrain/editing.js +169 -0
- package/dist/terrain/editing.js.map +1 -0
- package/dist/terrain/index.d.ts +10 -0
- package/dist/terrain/index.d.ts.map +1 -0
- package/dist/terrain/index.js +10 -0
- package/dist/terrain/kernel.d.ts +57 -0
- package/dist/terrain/kernel.d.ts.map +1 -0
- package/dist/terrain/kernel.js +198 -0
- package/dist/terrain/kernel.js.map +1 -0
- package/dist/terrain/mask.d.ts +37 -0
- package/dist/terrain/mask.d.ts.map +1 -0
- package/dist/terrain/mask.js +244 -0
- package/dist/terrain/mask.js.map +1 -0
- package/dist/terrain/patches.d.ts +32 -0
- package/dist/terrain/patches.d.ts.map +1 -0
- package/dist/terrain/patches.js +135 -0
- package/dist/terrain/patches.js.map +1 -0
- package/dist/terrain/rle.d.ts +4 -0
- package/dist/terrain/rle.d.ts.map +1 -0
- package/dist/terrain/rle.js +68 -0
- package/dist/terrain/rle.js.map +1 -0
- package/dist/terrain/scheduler.d.ts +46 -0
- package/dist/terrain/scheduler.d.ts.map +1 -0
- package/dist/terrain/scheduler.js +148 -0
- package/dist/terrain/scheduler.js.map +1 -0
- package/dist/terrain/types.d.ts +110 -0
- package/dist/terrain/types.d.ts.map +1 -0
- package/dist/terrain/types.js +11 -0
- package/dist/terrain/types.js.map +1 -0
- package/dist/terrain-render/backend.d.ts +66 -0
- package/dist/terrain-render/backend.d.ts.map +1 -0
- package/dist/terrain-render/backend.js +96 -0
- package/dist/terrain-render/backend.js.map +1 -0
- package/dist/terrain-render/host.d.ts +21 -0
- package/dist/terrain-render/host.d.ts.map +1 -0
- package/dist/terrain-render/host.js +62 -0
- package/dist/terrain-render/host.js.map +1 -0
- package/dist/terrain-render/index.d.ts +3 -0
- package/dist/terrain-render/index.d.ts.map +1 -0
- package/dist/terrain-render/index.js +3 -0
- package/dist/terrain-render/worker.d.ts +2 -0
- package/dist/terrain-render/worker.d.ts.map +1 -0
- package/dist/terrain-render/worker.js +34 -0
- package/dist/terrain-render/worker.js.map +1 -0
- package/package.json +124 -3
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { MapPatch } from './patches';
|
|
2
|
+
import { ApplyResult, MaybePromise, Unsubscribe } from './result';
|
|
3
|
+
export interface HistoryStatus {
|
|
4
|
+
readonly canUndo: boolean;
|
|
5
|
+
readonly canRedo: boolean;
|
|
6
|
+
readonly undoDepth: number;
|
|
7
|
+
readonly redoDepth: number;
|
|
8
|
+
}
|
|
9
|
+
export interface MapHistory<R extends MaybePromise<ApplyResult> = ApplyResult> {
|
|
10
|
+
readonly canUndo: boolean;
|
|
11
|
+
readonly canRedo: boolean;
|
|
12
|
+
readonly status: HistoryStatus;
|
|
13
|
+
/** Undoes the last action. Returns `null` when there is nothing to undo. */
|
|
14
|
+
undo(): R | null;
|
|
15
|
+
/** Redoes the last undone action. Returns `null` when there is nothing to redo. */
|
|
16
|
+
redo(): R | null;
|
|
17
|
+
/**
|
|
18
|
+
* Runs `run`, and undoes everything it applied as one action. Synchronous on
|
|
19
|
+
* purpose: with a remote committer, use a batch (`model.apply([...])`) instead.
|
|
20
|
+
*/
|
|
21
|
+
group<T>(run: () => T): T;
|
|
22
|
+
clear(): void;
|
|
23
|
+
subscribe(listener: (status: HistoryStatus) => void): Unsubscribe;
|
|
24
|
+
}
|
|
25
|
+
export interface HistoryEntry {
|
|
26
|
+
readonly patches: readonly MapPatch[];
|
|
27
|
+
/** Patches that undo `patches`, already in the order they must be applied. */
|
|
28
|
+
readonly inverse: readonly MapPatch[];
|
|
29
|
+
}
|
|
30
|
+
export interface HistoryOptions<R extends MaybePromise<ApplyResult>> {
|
|
31
|
+
/** Maximum number of actions kept. `0` disables the history. */
|
|
32
|
+
readonly limit: number;
|
|
33
|
+
/** Sends patches through the committer without recording them. */
|
|
34
|
+
readonly dispatch: (patches: readonly MapPatch[]) => R;
|
|
35
|
+
readonly onListenerError?: (error: unknown) => void;
|
|
36
|
+
}
|
|
37
|
+
export interface HistoryController<R extends MaybePromise<ApplyResult>> {
|
|
38
|
+
readonly history: MapHistory<R>;
|
|
39
|
+
/** Called by the model after a local change was applied. */
|
|
40
|
+
record(entry: HistoryEntry, coalesce?: string): void;
|
|
41
|
+
}
|
|
42
|
+
export declare function createHistory<R extends MaybePromise<ApplyResult>>(options: HistoryOptions<R>): HistoryController<R>;
|
|
43
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/model/history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAqB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAEpG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,YAAY,CAAC,WAAW,CAAC,GAAG,WAAW;IAC3E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,4EAA4E;IAC5E,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC;IACjB,mFAAmF;IACnF,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,KAAK,IAAI,IAAI,CAAC;IACd,SAAS,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,WAAW,CAAC;CACnE;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,YAAY,CAAC,WAAW,CAAC;IACjE,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,KAAK,CAAC,CAAC;IACvD,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,YAAY,CAAC,WAAW,CAAC;IACpE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IAChC,4DAA4D;IAC5D,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtD;AAUD,wBAAgB,aAAa,CAAC,CAAC,SAAS,YAAY,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAmInH"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { chain, isThenable } from "./result.js";
|
|
2
|
+
//#region src/model/history.ts
|
|
3
|
+
/** Patches are replayed later, against a different revision: drop the base revision they carried. */
|
|
4
|
+
var withoutRevision = (patches) => patches.map(({ baseRevision: _baseRevision, ...rest }) => rest);
|
|
5
|
+
function createHistory(options) {
|
|
6
|
+
const { limit, dispatch } = options;
|
|
7
|
+
let undoStack = [];
|
|
8
|
+
let redoStack = [];
|
|
9
|
+
let group = null;
|
|
10
|
+
let busy = false;
|
|
11
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
12
|
+
const status = () => ({
|
|
13
|
+
canUndo: undoStack.length > 0,
|
|
14
|
+
canRedo: redoStack.length > 0,
|
|
15
|
+
undoDepth: undoStack.length,
|
|
16
|
+
redoDepth: redoStack.length
|
|
17
|
+
});
|
|
18
|
+
const notify = () => {
|
|
19
|
+
const snapshot = status();
|
|
20
|
+
for (const listener of [...listeners]) try {
|
|
21
|
+
listener(snapshot);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
(options.onListenerError ?? console.error)(error);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const push = (slot) => {
|
|
27
|
+
const top = undoStack[undoStack.length - 1];
|
|
28
|
+
if (slot.coalesce !== void 0 && top?.coalesce === slot.coalesce) undoStack[undoStack.length - 1] = {
|
|
29
|
+
coalesce: slot.coalesce,
|
|
30
|
+
patches: [...top.patches, ...slot.patches],
|
|
31
|
+
inverse: [...slot.inverse, ...top.inverse]
|
|
32
|
+
};
|
|
33
|
+
else {
|
|
34
|
+
undoStack.push(slot);
|
|
35
|
+
if (undoStack.length > limit) undoStack = undoStack.slice(undoStack.length - limit);
|
|
36
|
+
}
|
|
37
|
+
redoStack = [];
|
|
38
|
+
};
|
|
39
|
+
const replay = (from) => {
|
|
40
|
+
const stack = from === "undo" ? undoStack : redoStack;
|
|
41
|
+
const slot = stack[stack.length - 1];
|
|
42
|
+
if (!slot || busy) return null;
|
|
43
|
+
busy = true;
|
|
44
|
+
const finish = (result) => {
|
|
45
|
+
busy = false;
|
|
46
|
+
if (result.ok) {
|
|
47
|
+
if (from === "undo") {
|
|
48
|
+
undoStack = undoStack.filter((entry) => entry !== slot);
|
|
49
|
+
redoStack = [...redoStack, {
|
|
50
|
+
patches: slot.patches,
|
|
51
|
+
inverse: slot.inverse
|
|
52
|
+
}];
|
|
53
|
+
} else {
|
|
54
|
+
redoStack = redoStack.filter((entry) => entry !== slot);
|
|
55
|
+
undoStack = [...undoStack, {
|
|
56
|
+
patches: slot.patches,
|
|
57
|
+
inverse: withoutRevision(result.inverse)
|
|
58
|
+
}];
|
|
59
|
+
}
|
|
60
|
+
notify();
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
let result;
|
|
65
|
+
try {
|
|
66
|
+
result = dispatch(from === "undo" ? slot.inverse : slot.patches);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
busy = false;
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
if (isThenable(result)) return result.then(finish, (error) => {
|
|
72
|
+
busy = false;
|
|
73
|
+
throw error;
|
|
74
|
+
});
|
|
75
|
+
return chain(result, finish);
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
history: {
|
|
79
|
+
get canUndo() {
|
|
80
|
+
return undoStack.length > 0;
|
|
81
|
+
},
|
|
82
|
+
get canRedo() {
|
|
83
|
+
return redoStack.length > 0;
|
|
84
|
+
},
|
|
85
|
+
get status() {
|
|
86
|
+
return status();
|
|
87
|
+
},
|
|
88
|
+
undo: () => replay("undo"),
|
|
89
|
+
redo: () => replay("redo"),
|
|
90
|
+
group(run) {
|
|
91
|
+
if (group) return run();
|
|
92
|
+
const open = {
|
|
93
|
+
patches: [],
|
|
94
|
+
inverse: []
|
|
95
|
+
};
|
|
96
|
+
group = open;
|
|
97
|
+
try {
|
|
98
|
+
return run();
|
|
99
|
+
} finally {
|
|
100
|
+
group = null;
|
|
101
|
+
if (open.patches.length > 0 && limit > 0) {
|
|
102
|
+
push({
|
|
103
|
+
patches: open.patches,
|
|
104
|
+
inverse: open.inverse
|
|
105
|
+
});
|
|
106
|
+
notify();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
clear() {
|
|
111
|
+
if (undoStack.length === 0 && redoStack.length === 0) return;
|
|
112
|
+
undoStack = [];
|
|
113
|
+
redoStack = [];
|
|
114
|
+
notify();
|
|
115
|
+
},
|
|
116
|
+
subscribe(listener) {
|
|
117
|
+
listeners.add(listener);
|
|
118
|
+
return () => listeners.delete(listener);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
record(entry, coalesce) {
|
|
122
|
+
if (limit <= 0) return;
|
|
123
|
+
const patches = withoutRevision(entry.patches);
|
|
124
|
+
if (group) {
|
|
125
|
+
group.patches.push(...patches);
|
|
126
|
+
group.inverse.unshift(...entry.inverse);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
push({
|
|
130
|
+
patches,
|
|
131
|
+
inverse: [...entry.inverse],
|
|
132
|
+
...coalesce !== void 0 ? { coalesce } : {}
|
|
133
|
+
});
|
|
134
|
+
notify();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { createHistory };
|
|
140
|
+
|
|
141
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","names":[],"sources":["../../src/model/history.ts"],"sourcesContent":["import type { MapPatch } from \"./patches\";\nimport { chain, isThenable, type ApplyResult, type MaybePromise, type Unsubscribe } from \"./result\";\n\nexport interface HistoryStatus {\n readonly canUndo: boolean;\n readonly canRedo: boolean;\n readonly undoDepth: number;\n readonly redoDepth: number;\n}\n\nexport interface MapHistory<R extends MaybePromise<ApplyResult> = ApplyResult> {\n readonly canUndo: boolean;\n readonly canRedo: boolean;\n readonly status: HistoryStatus;\n /** Undoes the last action. Returns `null` when there is nothing to undo. */\n undo(): R | null;\n /** Redoes the last undone action. Returns `null` when there is nothing to redo. */\n redo(): R | null;\n /**\n * Runs `run`, and undoes everything it applied as one action. Synchronous on\n * purpose: with a remote committer, use a batch (`model.apply([...])`) instead.\n */\n group<T>(run: () => T): T;\n clear(): void;\n subscribe(listener: (status: HistoryStatus) => void): Unsubscribe;\n}\n\nexport interface HistoryEntry {\n readonly patches: readonly MapPatch[];\n /** Patches that undo `patches`, already in the order they must be applied. */\n readonly inverse: readonly MapPatch[];\n}\n\nexport interface HistoryOptions<R extends MaybePromise<ApplyResult>> {\n /** Maximum number of actions kept. `0` disables the history. */\n readonly limit: number;\n /** Sends patches through the committer without recording them. */\n readonly dispatch: (patches: readonly MapPatch[]) => R;\n readonly onListenerError?: (error: unknown) => void;\n}\n\nexport interface HistoryController<R extends MaybePromise<ApplyResult>> {\n readonly history: MapHistory<R>;\n /** Called by the model after a local change was applied. */\n record(entry: HistoryEntry, coalesce?: string): void;\n}\n\n/** Patches are replayed later, against a different revision: drop the base revision they carried. */\nconst withoutRevision = (patches: readonly MapPatch[]): MapPatch[] =>\n patches.map(({ baseRevision: _baseRevision, ...rest }) => rest as MapPatch);\n\ninterface Slot extends HistoryEntry {\n readonly coalesce?: string;\n}\n\nexport function createHistory<R extends MaybePromise<ApplyResult>>(options: HistoryOptions<R>): HistoryController<R> {\n const { limit, dispatch } = options;\n let undoStack: Slot[] = [];\n let redoStack: Slot[] = [];\n let group: { patches: MapPatch[]; inverse: MapPatch[] } | null = null;\n let busy = false;\n const listeners = new Set<(status: HistoryStatus) => void>();\n\n const status = (): HistoryStatus => ({\n canUndo: undoStack.length > 0,\n canRedo: redoStack.length > 0,\n undoDepth: undoStack.length,\n redoDepth: redoStack.length,\n });\n\n const notify = (): void => {\n const snapshot = status();\n for (const listener of [...listeners]) {\n try {\n listener(snapshot);\n } catch (error) {\n (options.onListenerError ?? console.error)(error);\n }\n }\n };\n\n const push = (slot: Slot): void => {\n const top = undoStack[undoStack.length - 1];\n if (slot.coalesce !== undefined && top?.coalesce === slot.coalesce) {\n // One continuous edit (a drag, a slider): a single undo step.\n undoStack[undoStack.length - 1] = {\n coalesce: slot.coalesce,\n patches: [...top.patches, ...slot.patches],\n inverse: [...slot.inverse, ...top.inverse],\n };\n } else {\n undoStack.push(slot);\n if (undoStack.length > limit) undoStack = undoStack.slice(undoStack.length - limit);\n }\n redoStack = [];\n };\n\n const replay = (from: \"undo\" | \"redo\"): R | null => {\n const stack = from === \"undo\" ? undoStack : redoStack;\n const slot = stack[stack.length - 1];\n if (!slot || busy) return null;\n\n busy = true;\n const finish = (result: ApplyResult): ApplyResult => {\n busy = false;\n if (result.ok) {\n if (from === \"undo\") {\n undoStack = undoStack.filter((entry) => entry !== slot);\n redoStack = [...redoStack, { patches: slot.patches, inverse: slot.inverse }];\n } else {\n redoStack = redoStack.filter((entry) => entry !== slot);\n undoStack = [...undoStack, { patches: slot.patches, inverse: withoutRevision(result.inverse) }];\n }\n notify();\n }\n return result;\n };\n\n let result: MaybePromise<ApplyResult>;\n try {\n result = dispatch(from === \"undo\" ? slot.inverse : slot.patches);\n } catch (error) {\n busy = false;\n throw error;\n }\n if (isThenable(result)) {\n return result.then(finish, (error) => {\n busy = false;\n throw error;\n }) as R;\n }\n return chain(result, finish) as R;\n };\n\n const history: MapHistory<R> = {\n get canUndo() {\n return undoStack.length > 0;\n },\n get canRedo() {\n return redoStack.length > 0;\n },\n get status() {\n return status();\n },\n undo: () => replay(\"undo\"),\n redo: () => replay(\"redo\"),\n group<T>(run: () => T): T {\n if (group) return run();\n const open = { patches: [] as MapPatch[], inverse: [] as MapPatch[] };\n group = open;\n try {\n return run();\n } finally {\n group = null;\n if (open.patches.length > 0 && limit > 0) {\n push({ patches: open.patches, inverse: open.inverse });\n notify();\n }\n }\n },\n clear() {\n if (undoStack.length === 0 && redoStack.length === 0) return;\n undoStack = [];\n redoStack = [];\n notify();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n\n return {\n history,\n record(entry, coalesce) {\n if (limit <= 0) return;\n const patches = withoutRevision(entry.patches);\n if (group) {\n group.patches.push(...patches);\n group.inverse.unshift(...entry.inverse);\n return;\n }\n push({ patches, inverse: [...entry.inverse], ...(coalesce !== undefined ? { coalesce } : {}) });\n notify();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './json';
|
|
2
|
+
export * from './definition';
|
|
3
|
+
export * from './errors';
|
|
4
|
+
export * from './parse';
|
|
5
|
+
export * from './assets';
|
|
6
|
+
export * from './define-map';
|
|
7
|
+
export * from './patches';
|
|
8
|
+
export * from './result';
|
|
9
|
+
export * from './history';
|
|
10
|
+
export * from './committer';
|
|
11
|
+
export * from './model';
|
|
12
|
+
export * from './source';
|
|
13
|
+
export { coreHandlers } from './core-patches';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { cloneJson, isFiniteNumberValue, isJsonObject, isJsonValue, isPlainObject } from "./json.js";
|
|
2
|
+
import { MAP_DEFINITION_VERSION } from "./definition.js";
|
|
3
|
+
import { MapDefinitionError } from "./errors.js";
|
|
4
|
+
import { checkPlaced, defaultMapMigrations, isScale, parseMapDefinition } from "./parse.js";
|
|
5
|
+
import { defineAssets } from "./assets.js";
|
|
6
|
+
import { defineMap, el, event } from "./define-map.js";
|
|
7
|
+
import { definePatchHandlers, patchFail, patchOk } from "./patches.js";
|
|
8
|
+
import { chain, isThenable } from "./result.js";
|
|
9
|
+
import { createHistory } from "./history.js";
|
|
10
|
+
import { localCommitter } from "./committer.js";
|
|
11
|
+
import { coreHandlers } from "./core-patches.js";
|
|
12
|
+
import { DEFAULT_HISTORY_LIMIT, createMapModel } from "./model.js";
|
|
13
|
+
import { MapSourceError, codeMapSource, httpMapSource, jsonMapSource, loadMapRegion, regionOf } from "./source.js";
|
|
14
|
+
export { DEFAULT_HISTORY_LIMIT, MAP_DEFINITION_VERSION, MapDefinitionError, MapSourceError, chain, checkPlaced, cloneJson, codeMapSource, coreHandlers, createHistory, createMapModel, defaultMapMigrations, defineAssets, defineMap, definePatchHandlers, el, event, httpMapSource, isFiniteNumberValue, isJsonObject, isJsonValue, isPlainObject, isScale, isThenable, jsonMapSource, loadMapRegion, localCommitter, parseMapDefinition, patchFail, patchOk, regionOf };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
2
|
+
export type JsonValue = JsonPrimitive | readonly JsonValue[] | {
|
|
3
|
+
readonly [key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
export type JsonObject = {
|
|
6
|
+
readonly [key: string]: JsonValue;
|
|
7
|
+
};
|
|
8
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
9
|
+
/** True when `value` survives `JSON.stringify` / `JSON.parse` unchanged. */
|
|
10
|
+
export declare function isJsonValue(value: unknown): value is JsonValue;
|
|
11
|
+
export declare function isJsonObject(value: unknown): value is JsonObject;
|
|
12
|
+
/** Deep copy of a JSON value, so that callers never share structure with the model. */
|
|
13
|
+
export declare function cloneJson<T extends JsonValue>(value: T): T;
|
|
14
|
+
export declare function isFiniteNumberValue(value: unknown): value is number;
|
|
15
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/model/json.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAC7D,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,SAAS,SAAS,EAAE,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AACrG,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/D,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAe9D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE;AAED,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAW1D;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEnE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/model/json.ts
|
|
2
|
+
function isPlainObject(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null) return false;
|
|
4
|
+
const proto = Object.getPrototypeOf(value);
|
|
5
|
+
return proto === Object.prototype || proto === null;
|
|
6
|
+
}
|
|
7
|
+
/** True when `value` survives `JSON.stringify` / `JSON.parse` unchanged. */
|
|
8
|
+
function isJsonValue(value) {
|
|
9
|
+
if (value === null) return true;
|
|
10
|
+
switch (typeof value) {
|
|
11
|
+
case "string":
|
|
12
|
+
case "boolean": return true;
|
|
13
|
+
case "number": return Number.isFinite(value);
|
|
14
|
+
case "object":
|
|
15
|
+
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
16
|
+
return isPlainObject(value) && Object.values(value).every((item) => item === void 0 || isJsonValue(item));
|
|
17
|
+
default: return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function isJsonObject(value) {
|
|
21
|
+
return isPlainObject(value) && isJsonValue(value);
|
|
22
|
+
}
|
|
23
|
+
/** Deep copy of a JSON value, so that callers never share structure with the model. */
|
|
24
|
+
function cloneJson(value) {
|
|
25
|
+
if (Array.isArray(value)) return value.map((item) => cloneJson(item));
|
|
26
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => [key, cloneJson(item)]));
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function isFiniteNumberValue(value) {
|
|
30
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { cloneJson, isFiniteNumberValue, isJsonObject, isJsonValue, isPlainObject };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","names":[],"sources":["../../src/model/json.ts"],"sourcesContent":["export type JsonPrimitive = string | number | boolean | null;\nexport type JsonValue = JsonPrimitive | readonly JsonValue[] | { readonly [key: string]: JsonValue };\nexport type JsonObject = { readonly [key: string]: JsonValue };\n\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/** True when `value` survives `JSON.stringify` / `JSON.parse` unchanged. */\nexport function isJsonValue(value: unknown): value is JsonValue {\n if (value === null) return true;\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return true;\n case \"number\":\n return Number.isFinite(value);\n case \"object\":\n if (Array.isArray(value)) return value.every(isJsonValue);\n // A property set to `undefined` is dropped by `JSON.stringify`, so it counts as absent.\n return isPlainObject(value) && Object.values(value).every((item) => item === undefined || isJsonValue(item));\n default:\n return false;\n }\n}\n\nexport function isJsonObject(value: unknown): value is JsonObject {\n return isPlainObject(value) && isJsonValue(value);\n}\n\n/** Deep copy of a JSON value, so that callers never share structure with the model. */\nexport function cloneJson<T extends JsonValue>(value: T): T {\n if (Array.isArray(value)) return value.map((item) => cloneJson(item)) as unknown as T;\n if (value !== null && typeof value === \"object\") {\n // `fromEntries` defines own properties, so a `__proto__` key can never reach the prototype.\n return Object.fromEntries(\n Object.entries(value)\n .filter(([, item]) => item !== undefined)\n .map(([key, item]) => [key, cloneJson(item as JsonValue)]),\n ) as unknown as T;\n }\n return value;\n}\n\nexport function isFiniteNumberValue(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n"],"mappings":";AAIA,SAAgB,cAAc,OAAkD;CAC9E,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,QAAQ,OAAO,eAAe,KAAK;CACzC,OAAO,UAAU,OAAO,aAAa,UAAU;AACjD;;AAGA,SAAgB,YAAY,OAAoC;CAC9D,IAAI,UAAU,MAAM,OAAO;CAC3B,QAAQ,OAAO,OAAf;EACE,KAAK;EACL,KAAK,WACH,OAAO;EACT,KAAK,UACH,OAAO,OAAO,SAAS,KAAK;EAC9B,KAAK;GACH,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,MAAM,WAAW;GAExD,OAAO,cAAc,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC,CAAC,OAAO,SAAS,SAAS,KAAA,KAAa,YAAY,IAAI,CAAC;EAC7G,SACE,OAAO;CACX;AACF;AAEA,SAAgB,aAAa,OAAqC;CAChE,OAAO,cAAc,KAAK,KAAK,YAAY,KAAK;AAClD;;AAGA,SAAgB,UAA+B,OAAa;CAC1D,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,SAAS,UAAU,IAAI,CAAC;CACpE,IAAI,UAAU,QAAQ,OAAO,UAAU,UAErC,OAAO,OAAO,YACZ,OAAO,QAAQ,KAAK,CAAC,CAClB,QAAQ,GAAG,UAAU,SAAS,KAAA,CAAS,CAAC,CACxC,KAAK,CAAC,KAAK,UAAU,CAAC,KAAK,UAAU,IAAiB,CAAC,CAAC,CAC7D;CAEF,OAAO;AACT;AAEA,SAAgB,oBAAoB,OAAiC;CACnE,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK;AAC3D"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MapCommitter } from './committer';
|
|
2
|
+
import { MapDefinition, MapElement, MapEvent, MapState } from './definition';
|
|
3
|
+
import { MapHistory } from './history';
|
|
4
|
+
import { MapPatch, PatchHandlers } from './patches';
|
|
5
|
+
import { ApplyResult, MaybePromise, Unsubscribe } from './result';
|
|
6
|
+
export interface MapChange {
|
|
7
|
+
readonly revision: number;
|
|
8
|
+
readonly patches: readonly MapPatch[];
|
|
9
|
+
readonly inverse: readonly MapPatch[];
|
|
10
|
+
readonly origin: "local" | "remote";
|
|
11
|
+
/** State after the change. */
|
|
12
|
+
readonly state: MapState;
|
|
13
|
+
/**
|
|
14
|
+
* The whole state was replaced (a snapshot from the authority): `patches` is empty
|
|
15
|
+
* and whatever was derived from the previous state must be rebuilt.
|
|
16
|
+
*/
|
|
17
|
+
readonly reset?: true;
|
|
18
|
+
}
|
|
19
|
+
export interface ApplyOptions {
|
|
20
|
+
/** Consecutive `apply` calls with the same key become one undo step (a drag, a slider). */
|
|
21
|
+
readonly coalesce?: string;
|
|
22
|
+
/** The intents the patches come from. A committer that talks to an authority sends these. */
|
|
23
|
+
readonly intents?: readonly unknown[];
|
|
24
|
+
}
|
|
25
|
+
export interface MapModelOptions {
|
|
26
|
+
readonly committer?: MapCommitter<ApplyResult>;
|
|
27
|
+
/** Handlers for the patch types features add. They cannot replace a built-in one. */
|
|
28
|
+
readonly patchHandlers?: readonly PatchHandlers[];
|
|
29
|
+
/** Revision the map starts at, e.g. the one of a server snapshot. Defaults to 0. */
|
|
30
|
+
readonly revision?: number;
|
|
31
|
+
/** Maximum number of undoable actions, or `false` to disable the history. Defaults to 50. */
|
|
32
|
+
readonly history?: {
|
|
33
|
+
readonly limit?: number;
|
|
34
|
+
} | false;
|
|
35
|
+
/**
|
|
36
|
+
* What to do with a patch computed against another revision than the current one:
|
|
37
|
+
* `"reject"` (default) or `"rebase"` (apply it anyway, if it is still valid).
|
|
38
|
+
*/
|
|
39
|
+
readonly onStale?: "reject" | "rebase";
|
|
40
|
+
readonly onListenerError?: (error: unknown) => void;
|
|
41
|
+
}
|
|
42
|
+
export interface MapModel<R extends MaybePromise<ApplyResult> = ApplyResult> {
|
|
43
|
+
/** Current state. Immutable: every change produces a new object. */
|
|
44
|
+
readonly state: MapState;
|
|
45
|
+
readonly revision: number;
|
|
46
|
+
readonly history: MapHistory<R>;
|
|
47
|
+
/**
|
|
48
|
+
* Submits an intention. It goes through the committer, which decides where it
|
|
49
|
+
* becomes a fact. A batch (an array) is atomic: all or nothing, one revision.
|
|
50
|
+
*/
|
|
51
|
+
apply(input: MapPatch | readonly MapPatch[], options?: ApplyOptions): R;
|
|
52
|
+
/** Deep copy of the current state, safe to serialize or mutate. */
|
|
53
|
+
getDefinition(): MapDefinition;
|
|
54
|
+
getElement(id: string): MapElement | undefined;
|
|
55
|
+
getEvent(id: string): MapEvent | undefined;
|
|
56
|
+
/** Framework-neutral: plugs into Angular signals, Vue, React or CanvasEngine. */
|
|
57
|
+
subscribe(listener: (change: MapChange) => void): Unsubscribe;
|
|
58
|
+
}
|
|
59
|
+
export declare const DEFAULT_HISTORY_LIMIT = 50;
|
|
60
|
+
/**
|
|
61
|
+
* Creates the model of a map: the single source of truth, changed only by patches.
|
|
62
|
+
* The definition is validated and copied, so the model never shares structure with the caller.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createMapModel(definition: MapDefinition, options?: MapModelOptions): MapModel<ApplyResult>;
|
|
65
|
+
export declare function createMapModel<R extends MaybePromise<ApplyResult>>(definition: MapDefinition, options: Omit<MapModelOptions, "committer"> & {
|
|
66
|
+
readonly committer: MapCommitter<R>;
|
|
67
|
+
}): MapModel<R>;
|
|
68
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/model/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { cloneJson } from "./json.js";
|
|
2
|
+
import { parseMapDefinition } from "./parse.js";
|
|
3
|
+
import { createHistory } from "./history.js";
|
|
4
|
+
import { localCommitter } from "./committer.js";
|
|
5
|
+
import { coreHandlers } from "./core-patches.js";
|
|
6
|
+
//#region src/model/model.ts
|
|
7
|
+
var DEFAULT_HISTORY_LIMIT = 50;
|
|
8
|
+
var reject = (rejection) => ({
|
|
9
|
+
ok: false,
|
|
10
|
+
rejection
|
|
11
|
+
});
|
|
12
|
+
function createMapModel(definition, options = {}) {
|
|
13
|
+
const committer = options.committer ?? localCommitter();
|
|
14
|
+
const onStale = options.onStale ?? "reject";
|
|
15
|
+
const reportListenerError = options.onListenerError ?? ((error) => console.error(error));
|
|
16
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
17
|
+
const register = (set, locked) => {
|
|
18
|
+
for (const [type, handler] of Object.entries(set)) {
|
|
19
|
+
if (!handler) continue;
|
|
20
|
+
if (locked && handlers.has(type)) throw new Error(`Patch type "${type}" is already handled`);
|
|
21
|
+
handlers.set(type, handler);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
register(coreHandlers, false);
|
|
25
|
+
for (const set of options.patchHandlers ?? []) register(set, true);
|
|
26
|
+
let state = parseMapDefinition(definition);
|
|
27
|
+
let revision = options.revision ?? 0;
|
|
28
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
29
|
+
const controller = createHistory({
|
|
30
|
+
limit: options.history === false ? 0 : options.history?.limit ?? 50,
|
|
31
|
+
dispatch: (patches) => committer.commit({
|
|
32
|
+
patches,
|
|
33
|
+
record: false
|
|
34
|
+
}),
|
|
35
|
+
onListenerError: reportListenerError
|
|
36
|
+
});
|
|
37
|
+
function integrate(patches, integrateOptions = {}) {
|
|
38
|
+
const origin = integrateOptions.origin ?? "local";
|
|
39
|
+
if (patches.length === 0) return reject({
|
|
40
|
+
code: "invalid-patch",
|
|
41
|
+
message: "A batch must contain at least one patch"
|
|
42
|
+
});
|
|
43
|
+
const malformed = patches.findIndex((patch) => typeof patch !== "object" || patch === null);
|
|
44
|
+
if (malformed >= 0) return reject({
|
|
45
|
+
code: "invalid-patch",
|
|
46
|
+
message: "A patch must be an object",
|
|
47
|
+
patchIndex: malformed
|
|
48
|
+
});
|
|
49
|
+
if (onStale === "reject") {
|
|
50
|
+
const index = patches.findIndex((patch) => patch.baseRevision !== void 0 && patch.baseRevision !== revision);
|
|
51
|
+
if (index >= 0) return reject({
|
|
52
|
+
code: "stale-revision",
|
|
53
|
+
message: `Patch was computed against revision ${patches[index].baseRevision}, current revision is ${revision}`,
|
|
54
|
+
patchIndex: index
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (integrateOptions.revision !== void 0 && integrateOptions.revision !== revision + 1) return reject({
|
|
58
|
+
code: "revision-gap",
|
|
59
|
+
message: `Expected revision ${revision + 1}, got ${integrateOptions.revision}`
|
|
60
|
+
});
|
|
61
|
+
let draft = state;
|
|
62
|
+
let inverse = [];
|
|
63
|
+
for (const [index, patch] of patches.entries()) {
|
|
64
|
+
const handler = typeof patch?.type === "string" ? handlers.get(patch.type) : void 0;
|
|
65
|
+
if (!handler) return reject({
|
|
66
|
+
code: "unknown-patch",
|
|
67
|
+
message: `No handler for patch type "${String(patch?.type)}"`,
|
|
68
|
+
patchIndex: index
|
|
69
|
+
});
|
|
70
|
+
let outcome;
|
|
71
|
+
try {
|
|
72
|
+
outcome = handler.apply(draft, patch);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return reject({
|
|
75
|
+
code: "invalid-patch",
|
|
76
|
+
message: error instanceof Error ? error.message : String(error),
|
|
77
|
+
patchIndex: index
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (!outcome.ok) return reject({
|
|
81
|
+
...outcome.rejection,
|
|
82
|
+
patchIndex: index
|
|
83
|
+
});
|
|
84
|
+
draft = outcome.state;
|
|
85
|
+
inverse = [...outcome.inverse, ...inverse];
|
|
86
|
+
}
|
|
87
|
+
state = draft;
|
|
88
|
+
revision = integrateOptions.revision ?? revision + 1;
|
|
89
|
+
const result = {
|
|
90
|
+
ok: true,
|
|
91
|
+
revision,
|
|
92
|
+
patches,
|
|
93
|
+
inverse
|
|
94
|
+
};
|
|
95
|
+
if (origin === "local" && integrateOptions.record !== false) controller.record({
|
|
96
|
+
patches,
|
|
97
|
+
inverse
|
|
98
|
+
}, integrateOptions.coalesce);
|
|
99
|
+
const change = {
|
|
100
|
+
revision,
|
|
101
|
+
patches,
|
|
102
|
+
inverse,
|
|
103
|
+
origin,
|
|
104
|
+
state
|
|
105
|
+
};
|
|
106
|
+
for (const listener of [...listeners]) try {
|
|
107
|
+
listener(change);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
reportListenerError(error);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
const host = {
|
|
114
|
+
get state() {
|
|
115
|
+
return state;
|
|
116
|
+
},
|
|
117
|
+
get revision() {
|
|
118
|
+
return revision;
|
|
119
|
+
},
|
|
120
|
+
integrate,
|
|
121
|
+
reset(next, nextRevision) {
|
|
122
|
+
state = parseMapDefinition(next);
|
|
123
|
+
revision = nextRevision;
|
|
124
|
+
controller.history.clear();
|
|
125
|
+
const change = {
|
|
126
|
+
revision,
|
|
127
|
+
patches: [],
|
|
128
|
+
inverse: [],
|
|
129
|
+
origin: "remote",
|
|
130
|
+
state,
|
|
131
|
+
reset: true
|
|
132
|
+
};
|
|
133
|
+
for (const listener of [...listeners]) try {
|
|
134
|
+
listener(change);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
reportListenerError(error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
committer.attach(host);
|
|
141
|
+
return {
|
|
142
|
+
get state() {
|
|
143
|
+
return state;
|
|
144
|
+
},
|
|
145
|
+
get revision() {
|
|
146
|
+
return revision;
|
|
147
|
+
},
|
|
148
|
+
history: controller.history,
|
|
149
|
+
apply(input, applyOptions) {
|
|
150
|
+
const patches = Array.isArray(input) ? input : [input];
|
|
151
|
+
return committer.commit({
|
|
152
|
+
patches,
|
|
153
|
+
record: true,
|
|
154
|
+
...applyOptions?.coalesce !== void 0 ? { coalesce: applyOptions.coalesce } : {},
|
|
155
|
+
...applyOptions?.intents !== void 0 ? { intents: applyOptions.intents } : {}
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
getDefinition: () => cloneJson(state),
|
|
159
|
+
getElement: (id) => state.elements.find((element) => element.id === id),
|
|
160
|
+
getEvent: (id) => state.events.find((entry) => entry.id === id),
|
|
161
|
+
subscribe(listener) {
|
|
162
|
+
listeners.add(listener);
|
|
163
|
+
return () => listeners.delete(listener);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
export { DEFAULT_HISTORY_LIMIT, createMapModel };
|
|
169
|
+
|
|
170
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","names":[],"sources":["../../src/model/model.ts"],"sourcesContent":["import { localCommitter, type CommitHost, type IntegrateOptions, type MapCommitter } from \"./committer\";\nimport { coreHandlers } from \"./core-patches\";\nimport type { MapDefinition, MapElement, MapEvent, MapState } from \"./definition\";\nimport type { MapRejection } from \"./errors\";\nimport { createHistory, type MapHistory } from \"./history\";\nimport { cloneJson, type JsonValue } from \"./json\";\nimport { parseMapDefinition } from \"./parse\";\nimport type { MapPatch, PatchHandler, PatchHandlers, PatchType } from \"./patches\";\nimport type { ApplyFailure, ApplyResult, MaybePromise, Unsubscribe } from \"./result\";\n\nexport interface MapChange {\n readonly revision: number;\n readonly patches: readonly MapPatch[];\n readonly inverse: readonly MapPatch[];\n readonly origin: \"local\" | \"remote\";\n /** State after the change. */\n readonly state: MapState;\n /**\n * The whole state was replaced (a snapshot from the authority): `patches` is empty\n * and whatever was derived from the previous state must be rebuilt.\n */\n readonly reset?: true;\n}\n\nexport interface ApplyOptions {\n /** Consecutive `apply` calls with the same key become one undo step (a drag, a slider). */\n readonly coalesce?: string;\n /** The intents the patches come from. A committer that talks to an authority sends these. */\n readonly intents?: readonly unknown[];\n}\n\nexport interface MapModelOptions {\n readonly committer?: MapCommitter<ApplyResult>;\n /** Handlers for the patch types features add. They cannot replace a built-in one. */\n readonly patchHandlers?: readonly PatchHandlers[];\n /** Revision the map starts at, e.g. the one of a server snapshot. Defaults to 0. */\n readonly revision?: number;\n /** Maximum number of undoable actions, or `false` to disable the history. Defaults to 50. */\n readonly history?: { readonly limit?: number } | false;\n /**\n * What to do with a patch computed against another revision than the current one:\n * `\"reject\"` (default) or `\"rebase\"` (apply it anyway, if it is still valid).\n */\n readonly onStale?: \"reject\" | \"rebase\";\n readonly onListenerError?: (error: unknown) => void;\n}\n\nexport interface MapModel<R extends MaybePromise<ApplyResult> = ApplyResult> {\n /** Current state. Immutable: every change produces a new object. */\n readonly state: MapState;\n readonly revision: number;\n readonly history: MapHistory<R>;\n /**\n * Submits an intention. It goes through the committer, which decides where it\n * becomes a fact. A batch (an array) is atomic: all or nothing, one revision.\n */\n apply(input: MapPatch | readonly MapPatch[], options?: ApplyOptions): R;\n /** Deep copy of the current state, safe to serialize or mutate. */\n getDefinition(): MapDefinition;\n getElement(id: string): MapElement | undefined;\n getEvent(id: string): MapEvent | undefined;\n /** Framework-neutral: plugs into Angular signals, Vue, React or CanvasEngine. */\n subscribe(listener: (change: MapChange) => void): Unsubscribe;\n}\n\nexport const DEFAULT_HISTORY_LIMIT = 50;\n\nconst reject = (rejection: MapRejection): ApplyFailure => ({ ok: false, rejection });\n\n/**\n * Creates the model of a map: the single source of truth, changed only by patches.\n * The definition is validated and copied, so the model never shares structure with the caller.\n */\nexport function createMapModel(definition: MapDefinition, options?: MapModelOptions): MapModel<ApplyResult>;\nexport function createMapModel<R extends MaybePromise<ApplyResult>>(\n definition: MapDefinition,\n options: Omit<MapModelOptions, \"committer\"> & { readonly committer: MapCommitter<R> },\n): MapModel<R>;\nexport function createMapModel(\n definition: MapDefinition,\n options: Omit<MapModelOptions, \"committer\"> & { readonly committer?: MapCommitter<MaybePromise<ApplyResult>> } = {},\n): MapModel<MaybePromise<ApplyResult>> {\n type R = MaybePromise<ApplyResult>;\n const committer: MapCommitter<R> = options.committer ?? localCommitter();\n const onStale = options.onStale ?? \"reject\";\n const reportListenerError = options.onListenerError ?? ((error: unknown) => console.error(error));\n\n const handlers = new Map<string, PatchHandler<PatchType>>();\n const register = (set: PatchHandlers, locked: boolean): void => {\n for (const [type, handler] of Object.entries(set)) {\n if (!handler) continue;\n if (locked && handlers.has(type)) throw new Error(`Patch type \"${type}\" is already handled`);\n handlers.set(type, handler as PatchHandler<PatchType>);\n }\n };\n register(coreHandlers, false);\n for (const set of options.patchHandlers ?? []) register(set, true);\n\n let state: MapState = parseMapDefinition(definition);\n let revision = options.revision ?? 0;\n const listeners = new Set<(change: MapChange) => void>();\n\n const limit = options.history === false ? 0 : (options.history?.limit ?? DEFAULT_HISTORY_LIMIT);\n const controller = createHistory<R>({\n limit,\n dispatch: (patches) => committer.commit({ patches, record: false }),\n onListenerError: reportListenerError,\n });\n\n function integrate(patches: readonly MapPatch[], integrateOptions: IntegrateOptions = {}): ApplyResult {\n const origin = integrateOptions.origin ?? \"local\";\n if (patches.length === 0) {\n return reject({ code: \"invalid-patch\", message: \"A batch must contain at least one patch\" });\n }\n\n // Patches may come from the network: never trust their shape.\n const malformed = patches.findIndex((patch) => typeof patch !== \"object\" || patch === null);\n if (malformed >= 0) {\n return reject({ code: \"invalid-patch\", message: \"A patch must be an object\", patchIndex: malformed });\n }\n\n if (onStale === \"reject\") {\n const index = patches.findIndex((patch) => patch.baseRevision !== undefined && patch.baseRevision !== revision);\n if (index >= 0) {\n return reject({\n code: \"stale-revision\",\n message: `Patch was computed against revision ${patches[index]!.baseRevision}, current revision is ${revision}`,\n patchIndex: index,\n });\n }\n }\n if (integrateOptions.revision !== undefined && integrateOptions.revision !== revision + 1) {\n return reject({\n code: \"revision-gap\",\n message: `Expected revision ${revision + 1}, got ${integrateOptions.revision}`,\n });\n }\n\n // Handlers are pure, so working on a draft makes the batch atomic.\n let draft = state;\n let inverse: readonly MapPatch[] = [];\n for (const [index, patch] of patches.entries()) {\n const handler = typeof patch?.type === \"string\" ? handlers.get(patch.type) : undefined;\n if (!handler) {\n return reject({\n code: \"unknown-patch\",\n message: `No handler for patch type \"${String(patch?.type)}\"`,\n patchIndex: index,\n });\n }\n let outcome;\n try {\n outcome = handler.apply(draft, patch as never);\n } catch (error) {\n return reject({\n code: \"invalid-patch\",\n message: error instanceof Error ? error.message : String(error),\n patchIndex: index,\n });\n }\n if (!outcome.ok) return reject({ ...outcome.rejection, patchIndex: index });\n draft = outcome.state;\n // The inverse of a later patch must run before the inverse of an earlier one.\n inverse = [...outcome.inverse, ...inverse];\n }\n\n state = draft;\n revision = integrateOptions.revision ?? revision + 1;\n const result: ApplyResult = { ok: true, revision, patches, inverse };\n\n if (origin === \"local\" && integrateOptions.record !== false) {\n controller.record({ patches, inverse }, integrateOptions.coalesce);\n }\n const change: MapChange = { revision, patches, inverse, origin, state };\n for (const listener of [...listeners]) {\n try {\n listener(change);\n } catch (error) {\n reportListenerError(error);\n }\n }\n return result;\n }\n\n const host: CommitHost = {\n get state() {\n return state;\n },\n get revision() {\n return revision;\n },\n integrate,\n reset(next, nextRevision) {\n state = parseMapDefinition(next);\n revision = nextRevision;\n controller.history.clear();\n const change: MapChange = { revision, patches: [], inverse: [], origin: \"remote\", state, reset: true };\n for (const listener of [...listeners]) {\n try {\n listener(change);\n } catch (error) {\n reportListenerError(error);\n }\n }\n },\n };\n committer.attach(host);\n\n return {\n get state() {\n return state;\n },\n get revision() {\n return revision;\n },\n history: controller.history,\n apply(input, applyOptions) {\n const patches = Array.isArray(input) ? (input as readonly MapPatch[]) : [input as MapPatch];\n return committer.commit({\n patches,\n record: true,\n ...(applyOptions?.coalesce !== undefined ? { coalesce: applyOptions.coalesce } : {}),\n ...(applyOptions?.intents !== undefined ? { intents: applyOptions.intents } : {}),\n });\n },\n getDefinition: () => cloneJson(state as unknown as JsonValue) as unknown as MapDefinition,\n getElement: (id) => state.elements.find((element) => element.id === id),\n getEvent: (id) => state.events.find((entry) => entry.id === id),\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MapDefinition } from './definition';
|
|
2
|
+
import { MapIssue } from './errors';
|
|
3
|
+
/** Upgrades a raw definition from version `n` to version `n + 1`. */
|
|
4
|
+
export type MapMigration = (raw: Record<string, unknown>) => Record<string, unknown>;
|
|
5
|
+
/** Migrations keyed by the version they upgrade *from*. Empty until version 2 exists. */
|
|
6
|
+
export declare const defaultMapMigrations: Readonly<Record<number, MapMigration>>;
|
|
7
|
+
export interface ParseMapOptions {
|
|
8
|
+
readonly migrations?: Readonly<Record<number, MapMigration>>;
|
|
9
|
+
readonly currentVersion?: number;
|
|
10
|
+
}
|
|
11
|
+
/** A positive number, or `[x, y]` of positive numbers. */
|
|
12
|
+
export declare const isScale: (value: unknown) => boolean;
|
|
13
|
+
/** Problems found in one element or event, with paths relative to `path`. */
|
|
14
|
+
export declare function checkPlaced(value: unknown, path: string, kind: "element" | "event"): MapIssue[];
|
|
15
|
+
/**
|
|
16
|
+
* Validates, migrates and copies a raw definition (for instance parsed JSON).
|
|
17
|
+
*
|
|
18
|
+
* - The result shares no structure with the input.
|
|
19
|
+
* - Fields unknown to this version are preserved, so an older tool never destroys
|
|
20
|
+
* data written by a newer one.
|
|
21
|
+
* - Throws {@link MapDefinitionError} listing every problem found.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseMapDefinition(raw: unknown, options?: ParseMapOptions): MapDefinition;
|
|
24
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/model/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAsB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAG7D,qEAAqE;AACrE,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAErF,yFAAyF;AACzF,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAM,CAAC;AAE/E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAID,0DAA0D;AAC1D,eAAO,MAAM,OAAO,GAAI,OAAO,OAAO,KAAG,OACqD,CAAC;AAG/F,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,EAAE,CA6B/F;AAyDD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,eAAoB,GAAG,aAAa,CAqC7F"}
|