@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 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","names":[],"sources":["../../src/model/assets.ts"],"sourcesContent":["/** What the renderer needs to know about an asset, independently of how it is stored. */\nexport interface AssetDescriptor {\n /** Where the image lives. How it is fetched and decoded is up to the renderer. */\n readonly src: string;\n /**\n * `[x, y, width, height]` of the part of the image to show, for assets cut out\n * of a larger image (a tileset). Defaults to the whole image.\n */\n readonly frame?: readonly [x: number, y: number, width: number, height: number];\n /**\n * Size in pixels at which the asset is drawn. Defaults to the size of `frame`.\n * Without a `frame`, it is also taken as the size of the image. An asset that\n * declares neither is measured once its image has loaded.\n */\n readonly size?: readonly [width: number, height: number];\n /**\n * `[x, y, width, height]` of the collision box, in pixels of the *source* image\n * (`frame`), relative to its top-left corner. It is scaled with the asset.\n */\n readonly hitbox?: readonly [x: number, y: number, width: number, height: number];\n}\n\n/**\n * Turns an asset reference (`\"tree-oak\"`) into a descriptor. Studio, a local\n * folder or a game each provide their own.\n */\nexport interface AssetResolver<K extends string = string> {\n /** Every reference this resolver knows. */\n readonly kinds: readonly K[];\n has(kind: string): kind is K;\n resolve(kind: string): AssetDescriptor | undefined;\n}\n\n/** Union of the references a resolver knows. */\nexport type AssetKind<A> = A extends AssetResolver<infer K> ? K : never;\n\n/** Declares assets once, so that references are checked by the compiler. */\nexport function defineAssets<const T extends Record<string, AssetDescriptor>>(\n assets: T,\n): AssetResolver<Extract<keyof T, string>> {\n const table = new Map<string, AssetDescriptor>(Object.entries(assets));\n const kinds = [...table.keys()] as Extract<keyof T, string>[];\n return {\n kinds,\n has(kind): kind is Extract<keyof T, string> {\n return table.has(kind);\n },\n resolve(kind) {\n return table.get(kind);\n },\n };\n}\n"],"mappings":";;AAqCA,SAAgB,aACd,QACyC;CACzC,MAAM,QAAQ,IAAI,IAA6B,OAAO,QAAQ,MAAM,CAAC;CAErE,OAAO;EACL,OAAA,CAFa,GAAG,MAAM,KAAK,CAE3B;EACA,IAAI,MAAwC;GAC1C,OAAO,MAAM,IAAI,IAAI;EACvB;EACA,QAAQ,MAAM;GACZ,OAAO,MAAM,IAAI,IAAI;EACvB;CACF;AACF"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MapDefinition, MapState } from './definition';
|
|
2
|
+
import { MapPatch } from './patches';
|
|
3
|
+
import { ApplyResult, MaybePromise } from './result';
|
|
4
|
+
export type ChangeOrigin = "local" | "remote";
|
|
5
|
+
export interface IntegrateOptions {
|
|
6
|
+
/**
|
|
7
|
+
* `local` changes are recorded in the history. `remote` ones (broadcast by the
|
|
8
|
+
* server, made by someone else) are not. Defaults to `local`.
|
|
9
|
+
*/
|
|
10
|
+
readonly origin?: ChangeOrigin;
|
|
11
|
+
/** Revision assigned by an authority. It must be exactly the next one. */
|
|
12
|
+
readonly revision?: number;
|
|
13
|
+
/** Set to `false` for undo / redo, which manage the history themselves. */
|
|
14
|
+
readonly record?: boolean;
|
|
15
|
+
/** Consecutive changes with the same key become one undo step. */
|
|
16
|
+
readonly coalesce?: string;
|
|
17
|
+
}
|
|
18
|
+
/** What a committer can do to the model it is attached to. */
|
|
19
|
+
export interface CommitHost {
|
|
20
|
+
readonly state: MapState;
|
|
21
|
+
readonly revision: number;
|
|
22
|
+
/** Validates and applies patches atomically, then notifies. This is the only way to change the model. */
|
|
23
|
+
integrate(patches: readonly MapPatch[], options?: IntegrateOptions): ApplyResult;
|
|
24
|
+
/**
|
|
25
|
+
* Replaces the whole state, e.g. with a snapshot from the authority after a long
|
|
26
|
+
* disconnection. The history is cleared, and listeners get a change with `reset: true`.
|
|
27
|
+
*/
|
|
28
|
+
reset(definition: MapDefinition, revision: number): void;
|
|
29
|
+
}
|
|
30
|
+
export interface CommitRequest {
|
|
31
|
+
readonly patches: readonly MapPatch[];
|
|
32
|
+
readonly record: boolean;
|
|
33
|
+
readonly coalesce?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The intents the patches were resolved from, when there were some. A committer that
|
|
36
|
+
* talks to an authority sends these instead of the patches: the client asks, the
|
|
37
|
+
* authority decides.
|
|
38
|
+
*/
|
|
39
|
+
readonly intents?: readonly unknown[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The only part that knows *where* an intention becomes a fact. The renderer and
|
|
43
|
+
* the tools never know which committer is in use.
|
|
44
|
+
*
|
|
45
|
+
* `R` is the type `model.apply` returns: `ApplyResult` for a synchronous committer
|
|
46
|
+
* (`localCommitter`), `Promise<ApplyResult>` for one that talks to a server.
|
|
47
|
+
*/
|
|
48
|
+
export interface MapCommitter<R extends MaybePromise<ApplyResult> = MaybePromise<ApplyResult>> {
|
|
49
|
+
attach(host: CommitHost): void;
|
|
50
|
+
commit(request: CommitRequest): R;
|
|
51
|
+
}
|
|
52
|
+
export interface LocalCommitterOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Called after each successful change, in order, to save it (an API call, a file).
|
|
55
|
+
* It never affects the result of `apply`: a failing save is reported to `onError`.
|
|
56
|
+
*/
|
|
57
|
+
readonly persist?: (patches: readonly MapPatch[], revision: number) => void | Promise<void>;
|
|
58
|
+
readonly onError?: (error: unknown) => void;
|
|
59
|
+
}
|
|
60
|
+
export interface LocalCommitter extends MapCommitter<ApplyResult> {
|
|
61
|
+
/** Resolves once every pending `persist` call has settled. */
|
|
62
|
+
flush(): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
/** Applies changes immediately, in the browser. The editor's committer. */
|
|
65
|
+
export declare function localCommitter(options?: LocalCommitterOptions): LocalCommitter;
|
|
66
|
+
//# sourceMappingURL=committer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"committer.d.ts","sourceRoot":"","sources":["../../src/model/committer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE1D,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yGAAyG;IACzG,SAAS,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,WAAW,CAAC;IACjF;;;OAGG;IACH,KAAK,CAAC,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1D;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CACvC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,YAAY,CAAC,WAAW,CAAC,GAAG,YAAY,CAAC,WAAW,CAAC;IAC3F,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,cAAe,SAAQ,YAAY,CAAC,WAAW,CAAC;IAC/D,8DAA8D;IAC9D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CAgClF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/model/committer.ts
|
|
2
|
+
/** Applies changes immediately, in the browser. The editor's committer. */
|
|
3
|
+
function localCommitter(options = {}) {
|
|
4
|
+
let host;
|
|
5
|
+
let queue = Promise.resolve();
|
|
6
|
+
const report = options.onError ?? ((error) => console.error(error));
|
|
7
|
+
return {
|
|
8
|
+
attach(next) {
|
|
9
|
+
if (host && host !== next) throw new Error("A committer can only be attached to one model");
|
|
10
|
+
host = next;
|
|
11
|
+
},
|
|
12
|
+
commit(request) {
|
|
13
|
+
if (!host) throw new Error("The committer is not attached to a model");
|
|
14
|
+
const result = host.integrate(request.patches, {
|
|
15
|
+
origin: "local",
|
|
16
|
+
record: request.record,
|
|
17
|
+
...request.coalesce !== void 0 ? { coalesce: request.coalesce } : {}
|
|
18
|
+
});
|
|
19
|
+
if (result.ok && options.persist) {
|
|
20
|
+
const { patches, revision } = result;
|
|
21
|
+
const persist = options.persist;
|
|
22
|
+
queue = queue.then(async () => {
|
|
23
|
+
try {
|
|
24
|
+
await persist(patches, revision);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
report(error);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
},
|
|
32
|
+
flush: () => queue
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { localCommitter };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=committer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"committer.js","names":[],"sources":["../../src/model/committer.ts"],"sourcesContent":["import type { MapDefinition, MapState } from \"./definition\";\nimport type { MapPatch } from \"./patches\";\nimport type { ApplyResult, MaybePromise } from \"./result\";\n\nexport type ChangeOrigin = \"local\" | \"remote\";\n\nexport interface IntegrateOptions {\n /**\n * `local` changes are recorded in the history. `remote` ones (broadcast by the\n * server, made by someone else) are not. Defaults to `local`.\n */\n readonly origin?: ChangeOrigin;\n /** Revision assigned by an authority. It must be exactly the next one. */\n readonly revision?: number;\n /** Set to `false` for undo / redo, which manage the history themselves. */\n readonly record?: boolean;\n /** Consecutive changes with the same key become one undo step. */\n readonly coalesce?: string;\n}\n\n/** What a committer can do to the model it is attached to. */\nexport interface CommitHost {\n readonly state: MapState;\n readonly revision: number;\n /** Validates and applies patches atomically, then notifies. This is the only way to change the model. */\n integrate(patches: readonly MapPatch[], options?: IntegrateOptions): ApplyResult;\n /**\n * Replaces the whole state, e.g. with a snapshot from the authority after a long\n * disconnection. The history is cleared, and listeners get a change with `reset: true`.\n */\n reset(definition: MapDefinition, revision: number): void;\n}\n\nexport interface CommitRequest {\n readonly patches: readonly MapPatch[];\n readonly record: boolean;\n readonly coalesce?: string;\n /**\n * The intents the patches were resolved from, when there were some. A committer that\n * talks to an authority sends these instead of the patches: the client asks, the\n * authority decides.\n */\n readonly intents?: readonly unknown[];\n}\n\n/**\n * The only part that knows *where* an intention becomes a fact. The renderer and\n * the tools never know which committer is in use.\n *\n * `R` is the type `model.apply` returns: `ApplyResult` for a synchronous committer\n * (`localCommitter`), `Promise<ApplyResult>` for one that talks to a server.\n */\nexport interface MapCommitter<R extends MaybePromise<ApplyResult> = MaybePromise<ApplyResult>> {\n attach(host: CommitHost): void;\n commit(request: CommitRequest): R;\n}\n\nexport interface LocalCommitterOptions {\n /**\n * Called after each successful change, in order, to save it (an API call, a file).\n * It never affects the result of `apply`: a failing save is reported to `onError`.\n */\n readonly persist?: (patches: readonly MapPatch[], revision: number) => void | Promise<void>;\n readonly onError?: (error: unknown) => void;\n}\n\nexport interface LocalCommitter extends MapCommitter<ApplyResult> {\n /** Resolves once every pending `persist` call has settled. */\n flush(): Promise<void>;\n}\n\n/** Applies changes immediately, in the browser. The editor's committer. */\nexport function localCommitter(options: LocalCommitterOptions = {}): LocalCommitter {\n let host: CommitHost | undefined;\n let queue: Promise<void> = Promise.resolve();\n const report = options.onError ?? ((error: unknown) => console.error(error));\n\n return {\n attach(next) {\n if (host && host !== next) throw new Error(\"A committer can only be attached to one model\");\n host = next;\n },\n commit(request) {\n if (!host) throw new Error(\"The committer is not attached to a model\");\n const result = host.integrate(request.patches, {\n origin: \"local\",\n record: request.record,\n ...(request.coalesce !== undefined ? { coalesce: request.coalesce } : {}),\n });\n if (result.ok && options.persist) {\n const { patches, revision } = result;\n const persist = options.persist;\n queue = queue.then(async () => {\n try {\n await persist(patches, revision);\n } catch (error) {\n report(error);\n }\n });\n }\n return result;\n },\n flush: () => queue,\n };\n}\n"],"mappings":";;AAwEA,SAAgB,eAAe,UAAiC,CAAC,GAAmB;CAClF,IAAI;CACJ,IAAI,QAAuB,QAAQ,QAAQ;CAC3C,MAAM,SAAS,QAAQ,aAAa,UAAmB,QAAQ,MAAM,KAAK;CAE1E,OAAO;EACL,OAAO,MAAM;GACX,IAAI,QAAQ,SAAS,MAAM,MAAM,IAAI,MAAM,+CAA+C;GAC1F,OAAO;EACT;EACA,OAAO,SAAS;GACd,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,0CAA0C;GACrE,MAAM,SAAS,KAAK,UAAU,QAAQ,SAAS;IAC7C,QAAQ;IACR,QAAQ,QAAQ;IAChB,GAAI,QAAQ,aAAa,KAAA,IAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;GACzE,CAAC;GACD,IAAI,OAAO,MAAM,QAAQ,SAAS;IAChC,MAAM,EAAE,SAAS,aAAa;IAC9B,MAAM,UAAU,QAAQ;IACxB,QAAQ,MAAM,KAAK,YAAY;KAC7B,IAAI;MACF,MAAM,QAAQ,SAAS,QAAQ;KACjC,SAAS,OAAO;MACd,OAAO,KAAK;KACd;IACF,CAAC;GACH;GACA,OAAO;EACT;EACA,aAAa;CACf;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-patches.d.ts","sourceRoot":"","sources":["../../src/model/core-patches.ts"],"names":[],"mappings":"AAiEA,eAAO,MAAM,YAAY,mCA6GvB,CAAC"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { cloneJson, isFiniteNumberValue, isJsonObject, isJsonValue } from "./json.js";
|
|
2
|
+
import { checkPlaced, isScale } from "./parse.js";
|
|
3
|
+
import { definePatchHandlers, patchFail, patchOk } from "./patches.js";
|
|
4
|
+
//#region src/model/core-patches.ts
|
|
5
|
+
function insertAt(list, item, index) {
|
|
6
|
+
const at = index === void 0 || index > list.length ? list.length : index;
|
|
7
|
+
return [
|
|
8
|
+
...list.slice(0, at),
|
|
9
|
+
item,
|
|
10
|
+
...list.slice(at)
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
var replaceAt = (list, index, item) => list.map((entry, i) => i === index ? item : entry);
|
|
14
|
+
var withoutAt = (list, index) => list.filter((_, i) => i !== index);
|
|
15
|
+
var validIndex = (index, length) => index === void 0 || Number.isInteger(index) && index >= 0 && index <= length;
|
|
16
|
+
/** Canonical form: an empty `data` object is omitted. */
|
|
17
|
+
function withData(item, data) {
|
|
18
|
+
const { data: _previous, ...rest } = item;
|
|
19
|
+
return Object.keys(data).length === 0 ? rest : {
|
|
20
|
+
...rest,
|
|
21
|
+
data
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function addPlaced(state, collection, value, index) {
|
|
25
|
+
const kind = collection === "elements" ? "element" : "event";
|
|
26
|
+
const issues = checkPlaced(value, kind, kind);
|
|
27
|
+
if (issues.length > 0) return patchFail("invalid-patch", issues.map((i) => `${i.path} ${i.message}`).join("; "));
|
|
28
|
+
if (!isJsonValue(value)) return patchFail("invalid-patch", `${kind} must be JSON-serializable`);
|
|
29
|
+
const item = value;
|
|
30
|
+
const list = state[collection];
|
|
31
|
+
if (!validIndex(index, list.length)) return patchFail("invalid-patch", `index out of range`);
|
|
32
|
+
if (list.some((entry) => entry.id === item.id)) return patchFail("duplicate-id", `${kind} "${item.id}" already exists`);
|
|
33
|
+
const owned = cloneJson(item);
|
|
34
|
+
const copy = withData(owned, owned.data ?? {});
|
|
35
|
+
const removal = {
|
|
36
|
+
type: `${kind}.remove`,
|
|
37
|
+
id: item.id
|
|
38
|
+
};
|
|
39
|
+
return patchOk({
|
|
40
|
+
...state,
|
|
41
|
+
[collection]: insertAt(list, copy, index)
|
|
42
|
+
}, [removal]);
|
|
43
|
+
}
|
|
44
|
+
function removePlaced(state, collection, id) {
|
|
45
|
+
const kind = collection === "elements" ? "element" : "event";
|
|
46
|
+
const list = state[collection];
|
|
47
|
+
const index = list.findIndex((entry) => entry.id === id);
|
|
48
|
+
if (index < 0) return patchFail("not-found", `${kind} "${String(id)}" does not exist`);
|
|
49
|
+
const restore = {
|
|
50
|
+
type: `${kind}.add`,
|
|
51
|
+
[kind]: list[index],
|
|
52
|
+
index
|
|
53
|
+
};
|
|
54
|
+
return patchOk({
|
|
55
|
+
...state,
|
|
56
|
+
[collection]: withoutAt(list, index)
|
|
57
|
+
}, [restore]);
|
|
58
|
+
}
|
|
59
|
+
function movePlaced(state, collection, id, x, y) {
|
|
60
|
+
const kind = collection === "elements" ? "element" : "event";
|
|
61
|
+
if (!isFiniteNumberValue(x) || !isFiniteNumberValue(y)) return patchFail("invalid-patch", "x and y must be finite numbers");
|
|
62
|
+
const list = state[collection];
|
|
63
|
+
const index = list.findIndex((entry) => entry.id === id);
|
|
64
|
+
if (index < 0) return patchFail("not-found", `${kind} "${String(id)}" does not exist`);
|
|
65
|
+
const previous = list[index];
|
|
66
|
+
const back = {
|
|
67
|
+
type: `${kind}.move`,
|
|
68
|
+
id: previous.id,
|
|
69
|
+
x: previous.x,
|
|
70
|
+
y: previous.y
|
|
71
|
+
};
|
|
72
|
+
return patchOk({
|
|
73
|
+
...state,
|
|
74
|
+
[collection]: replaceAt(list, index, {
|
|
75
|
+
...previous,
|
|
76
|
+
x,
|
|
77
|
+
y
|
|
78
|
+
})
|
|
79
|
+
}, [back]);
|
|
80
|
+
}
|
|
81
|
+
var coreHandlers = definePatchHandlers({
|
|
82
|
+
"element.add": { apply: (state, patch) => addPlaced(state, "elements", patch.element, patch.index) },
|
|
83
|
+
"element.remove": { apply: (state, patch) => removePlaced(state, "elements", patch.id) },
|
|
84
|
+
"element.move": { apply: (state, patch) => movePlaced(state, "elements", patch.id, patch.x, patch.y) },
|
|
85
|
+
"element.update": { apply(state, patch) {
|
|
86
|
+
const set = patch.set;
|
|
87
|
+
if (typeof set !== "object" || set === null) return patchFail("invalid-patch", "set must be an object");
|
|
88
|
+
if (set.kind !== void 0 && (typeof set.kind !== "string" || set.kind.length === 0)) return patchFail("invalid-patch", "kind must be a non-empty string");
|
|
89
|
+
if (set.depth !== void 0 && set.depth !== null) {
|
|
90
|
+
const issues = checkPlaced({
|
|
91
|
+
id: "_",
|
|
92
|
+
kind: "_",
|
|
93
|
+
x: 0,
|
|
94
|
+
y: 0,
|
|
95
|
+
depth: set.depth
|
|
96
|
+
}, "element", "element");
|
|
97
|
+
if (issues.length > 0) return patchFail("invalid-patch", issues.map((i) => `${i.path} ${i.message}`).join("; "));
|
|
98
|
+
}
|
|
99
|
+
if (set.scale !== void 0 && set.scale !== null && !isScale(set.scale)) return patchFail("invalid-patch", "scale must be a positive number or [x, y] of positive numbers");
|
|
100
|
+
const index = state.elements.findIndex((entry) => entry.id === patch.id);
|
|
101
|
+
if (index < 0) return patchFail("not-found", `element "${patch.id}" does not exist`);
|
|
102
|
+
const previous = state.elements[index];
|
|
103
|
+
const next = { ...previous };
|
|
104
|
+
const back = {};
|
|
105
|
+
if (set.kind !== void 0) {
|
|
106
|
+
next.kind = set.kind;
|
|
107
|
+
back.kind = previous.kind;
|
|
108
|
+
}
|
|
109
|
+
if (set.depth !== void 0) {
|
|
110
|
+
if (set.depth === null) delete next.depth;
|
|
111
|
+
else next.depth = cloneJson(set.depth);
|
|
112
|
+
back.depth = previous.depth ?? null;
|
|
113
|
+
}
|
|
114
|
+
if (set.scale !== void 0) {
|
|
115
|
+
if (set.scale === null) delete next.scale;
|
|
116
|
+
else next.scale = cloneJson(set.scale);
|
|
117
|
+
back.scale = previous.scale ?? null;
|
|
118
|
+
}
|
|
119
|
+
return patchOk({
|
|
120
|
+
...state,
|
|
121
|
+
elements: replaceAt(state.elements, index, next)
|
|
122
|
+
}, [{
|
|
123
|
+
type: "element.update",
|
|
124
|
+
id: patch.id,
|
|
125
|
+
set: back
|
|
126
|
+
}]);
|
|
127
|
+
} },
|
|
128
|
+
"element.data": { apply(state, patch) {
|
|
129
|
+
const set = patch.set ?? {};
|
|
130
|
+
const unset = patch.unset ?? [];
|
|
131
|
+
if (!isJsonObject(set)) return patchFail("invalid-patch", "set must be a JSON object");
|
|
132
|
+
if (!Array.isArray(unset) || !unset.every((key) => typeof key === "string")) return patchFail("invalid-patch", "unset must be a list of keys");
|
|
133
|
+
const forbidden = [...Object.keys(set), ...unset].find((key) => key === "__proto__");
|
|
134
|
+
if (forbidden) return patchFail("invalid-patch", `"${forbidden}" is not a valid data key`);
|
|
135
|
+
const overlap = unset.find((key) => Object.hasOwn(set, key));
|
|
136
|
+
if (overlap !== void 0) return patchFail("invalid-patch", `"${overlap}" is both set and unset`);
|
|
137
|
+
const index = state.elements.findIndex((entry) => entry.id === patch.id);
|
|
138
|
+
if (index < 0) return patchFail("not-found", `element "${patch.id}" does not exist`);
|
|
139
|
+
const previous = state.elements[index];
|
|
140
|
+
const before = previous.data ?? {};
|
|
141
|
+
const after = { ...before };
|
|
142
|
+
const restore = {};
|
|
143
|
+
const remove = [];
|
|
144
|
+
for (const [key, value] of Object.entries(set)) {
|
|
145
|
+
if (Object.hasOwn(before, key)) restore[key] = before[key];
|
|
146
|
+
else remove.push(key);
|
|
147
|
+
after[key] = cloneJson(value);
|
|
148
|
+
}
|
|
149
|
+
for (const key of unset) if (Object.hasOwn(before, key)) {
|
|
150
|
+
restore[key] = before[key];
|
|
151
|
+
delete after[key];
|
|
152
|
+
}
|
|
153
|
+
const inverse = {
|
|
154
|
+
type: "element.data",
|
|
155
|
+
id: patch.id,
|
|
156
|
+
...Object.keys(restore).length > 0 ? { set: restore } : {},
|
|
157
|
+
...remove.length > 0 ? { unset: remove } : {}
|
|
158
|
+
};
|
|
159
|
+
return patchOk({
|
|
160
|
+
...state,
|
|
161
|
+
elements: replaceAt(state.elements, index, withData(previous, after))
|
|
162
|
+
}, [inverse]);
|
|
163
|
+
} },
|
|
164
|
+
"event.add": { apply: (state, patch) => addPlaced(state, "events", patch.event, patch.index) },
|
|
165
|
+
"event.remove": { apply: (state, patch) => removePlaced(state, "events", patch.id) },
|
|
166
|
+
"event.move": { apply: (state, patch) => movePlaced(state, "events", patch.id, patch.x, patch.y) },
|
|
167
|
+
"extension.set": { apply(state, patch) {
|
|
168
|
+
if (typeof patch.id !== "string" || patch.id.length === 0) return patchFail("invalid-patch", "id must be a non-empty string");
|
|
169
|
+
if (patch.value !== void 0 && !isJsonValue(patch.value)) return patchFail("invalid-patch", "value must be JSON");
|
|
170
|
+
if (patch.id === "__proto__") return patchFail("invalid-patch", `"__proto__" is not a valid extension id`);
|
|
171
|
+
const extensions = { ...state.extensions ?? {} };
|
|
172
|
+
const had = Object.hasOwn(extensions, patch.id);
|
|
173
|
+
const previous = extensions[patch.id];
|
|
174
|
+
if (patch.value === void 0) delete extensions[patch.id];
|
|
175
|
+
else extensions[patch.id] = cloneJson(patch.value);
|
|
176
|
+
const { extensions: _drop, ...rest } = state;
|
|
177
|
+
return patchOk(Object.keys(extensions).length === 0 ? rest : {
|
|
178
|
+
...rest,
|
|
179
|
+
extensions
|
|
180
|
+
}, [had ? {
|
|
181
|
+
type: "extension.set",
|
|
182
|
+
id: patch.id,
|
|
183
|
+
value: previous
|
|
184
|
+
} : {
|
|
185
|
+
type: "extension.set",
|
|
186
|
+
id: patch.id
|
|
187
|
+
}]);
|
|
188
|
+
} }
|
|
189
|
+
});
|
|
190
|
+
//#endregion
|
|
191
|
+
export { coreHandlers };
|
|
192
|
+
|
|
193
|
+
//# sourceMappingURL=core-patches.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-patches.js","names":[],"sources":["../../src/model/core-patches.ts"],"sourcesContent":["import type { MapDepth, MapElement, MapEvent, MapScale, MapState } from \"./definition\";\nimport { cloneJson, isFiniteNumberValue, isJsonObject, isJsonValue, type JsonObject, type JsonValue } from \"./json\";\nimport { checkPlaced, isScale } from \"./parse\";\nimport { definePatchHandlers, patchFail, patchOk, type MapPatch, type PatchOutcome } from \"./patches\";\n\ntype Placed = MapElement | MapEvent;\n\nfunction insertAt<T>(list: readonly T[], item: T, index: number | undefined): readonly T[] {\n const at = index === undefined || index > list.length ? list.length : index;\n return [...list.slice(0, at), item, ...list.slice(at)];\n}\n\nconst replaceAt = <T>(list: readonly T[], index: number, item: T): readonly T[] =>\n list.map((entry, i) => (i === index ? item : entry));\n\nconst withoutAt = <T>(list: readonly T[], index: number): readonly T[] => list.filter((_, i) => i !== index);\n\nconst validIndex = (index: unknown, length: number): boolean =>\n index === undefined || (Number.isInteger(index) && (index as number) >= 0 && (index as number) <= length);\n\n/** Canonical form: an empty `data` object is omitted. */\nfunction withData<T extends Placed>(item: T, data: JsonObject): T {\n const { data: _previous, ...rest } = item;\n return (Object.keys(data).length === 0 ? rest : { ...rest, data }) as T;\n}\n\ntype Collection = \"elements\" | \"events\";\n\nfunction addPlaced(state: MapState, collection: Collection, value: unknown, index: unknown): PatchOutcome {\n const kind = collection === \"elements\" ? \"element\" : \"event\";\n const issues = checkPlaced(value, kind, kind);\n if (issues.length > 0) return patchFail(\"invalid-patch\", issues.map((i) => `${i.path} ${i.message}`).join(\"; \"));\n if (!isJsonValue(value)) return patchFail(\"invalid-patch\", `${kind} must be JSON-serializable`);\n const item = value as unknown as Placed;\n const list = state[collection] as readonly Placed[];\n if (!validIndex(index, list.length)) return patchFail(\"invalid-patch\", `index out of range`);\n if (list.some((entry) => entry.id === item.id)) {\n return patchFail(\"duplicate-id\", `${kind} \"${item.id}\" already exists`);\n }\n const owned = cloneJson(item as unknown as JsonObject) as unknown as Placed;\n const copy = withData(owned, (owned.data ?? {}) as JsonObject);\n const removal = { type: `${kind}.remove`, id: item.id } as MapPatch;\n return patchOk({ ...state, [collection]: insertAt(list, copy, index as number | undefined) }, [removal]);\n}\n\nfunction removePlaced(state: MapState, collection: Collection, id: unknown): PatchOutcome {\n const kind = collection === \"elements\" ? \"element\" : \"event\";\n const list = state[collection] as readonly Placed[];\n const index = list.findIndex((entry) => entry.id === id);\n if (index < 0) return patchFail(\"not-found\", `${kind} \"${String(id)}\" does not exist`);\n const restore = { type: `${kind}.add`, [kind]: list[index], index } as unknown as MapPatch;\n return patchOk({ ...state, [collection]: withoutAt(list, index) }, [restore]);\n}\n\nfunction movePlaced(state: MapState, collection: Collection, id: unknown, x: unknown, y: unknown): PatchOutcome {\n const kind = collection === \"elements\" ? \"element\" : \"event\";\n if (!isFiniteNumberValue(x) || !isFiniteNumberValue(y)) return patchFail(\"invalid-patch\", \"x and y must be finite numbers\");\n const list = state[collection] as readonly Placed[];\n const index = list.findIndex((entry) => entry.id === id);\n if (index < 0) return patchFail(\"not-found\", `${kind} \"${String(id)}\" does not exist`);\n const previous = list[index]!;\n const back = { type: `${kind}.move`, id: previous.id, x: previous.x, y: previous.y } as MapPatch;\n return patchOk({ ...state, [collection]: replaceAt(list, index, { ...previous, x, y }) }, [back]);\n}\n\nexport const coreHandlers = definePatchHandlers({\n \"element.add\": { apply: (state, patch) => addPlaced(state, \"elements\", patch.element, patch.index) },\n \"element.remove\": { apply: (state, patch) => removePlaced(state, \"elements\", patch.id) },\n \"element.move\": { apply: (state, patch) => movePlaced(state, \"elements\", patch.id, patch.x, patch.y) },\n\n \"element.update\": {\n apply(state, patch) {\n const set = patch.set as { kind?: unknown; depth?: unknown; scale?: unknown } | undefined;\n if (typeof set !== \"object\" || set === null) return patchFail(\"invalid-patch\", \"set must be an object\");\n if (set.kind !== undefined && (typeof set.kind !== \"string\" || set.kind.length === 0)) {\n return patchFail(\"invalid-patch\", \"kind must be a non-empty string\");\n }\n if (set.depth !== undefined && set.depth !== null) {\n const issues = checkPlaced({ id: \"_\", kind: \"_\", x: 0, y: 0, depth: set.depth }, \"element\", \"element\");\n if (issues.length > 0) return patchFail(\"invalid-patch\", issues.map((i) => `${i.path} ${i.message}`).join(\"; \"));\n }\n if (set.scale !== undefined && set.scale !== null && !isScale(set.scale)) {\n return patchFail(\"invalid-patch\", \"scale must be a positive number or [x, y] of positive numbers\");\n }\n const index = state.elements.findIndex((entry) => entry.id === patch.id);\n if (index < 0) return patchFail(\"not-found\", `element \"${patch.id}\" does not exist`);\n const previous = state.elements[index]!;\n\n const next: Record<string, unknown> = { ...previous };\n const back: { kind?: string; depth?: MapDepth | null; scale?: MapScale | null } = {};\n if (set.kind !== undefined) {\n next.kind = set.kind;\n back.kind = previous.kind;\n }\n if (set.depth !== undefined) {\n if (set.depth === null) delete next.depth;\n else next.depth = cloneJson(set.depth as JsonValue);\n back.depth = previous.depth ?? null;\n }\n if (set.scale !== undefined) {\n if (set.scale === null) delete next.scale;\n else next.scale = cloneJson(set.scale as JsonValue);\n back.scale = previous.scale ?? null;\n }\n return patchOk({ ...state, elements: replaceAt(state.elements, index, next as unknown as MapElement) }, [\n { type: \"element.update\", id: patch.id, set: back },\n ]);\n },\n },\n\n \"element.data\": {\n apply(state, patch) {\n const set = patch.set ?? {};\n const unset = patch.unset ?? [];\n if (!isJsonObject(set)) return patchFail(\"invalid-patch\", \"set must be a JSON object\");\n if (!Array.isArray(unset) || !unset.every((key) => typeof key === \"string\")) {\n return patchFail(\"invalid-patch\", \"unset must be a list of keys\");\n }\n const forbidden = [...Object.keys(set), ...unset].find((key) => key === \"__proto__\");\n if (forbidden) return patchFail(\"invalid-patch\", `\"${forbidden}\" is not a valid data key`);\n const overlap = unset.find((key) => Object.hasOwn(set, key));\n if (overlap !== undefined) return patchFail(\"invalid-patch\", `\"${overlap}\" is both set and unset`);\n const index = state.elements.findIndex((entry) => entry.id === patch.id);\n if (index < 0) return patchFail(\"not-found\", `element \"${patch.id}\" does not exist`);\n const previous = state.elements[index]!;\n const before = previous.data ?? {};\n\n const after: Record<string, JsonValue> = { ...before };\n const restore: Record<string, JsonValue> = {};\n const remove: string[] = [];\n for (const [key, value] of Object.entries(set)) {\n if (Object.hasOwn(before, key)) restore[key] = before[key]!;\n else remove.push(key);\n after[key] = cloneJson(value);\n }\n for (const key of unset) {\n if (Object.hasOwn(before, key)) {\n restore[key] = before[key]!;\n delete after[key];\n }\n }\n const inverse: MapPatch = {\n type: \"element.data\",\n id: patch.id,\n ...(Object.keys(restore).length > 0 ? { set: restore } : {}),\n ...(remove.length > 0 ? { unset: remove } : {}),\n };\n return patchOk({ ...state, elements: replaceAt(state.elements, index, withData(previous, after)) }, [inverse]);\n },\n },\n\n \"event.add\": { apply: (state, patch) => addPlaced(state, \"events\", patch.event, patch.index) },\n \"event.remove\": { apply: (state, patch) => removePlaced(state, \"events\", patch.id) },\n \"event.move\": { apply: (state, patch) => movePlaced(state, \"events\", patch.id, patch.x, patch.y) },\n\n \"extension.set\": {\n apply(state, patch) {\n if (typeof patch.id !== \"string\" || patch.id.length === 0) return patchFail(\"invalid-patch\", \"id must be a non-empty string\");\n if (patch.value !== undefined && !isJsonValue(patch.value)) return patchFail(\"invalid-patch\", \"value must be JSON\");\n if (patch.id === \"__proto__\") return patchFail(\"invalid-patch\", `\"__proto__\" is not a valid extension id`);\n const extensions: Record<string, JsonValue> = { ...(state.extensions ?? {}) };\n const had = Object.hasOwn(extensions, patch.id);\n const previous = extensions[patch.id];\n if (patch.value === undefined) delete extensions[patch.id];\n else extensions[patch.id] = cloneJson(patch.value);\n\n const { extensions: _drop, ...rest } = state;\n const next: MapState = Object.keys(extensions).length === 0 ? rest : { ...rest, extensions };\n const inverse: MapPatch = had\n ? { type: \"extension.set\", id: patch.id, value: previous as JsonValue }\n : { type: \"extension.set\", id: patch.id };\n return patchOk(next, [inverse]);\n },\n },\n});\n"],"mappings":";;;;AAOA,SAAS,SAAY,MAAoB,MAAS,OAAyC;CACzF,MAAM,KAAK,UAAU,KAAA,KAAa,QAAQ,KAAK,SAAS,KAAK,SAAS;CACtE,OAAO;EAAC,GAAG,KAAK,MAAM,GAAG,EAAE;EAAG;EAAM,GAAG,KAAK,MAAM,EAAE;CAAC;AACvD;AAEA,IAAM,aAAgB,MAAoB,OAAe,SACvD,KAAK,KAAK,OAAO,MAAO,MAAM,QAAQ,OAAO,KAAM;AAErD,IAAM,aAAgB,MAAoB,UAAgC,KAAK,QAAQ,GAAG,MAAM,MAAM,KAAK;AAE3G,IAAM,cAAc,OAAgB,WAClC,UAAU,KAAA,KAAc,OAAO,UAAU,KAAK,KAAM,SAAoB,KAAM,SAAoB;;AAGpG,SAAS,SAA2B,MAAS,MAAqB;CAChE,MAAM,EAAE,MAAM,WAAW,GAAG,SAAS;CACrC,OAAQ,OAAO,KAAK,IAAI,CAAC,CAAC,WAAW,IAAI,OAAO;EAAE,GAAG;EAAM;CAAK;AAClE;AAIA,SAAS,UAAU,OAAiB,YAAwB,OAAgB,OAA8B;CACxG,MAAM,OAAO,eAAe,aAAa,YAAY;CACrD,MAAM,SAAS,YAAY,OAAO,MAAM,IAAI;CAC5C,IAAI,OAAO,SAAS,GAAG,OAAO,UAAU,iBAAiB,OAAO,KAAK,MAAM,GAAG,EAAE,KAAK,GAAG,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI,CAAC;CAC/G,IAAI,CAAC,YAAY,KAAK,GAAG,OAAO,UAAU,iBAAiB,GAAG,KAAK,2BAA2B;CAC9F,MAAM,OAAO;CACb,MAAM,OAAO,MAAM;CACnB,IAAI,CAAC,WAAW,OAAO,KAAK,MAAM,GAAG,OAAO,UAAU,iBAAiB,oBAAoB;CAC3F,IAAI,KAAK,MAAM,UAAU,MAAM,OAAO,KAAK,EAAE,GAC3C,OAAO,UAAU,gBAAgB,GAAG,KAAK,IAAI,KAAK,GAAG,iBAAiB;CAExE,MAAM,QAAQ,UAAU,IAA6B;CACrD,MAAM,OAAO,SAAS,OAAQ,MAAM,QAAQ,CAAC,CAAgB;CAC7D,MAAM,UAAU;EAAE,MAAM,GAAG,KAAK;EAAU,IAAI,KAAK;CAAG;CACtD,OAAO,QAAQ;EAAE,GAAG;GAAQ,aAAa,SAAS,MAAM,MAAM,KAA2B;CAAE,GAAG,CAAC,OAAO,CAAC;AACzG;AAEA,SAAS,aAAa,OAAiB,YAAwB,IAA2B;CACxF,MAAM,OAAO,eAAe,aAAa,YAAY;CACrD,MAAM,OAAO,MAAM;CACnB,MAAM,QAAQ,KAAK,WAAW,UAAU,MAAM,OAAO,EAAE;CACvD,IAAI,QAAQ,GAAG,OAAO,UAAU,aAAa,GAAG,KAAK,IAAI,OAAO,EAAE,EAAE,iBAAiB;CACrF,MAAM,UAAU;EAAE,MAAM,GAAG,KAAK;GAAQ,OAAO,KAAK;EAAQ;CAAM;CAClE,OAAO,QAAQ;EAAE,GAAG;GAAQ,aAAa,UAAU,MAAM,KAAK;CAAE,GAAG,CAAC,OAAO,CAAC;AAC9E;AAEA,SAAS,WAAW,OAAiB,YAAwB,IAAa,GAAY,GAA0B;CAC9G,MAAM,OAAO,eAAe,aAAa,YAAY;CACrD,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,oBAAoB,CAAC,GAAG,OAAO,UAAU,iBAAiB,gCAAgC;CAC1H,MAAM,OAAO,MAAM;CACnB,MAAM,QAAQ,KAAK,WAAW,UAAU,MAAM,OAAO,EAAE;CACvD,IAAI,QAAQ,GAAG,OAAO,UAAU,aAAa,GAAG,KAAK,IAAI,OAAO,EAAE,EAAE,iBAAiB;CACrF,MAAM,WAAW,KAAK;CACtB,MAAM,OAAO;EAAE,MAAM,GAAG,KAAK;EAAQ,IAAI,SAAS;EAAI,GAAG,SAAS;EAAG,GAAG,SAAS;CAAE;CACnF,OAAO,QAAQ;EAAE,GAAG;GAAQ,aAAa,UAAU,MAAM,OAAO;GAAE,GAAG;GAAU;GAAG;EAAE,CAAC;CAAE,GAAG,CAAC,IAAI,CAAC;AAClG;AAEA,IAAa,eAAe,oBAAoB;CAC9C,eAAe,EAAE,QAAQ,OAAO,UAAU,UAAU,OAAO,YAAY,MAAM,SAAS,MAAM,KAAK,EAAE;CACnG,kBAAkB,EAAE,QAAQ,OAAO,UAAU,aAAa,OAAO,YAAY,MAAM,EAAE,EAAE;CACvF,gBAAgB,EAAE,QAAQ,OAAO,UAAU,WAAW,OAAO,YAAY,MAAM,IAAI,MAAM,GAAG,MAAM,CAAC,EAAE;CAErG,kBAAkB,EAChB,MAAM,OAAO,OAAO;EAClB,MAAM,MAAM,MAAM;EAClB,IAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM,OAAO,UAAU,iBAAiB,uBAAuB;EACtG,IAAI,IAAI,SAAS,KAAA,MAAc,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,WAAW,IACjF,OAAO,UAAU,iBAAiB,iCAAiC;EAErE,IAAI,IAAI,UAAU,KAAA,KAAa,IAAI,UAAU,MAAM;GACjD,MAAM,SAAS,YAAY;IAAE,IAAI;IAAK,MAAM;IAAK,GAAG;IAAG,GAAG;IAAG,OAAO,IAAI;GAAM,GAAG,WAAW,SAAS;GACrG,IAAI,OAAO,SAAS,GAAG,OAAO,UAAU,iBAAiB,OAAO,KAAK,MAAM,GAAG,EAAE,KAAK,GAAG,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI,CAAC;EACjH;EACA,IAAI,IAAI,UAAU,KAAA,KAAa,IAAI,UAAU,QAAQ,CAAC,QAAQ,IAAI,KAAK,GACrE,OAAO,UAAU,iBAAiB,+DAA+D;EAEnG,MAAM,QAAQ,MAAM,SAAS,WAAW,UAAU,MAAM,OAAO,MAAM,EAAE;EACvE,IAAI,QAAQ,GAAG,OAAO,UAAU,aAAa,YAAY,MAAM,GAAG,iBAAiB;EACnF,MAAM,WAAW,MAAM,SAAS;EAEhC,MAAM,OAAgC,EAAE,GAAG,SAAS;EACpD,MAAM,OAA4E,CAAC;EACnF,IAAI,IAAI,SAAS,KAAA,GAAW;GAC1B,KAAK,OAAO,IAAI;GAChB,KAAK,OAAO,SAAS;EACvB;EACA,IAAI,IAAI,UAAU,KAAA,GAAW;GAC3B,IAAI,IAAI,UAAU,MAAM,OAAO,KAAK;QAC/B,KAAK,QAAQ,UAAU,IAAI,KAAkB;GAClD,KAAK,QAAQ,SAAS,SAAS;EACjC;EACA,IAAI,IAAI,UAAU,KAAA,GAAW;GAC3B,IAAI,IAAI,UAAU,MAAM,OAAO,KAAK;QAC/B,KAAK,QAAQ,UAAU,IAAI,KAAkB;GAClD,KAAK,QAAQ,SAAS,SAAS;EACjC;EACA,OAAO,QAAQ;GAAE,GAAG;GAAO,UAAU,UAAU,MAAM,UAAU,OAAO,IAA6B;EAAE,GAAG,CACtG;GAAE,MAAM;GAAkB,IAAI,MAAM;GAAI,KAAK;EAAK,CACpD,CAAC;CACH,EACF;CAEA,gBAAgB,EACd,MAAM,OAAO,OAAO;EAClB,MAAM,MAAM,MAAM,OAAO,CAAC;EAC1B,MAAM,QAAQ,MAAM,SAAS,CAAC;EAC9B,IAAI,CAAC,aAAa,GAAG,GAAG,OAAO,UAAU,iBAAiB,2BAA2B;EACrF,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,OAAO,QAAQ,OAAO,QAAQ,QAAQ,GACxE,OAAO,UAAU,iBAAiB,8BAA8B;EAElE,MAAM,YAAY,CAAC,GAAG,OAAO,KAAK,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,QAAQ,QAAQ,WAAW;EACnF,IAAI,WAAW,OAAO,UAAU,iBAAiB,IAAI,UAAU,0BAA0B;EACzF,MAAM,UAAU,MAAM,MAAM,QAAQ,OAAO,OAAO,KAAK,GAAG,CAAC;EAC3D,IAAI,YAAY,KAAA,GAAW,OAAO,UAAU,iBAAiB,IAAI,QAAQ,wBAAwB;EACjG,MAAM,QAAQ,MAAM,SAAS,WAAW,UAAU,MAAM,OAAO,MAAM,EAAE;EACvE,IAAI,QAAQ,GAAG,OAAO,UAAU,aAAa,YAAY,MAAM,GAAG,iBAAiB;EACnF,MAAM,WAAW,MAAM,SAAS;EAChC,MAAM,SAAS,SAAS,QAAQ,CAAC;EAEjC,MAAM,QAAmC,EAAE,GAAG,OAAO;EACrD,MAAM,UAAqC,CAAC;EAC5C,MAAM,SAAmB,CAAC;EAC1B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG;GAC9C,IAAI,OAAO,OAAO,QAAQ,GAAG,GAAG,QAAQ,OAAO,OAAO;QACjD,OAAO,KAAK,GAAG;GACpB,MAAM,OAAO,UAAU,KAAK;EAC9B;EACA,KAAK,MAAM,OAAO,OAChB,IAAI,OAAO,OAAO,QAAQ,GAAG,GAAG;GAC9B,QAAQ,OAAO,OAAO;GACtB,OAAO,MAAM;EACf;EAEF,MAAM,UAAoB;GACxB,MAAM;GACN,IAAI,MAAM;GACV,GAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IAAI,EAAE,KAAK,QAAQ,IAAI,CAAC;GAC1D,GAAI,OAAO,SAAS,IAAI,EAAE,OAAO,OAAO,IAAI,CAAC;EAC/C;EACA,OAAO,QAAQ;GAAE,GAAG;GAAO,UAAU,UAAU,MAAM,UAAU,OAAO,SAAS,UAAU,KAAK,CAAC;EAAE,GAAG,CAAC,OAAO,CAAC;CAC/G,EACF;CAEA,aAAa,EAAE,QAAQ,OAAO,UAAU,UAAU,OAAO,UAAU,MAAM,OAAO,MAAM,KAAK,EAAE;CAC7F,gBAAgB,EAAE,QAAQ,OAAO,UAAU,aAAa,OAAO,UAAU,MAAM,EAAE,EAAE;CACnF,cAAc,EAAE,QAAQ,OAAO,UAAU,WAAW,OAAO,UAAU,MAAM,IAAI,MAAM,GAAG,MAAM,CAAC,EAAE;CAEjG,iBAAiB,EACf,MAAM,OAAO,OAAO;EAClB,IAAI,OAAO,MAAM,OAAO,YAAY,MAAM,GAAG,WAAW,GAAG,OAAO,UAAU,iBAAiB,+BAA+B;EAC5H,IAAI,MAAM,UAAU,KAAA,KAAa,CAAC,YAAY,MAAM,KAAK,GAAG,OAAO,UAAU,iBAAiB,oBAAoB;EAClH,IAAI,MAAM,OAAO,aAAa,OAAO,UAAU,iBAAiB,yCAAyC;EACzG,MAAM,aAAwC,EAAE,GAAI,MAAM,cAAc,CAAC,EAAG;EAC5E,MAAM,MAAM,OAAO,OAAO,YAAY,MAAM,EAAE;EAC9C,MAAM,WAAW,WAAW,MAAM;EAClC,IAAI,MAAM,UAAU,KAAA,GAAW,OAAO,WAAW,MAAM;OAClD,WAAW,MAAM,MAAM,UAAU,MAAM,KAAK;EAEjD,MAAM,EAAE,YAAY,OAAO,GAAG,SAAS;EAKvC,OAAO,QAJgB,OAAO,KAAK,UAAU,CAAC,CAAC,WAAW,IAAI,OAAO;GAAE,GAAG;GAAM;EAAW,GAItE,CAHK,MACtB;GAAE,MAAM;GAAiB,IAAI,MAAM;GAAI,OAAO;EAAsB,IACpE;GAAE,MAAM;GAAiB,IAAI,MAAM;EAAG,CACb,CAAC;CAChC,EACF;AACF,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AssetKind, AssetResolver } from './assets';
|
|
2
|
+
import { MapDefinition, MapDepth, MapElement, MapEvent, MapScale } from './definition';
|
|
3
|
+
import { JsonObject, JsonValue } from './json';
|
|
4
|
+
export type TypedElement<K extends string = string> = MapElement & {
|
|
5
|
+
readonly kind: K;
|
|
6
|
+
};
|
|
7
|
+
export type TypedEvent<G extends string = string> = MapEvent & {
|
|
8
|
+
readonly graphic?: G;
|
|
9
|
+
};
|
|
10
|
+
export interface ElementInput {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly x: number;
|
|
13
|
+
readonly y: number;
|
|
14
|
+
readonly depth?: MapDepth;
|
|
15
|
+
readonly scale?: MapScale;
|
|
16
|
+
readonly data?: JsonObject;
|
|
17
|
+
}
|
|
18
|
+
export interface EventInput<G extends string = never> {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly x: number;
|
|
21
|
+
readonly y: number;
|
|
22
|
+
readonly graphic?: G;
|
|
23
|
+
readonly data?: JsonObject;
|
|
24
|
+
}
|
|
25
|
+
/** An element placed on a map: `el("tree-oak", { id: "t1", x: 128, y: 96 })`. */
|
|
26
|
+
export declare function el<K extends string>(kind: K, input: ElementInput): TypedElement<K>;
|
|
27
|
+
/** An event placed on a map. `graphic`, when given, must be a known asset. */
|
|
28
|
+
export declare function event<G extends string = never>(kind: string, input: EventInput<G>): TypedEvent<G>;
|
|
29
|
+
/** Something that writes the data a feature owns, once the size of the map is known (e.g. `terrain.layers([...])`). */
|
|
30
|
+
export interface TerrainBuilder {
|
|
31
|
+
toExtension(size: readonly [width: number, height: number]): JsonValue;
|
|
32
|
+
}
|
|
33
|
+
export interface MapInput<K extends string = string> {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
/** World size, in pixels. */
|
|
36
|
+
readonly size: readonly [width: number, height: number];
|
|
37
|
+
readonly tileSize?: number;
|
|
38
|
+
readonly elements?: readonly TypedElement<K>[];
|
|
39
|
+
readonly events?: readonly TypedEvent<K>[];
|
|
40
|
+
readonly extensions?: {
|
|
41
|
+
readonly [featureId: string]: JsonValue;
|
|
42
|
+
};
|
|
43
|
+
/** Painted terrain, from `defineTerrain(...).layers([...])`. Stored under `extensions.terrain`. */
|
|
44
|
+
readonly terrain?: TerrainBuilder;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Describes a map in code. References to assets are checked by the compiler and
|
|
48
|
+
* again at runtime. Returns a plain, serializable {@link MapDefinition}, so the
|
|
49
|
+
* same map can become a JSON file or travel over the network.
|
|
50
|
+
*/
|
|
51
|
+
export declare function defineMap<A extends AssetResolver<string>>(assets: A, input: MapInput<AssetKind<A>>): MapDefinition;
|
|
52
|
+
//# sourceMappingURL=define-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-map.d.ts","sourceRoot":"","sources":["../../src/model/define-map.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { isPlainObject } from "./json.js";
|
|
2
|
+
import "./definition.js";
|
|
3
|
+
import { MapDefinitionError } from "./errors.js";
|
|
4
|
+
import { parseMapDefinition } from "./parse.js";
|
|
5
|
+
//#region src/model/define-map.ts
|
|
6
|
+
/** An element placed on a map: `el("tree-oak", { id: "t1", x: 128, y: 96 })`. */
|
|
7
|
+
function el(kind, input) {
|
|
8
|
+
return {
|
|
9
|
+
...input,
|
|
10
|
+
kind
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** An event placed on a map. `graphic`, when given, must be a known asset. */
|
|
14
|
+
function event(kind, input) {
|
|
15
|
+
return {
|
|
16
|
+
...input,
|
|
17
|
+
kind
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Describes a map in code. References to assets are checked by the compiler and
|
|
22
|
+
* again at runtime. Returns a plain, serializable {@link MapDefinition}, so the
|
|
23
|
+
* same map can become a JSON file or travel over the network.
|
|
24
|
+
*/
|
|
25
|
+
function defineMap(assets, input) {
|
|
26
|
+
const definition = parseMapDefinition({
|
|
27
|
+
version: 1,
|
|
28
|
+
id: input.id,
|
|
29
|
+
size: input.size,
|
|
30
|
+
...input.tileSize !== void 0 ? { tileSize: input.tileSize } : {},
|
|
31
|
+
elements: input.elements ?? [],
|
|
32
|
+
events: input.events ?? [],
|
|
33
|
+
...input.extensions !== void 0 || input.terrain !== void 0 ? { extensions: {
|
|
34
|
+
...input.extensions ?? {},
|
|
35
|
+
...input.terrain ? { terrain: input.terrain.toExtension(input.size) } : {}
|
|
36
|
+
} } : {}
|
|
37
|
+
});
|
|
38
|
+
const issues = [];
|
|
39
|
+
definition.elements.forEach((element, index) => {
|
|
40
|
+
if (!assets.has(element.kind)) issues.push({
|
|
41
|
+
path: `elements[${index}].kind`,
|
|
42
|
+
message: `unknown asset "${element.kind}"`
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
definition.events.forEach((entry, index) => {
|
|
46
|
+
if (entry.graphic !== void 0 && !assets.has(entry.graphic)) issues.push({
|
|
47
|
+
path: `events[${index}].graphic`,
|
|
48
|
+
message: `unknown asset "${entry.graphic}"`
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const terrain = definition.extensions?.terrain;
|
|
52
|
+
const materials = input.terrain && isPlainObject(terrain) ? terrain.materials : void 0;
|
|
53
|
+
if (Array.isArray(materials)) materials.forEach((material, index) => {
|
|
54
|
+
const texture = isPlainObject(material) ? material.texture : void 0;
|
|
55
|
+
if (typeof texture === "string" && !assets.has(texture)) issues.push({
|
|
56
|
+
path: `extensions.terrain.materials[${index}].texture`,
|
|
57
|
+
message: `unknown asset "${texture}"`
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
if (issues.length > 0) throw new MapDefinitionError(issues);
|
|
61
|
+
return definition;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { defineMap, el, event };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=define-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-map.js","names":[],"sources":["../../src/model/define-map.ts"],"sourcesContent":["import type { AssetKind, AssetResolver } from \"./assets\";\nimport { MAP_DEFINITION_VERSION, type MapDefinition, type MapDepth, type MapElement, type MapEvent, type MapScale } from \"./definition\";\nimport { MapDefinitionError, type MapIssue } from \"./errors\";\nimport { isPlainObject, type JsonObject, type JsonValue } from \"./json\";\nimport { parseMapDefinition } from \"./parse\";\n\nexport type TypedElement<K extends string = string> = MapElement & { readonly kind: K };\nexport type TypedEvent<G extends string = string> = MapEvent & { readonly graphic?: G };\n\nexport interface ElementInput {\n readonly id: string;\n readonly x: number;\n readonly y: number;\n readonly depth?: MapDepth;\n readonly scale?: MapScale;\n readonly data?: JsonObject;\n}\n\nexport interface EventInput<G extends string = never> {\n readonly id: string;\n readonly x: number;\n readonly y: number;\n readonly graphic?: G;\n readonly data?: JsonObject;\n}\n\n/** An element placed on a map: `el(\"tree-oak\", { id: \"t1\", x: 128, y: 96 })`. */\nexport function el<K extends string>(kind: K, input: ElementInput): TypedElement<K> {\n return { ...input, kind };\n}\n\n/** An event placed on a map. `graphic`, when given, must be a known asset. */\nexport function event<G extends string = never>(kind: string, input: EventInput<G>): TypedEvent<G> {\n return { ...input, kind };\n}\n\n/** Something that writes the data a feature owns, once the size of the map is known (e.g. `terrain.layers([...])`). */\nexport interface TerrainBuilder {\n toExtension(size: readonly [width: number, height: number]): JsonValue;\n}\n\nexport interface MapInput<K extends string = string> {\n readonly id: string;\n /** World size, in pixels. */\n readonly size: readonly [width: number, height: number];\n readonly tileSize?: number;\n readonly elements?: readonly TypedElement<K>[];\n readonly events?: readonly TypedEvent<K>[];\n readonly extensions?: { readonly [featureId: string]: JsonValue };\n /** Painted terrain, from `defineTerrain(...).layers([...])`. Stored under `extensions.terrain`. */\n readonly terrain?: TerrainBuilder;\n}\n\n/**\n * Describes a map in code. References to assets are checked by the compiler and\n * again at runtime. Returns a plain, serializable {@link MapDefinition}, so the\n * same map can become a JSON file or travel over the network.\n */\nexport function defineMap<A extends AssetResolver<string>>(assets: A, input: MapInput<AssetKind<A>>): MapDefinition {\n const definition = parseMapDefinition({\n version: MAP_DEFINITION_VERSION,\n id: input.id,\n size: input.size,\n ...(input.tileSize !== undefined ? { tileSize: input.tileSize } : {}),\n elements: input.elements ?? [],\n events: input.events ?? [],\n ...(input.extensions !== undefined || input.terrain !== undefined\n ? { extensions: { ...(input.extensions ?? {}), ...(input.terrain ? { terrain: input.terrain.toExtension(input.size) } : {}) } }\n : {}),\n });\n\n const issues: MapIssue[] = [];\n definition.elements.forEach((element, index) => {\n if (!assets.has(element.kind)) {\n issues.push({ path: `elements[${index}].kind`, message: `unknown asset \"${element.kind}\"` });\n }\n });\n definition.events.forEach((entry, index) => {\n if (entry.graphic !== undefined && !assets.has(entry.graphic)) {\n issues.push({ path: `events[${index}].graphic`, message: `unknown asset \"${entry.graphic}\"` });\n }\n });\n const terrain = definition.extensions?.terrain;\n const materials = input.terrain && isPlainObject(terrain) ? terrain.materials : undefined;\n if (Array.isArray(materials)) {\n materials.forEach((material, index) => {\n const texture = isPlainObject(material) ? material.texture : undefined;\n if (typeof texture === \"string\" && !assets.has(texture)) {\n issues.push({ path: `extensions.terrain.materials[${index}].texture`, message: `unknown asset \"${texture}\"` });\n }\n });\n }\n if (issues.length > 0) throw new MapDefinitionError(issues);\n return definition;\n}\n"],"mappings":";;;;;;AA2BA,SAAgB,GAAqB,MAAS,OAAsC;CAClF,OAAO;EAAE,GAAG;EAAO;CAAK;AAC1B;;AAGA,SAAgB,MAAgC,MAAc,OAAqC;CACjG,OAAO;EAAE,GAAG;EAAO;CAAK;AAC1B;;;;;;AAwBA,SAAgB,UAA2C,QAAW,OAA8C;CAClH,MAAM,aAAa,mBAAmB;EACpC,SAAA;EACA,IAAI,MAAM;EACV,MAAM,MAAM;EACZ,GAAI,MAAM,aAAa,KAAA,IAAY,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;EACnE,UAAU,MAAM,YAAY,CAAC;EAC7B,QAAQ,MAAM,UAAU,CAAC;EACzB,GAAI,MAAM,eAAe,KAAA,KAAa,MAAM,YAAY,KAAA,IACpD,EAAE,YAAY;GAAE,GAAI,MAAM,cAAc,CAAC;GAAI,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,YAAY,MAAM,IAAI,EAAE,IAAI,CAAC;EAAG,EAAE,IAC5H,CAAC;CACP,CAAC;CAED,MAAM,SAAqB,CAAC;CAC5B,WAAW,SAAS,SAAS,SAAS,UAAU;EAC9C,IAAI,CAAC,OAAO,IAAI,QAAQ,IAAI,GAC1B,OAAO,KAAK;GAAE,MAAM,YAAY,MAAM;GAAS,SAAS,kBAAkB,QAAQ,KAAK;EAAG,CAAC;CAE/F,CAAC;CACD,WAAW,OAAO,SAAS,OAAO,UAAU;EAC1C,IAAI,MAAM,YAAY,KAAA,KAAa,CAAC,OAAO,IAAI,MAAM,OAAO,GAC1D,OAAO,KAAK;GAAE,MAAM,UAAU,MAAM;GAAY,SAAS,kBAAkB,MAAM,QAAQ;EAAG,CAAC;CAEjG,CAAC;CACD,MAAM,UAAU,WAAW,YAAY;CACvC,MAAM,YAAY,MAAM,WAAW,cAAc,OAAO,IAAI,QAAQ,YAAY,KAAA;CAChF,IAAI,MAAM,QAAQ,SAAS,GACzB,UAAU,SAAS,UAAU,UAAU;EACrC,MAAM,UAAU,cAAc,QAAQ,IAAI,SAAS,UAAU,KAAA;EAC7D,IAAI,OAAO,YAAY,YAAY,CAAC,OAAO,IAAI,OAAO,GACpD,OAAO,KAAK;GAAE,MAAM,gCAAgC,MAAM;GAAY,SAAS,kBAAkB,QAAQ;EAAG,CAAC;CAEjH,CAAC;CAEH,IAAI,OAAO,SAAS,GAAG,MAAM,IAAI,mBAAmB,MAAM;CAC1D,OAAO;AACT"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { JsonObject, JsonValue } from './json';
|
|
2
|
+
export declare const MAP_DEFINITION_VERSION: 1;
|
|
3
|
+
/** Depth of an element. Reserved from the start so that adding it never reopens the format. */
|
|
4
|
+
export interface MapDepth {
|
|
5
|
+
/** Explicit sort order, on top of y-sorting. */
|
|
6
|
+
readonly z?: number;
|
|
7
|
+
/** Y offset (in pixels, from the element origin) used as the sort anchor. */
|
|
8
|
+
readonly anchorY?: number;
|
|
9
|
+
/** Height of the element in pixels, for shadows and occlusion. */
|
|
10
|
+
readonly height?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface MapElement {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
/** Asset reference (see `AssetResolver`). */
|
|
15
|
+
readonly kind: string;
|
|
16
|
+
/** World position, in pixels. */
|
|
17
|
+
readonly x: number;
|
|
18
|
+
readonly y: number;
|
|
19
|
+
readonly depth?: MapDepth;
|
|
20
|
+
/** Draw scale: one number for both axes, or `[x, y]`. Defaults to 1. */
|
|
21
|
+
readonly scale?: MapScale;
|
|
22
|
+
/** Per-element state that belongs to the map's persistent truth. Omitted when empty. */
|
|
23
|
+
readonly data?: JsonObject;
|
|
24
|
+
}
|
|
25
|
+
export type MapScale = number | readonly [x: number, y: number];
|
|
26
|
+
export interface MapEvent {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly kind: string;
|
|
29
|
+
/** World position, in pixels. */
|
|
30
|
+
readonly x: number;
|
|
31
|
+
readonly y: number;
|
|
32
|
+
readonly graphic?: string;
|
|
33
|
+
readonly data?: JsonObject;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Neutral description of a map. It knows nothing about projects, bundles or APIs,
|
|
37
|
+
* and is always JSON-serializable.
|
|
38
|
+
*
|
|
39
|
+
* The world is described in pixels. A tile grid is a feature, not the base.
|
|
40
|
+
*/
|
|
41
|
+
export interface MapDefinition {
|
|
42
|
+
readonly version: typeof MAP_DEFINITION_VERSION;
|
|
43
|
+
readonly id: string;
|
|
44
|
+
/** World size, in pixels. */
|
|
45
|
+
readonly size: readonly [width: number, height: number];
|
|
46
|
+
/** Optional grid size, used by features that need a grid. */
|
|
47
|
+
readonly tileSize?: number;
|
|
48
|
+
readonly elements: readonly MapElement[];
|
|
49
|
+
readonly events: readonly MapEvent[];
|
|
50
|
+
/** Data owned by features, keyed by feature id. Unknown keys are preserved. */
|
|
51
|
+
readonly extensions?: {
|
|
52
|
+
readonly [featureId: string]: JsonValue;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Current state of a map held by a model. It has the same shape as a definition. */
|
|
56
|
+
export type MapState = MapDefinition;
|
|
57
|
+
//# sourceMappingURL=definition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/model/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEpD,eAAO,MAAM,sBAAsB,EAAG,CAAU,CAAC;AAEjD,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IACvB,gDAAgD;IAChD,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC1B,wFAAwF;IACxF,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;AAEhE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,sBAAsB,CAAC;IAChD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACxD,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;IACrC,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,CAAC,EAAE;QAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;CACnE;AAED,qFAAqF;AACrF,MAAM,MAAM,QAAQ,GAAG,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition.js","names":[],"sources":["../../src/model/definition.ts"],"sourcesContent":["import type { JsonObject, JsonValue } from \"./json\";\n\nexport const MAP_DEFINITION_VERSION = 1 as const;\n\n/** Depth of an element. Reserved from the start so that adding it never reopens the format. */\nexport interface MapDepth {\n /** Explicit sort order, on top of y-sorting. */\n readonly z?: number;\n /** Y offset (in pixels, from the element origin) used as the sort anchor. */\n readonly anchorY?: number;\n /** Height of the element in pixels, for shadows and occlusion. */\n readonly height?: number;\n}\n\nexport interface MapElement {\n readonly id: string;\n /** Asset reference (see `AssetResolver`). */\n readonly kind: string;\n /** World position, in pixels. */\n readonly x: number;\n readonly y: number;\n readonly depth?: MapDepth;\n /** Draw scale: one number for both axes, or `[x, y]`. Defaults to 1. */\n readonly scale?: MapScale;\n /** Per-element state that belongs to the map's persistent truth. Omitted when empty. */\n readonly data?: JsonObject;\n}\n\nexport type MapScale = number | readonly [x: number, y: number];\n\nexport interface MapEvent {\n readonly id: string;\n readonly kind: string;\n /** World position, in pixels. */\n readonly x: number;\n readonly y: number;\n readonly graphic?: string;\n readonly data?: JsonObject;\n}\n\n/**\n * Neutral description of a map. It knows nothing about projects, bundles or APIs,\n * and is always JSON-serializable.\n *\n * The world is described in pixels. A tile grid is a feature, not the base.\n */\nexport interface MapDefinition {\n readonly version: typeof MAP_DEFINITION_VERSION;\n readonly id: string;\n /** World size, in pixels. */\n readonly size: readonly [width: number, height: number];\n /** Optional grid size, used by features that need a grid. */\n readonly tileSize?: number;\n readonly elements: readonly MapElement[];\n readonly events: readonly MapEvent[];\n /** Data owned by features, keyed by feature id. Unknown keys are preserved. */\n readonly extensions?: { readonly [featureId: string]: JsonValue };\n}\n\n/** Current state of a map held by a model. It has the same shape as a definition. */\nexport type MapState = MapDefinition;\n"],"mappings":";AAEA,IAAa,yBAAyB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface MapIssue {
|
|
2
|
+
/** Location of the problem, e.g. `elements[2].x`. */
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly message: string;
|
|
5
|
+
}
|
|
6
|
+
/** Thrown when a map description is invalid. */
|
|
7
|
+
export declare class MapDefinitionError extends Error {
|
|
8
|
+
readonly issues: readonly MapIssue[];
|
|
9
|
+
constructor(issues: readonly MapIssue[]);
|
|
10
|
+
}
|
|
11
|
+
export type KnownRejectionCode = "unknown-patch" | "invalid-patch" | "not-found" | "duplicate-id" | "stale-revision" | "revision-gap";
|
|
12
|
+
/** Open on purpose: pipeline steps add their own reasons (`"not-enough-wood"`). */
|
|
13
|
+
export type RejectionCode = KnownRejectionCode | (string & {});
|
|
14
|
+
/** Structured reason why a change was not applied. Normal control flow, never thrown. */
|
|
15
|
+
export interface MapRejection {
|
|
16
|
+
readonly code: RejectionCode;
|
|
17
|
+
readonly message: string;
|
|
18
|
+
/** Index, in the submitted batch, of the patch that was rejected. */
|
|
19
|
+
readonly patchIndex?: number;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/model/errors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,gDAAgD;AAChD,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;gBAEzB,MAAM,EAAE,SAAS,QAAQ,EAAE;CAOxC;AAED,MAAM,MAAM,kBAAkB,GAC1B,eAAe,GACf,eAAe,GACf,WAAW,GACX,cAAc,GACd,gBAAgB,GAChB,cAAc,CAAC;AAEnB,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE/D,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/model/errors.ts
|
|
2
|
+
/** Thrown when a map description is invalid. */
|
|
3
|
+
var MapDefinitionError = class extends Error {
|
|
4
|
+
issues;
|
|
5
|
+
constructor(issues) {
|
|
6
|
+
super(`Invalid map definition:\n${issues.map((issue) => ` - ${issue.path || "(root)"}: ${issue.message}`).join("\n")}`);
|
|
7
|
+
this.name = "MapDefinitionError";
|
|
8
|
+
this.issues = issues;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { MapDefinitionError };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../src/model/errors.ts"],"sourcesContent":["export interface MapIssue {\n /** Location of the problem, e.g. `elements[2].x`. */\n readonly path: string;\n readonly message: string;\n}\n\n/** Thrown when a map description is invalid. */\nexport class MapDefinitionError extends Error {\n readonly issues: readonly MapIssue[];\n\n constructor(issues: readonly MapIssue[]) {\n super(\n `Invalid map definition:\\n${issues.map((issue) => ` - ${issue.path || \"(root)\"}: ${issue.message}`).join(\"\\n\")}`,\n );\n this.name = \"MapDefinitionError\";\n this.issues = issues;\n }\n}\n\nexport type KnownRejectionCode =\n | \"unknown-patch\"\n | \"invalid-patch\"\n | \"not-found\"\n | \"duplicate-id\"\n | \"stale-revision\"\n | \"revision-gap\";\n\n/** Open on purpose: pipeline steps add their own reasons (`\"not-enough-wood\"`). */\nexport type RejectionCode = KnownRejectionCode | (string & {});\n\n/** Structured reason why a change was not applied. Normal control flow, never thrown. */\nexport interface MapRejection {\n readonly code: RejectionCode;\n readonly message: string;\n /** Index, in the submitted batch, of the patch that was rejected. */\n readonly patchIndex?: number;\n}\n"],"mappings":";;AAOA,IAAa,qBAAb,cAAwC,MAAM;CAC5C;CAEA,YAAY,QAA6B;EACvC,MACE,4BAA4B,OAAO,KAAK,UAAU,OAAO,MAAM,QAAQ,SAAS,IAAI,MAAM,SAAS,CAAC,CAAC,KAAK,IAAI,GAChH;EACA,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;AACF"}
|