mudlet-map-editor 0.18.0 → 0.20.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/dist-lib/components/Toolbar.d.ts +10 -1
- package/dist-lib/components/panels/RoutePanel.d.ts +6 -0
- package/dist-lib/editor/effects/RouteEffect.d.ts +37 -0
- package/dist-lib/editor/pathfinding.d.ts +35 -0
- package/dist-lib/editor/plugin.d.ts +49 -1
- package/dist-lib/editor/roomSearch.d.ts +41 -0
- package/dist-lib/editor/store.d.ts +11 -0
- package/dist-lib/editor/tools.d.ts +5 -0
- package/dist-lib/editor/types.d.ts +7 -1
- package/dist-lib/i18n/locales/panels.en.d.ts +29 -0
- package/dist-lib/i18n/locales/search.en.d.ts +18 -1
- package/dist-lib/index.d.ts +1 -1
- package/dist-lib/index.js +4009 -3100
- package/dist-lib/styles.css +1 -1
- package/package.json +3 -1
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { ToolbarAction } from '../editor/plugin';
|
|
3
|
+
/** `logo` is the plugin override slot. `undefined` means no plugin claimed it
|
|
4
|
+
* (toolbar falls back to the built-in Mudlet logo); any other value — including
|
|
5
|
+
* `null` — is rendered as-is, so a plugin can explicitly hide the slot.
|
|
6
|
+
* `transformActions` is the composed plugin transform applied to the built-in
|
|
7
|
+
* file-action list (see EditorPlugin#toolbarActions). */
|
|
8
|
+
export declare function Toolbar({ title, logo, transformActions, onHelpClick, onLoadFromUrl, onSave, onSearchClick, onSettingsClick }: {
|
|
2
9
|
title?: string;
|
|
10
|
+
logo?: ReactNode;
|
|
11
|
+
transformActions?: (actions: ToolbarAction[]) => ToolbarAction[];
|
|
3
12
|
onHelpClick: () => void;
|
|
4
13
|
onLoadFromUrl: () => void;
|
|
5
14
|
onSave?: (bytes: Uint8Array) => void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Konva from 'konva';
|
|
2
|
+
import { type CoordinateTransform, type LiveEffect, type ViewportBounds } from 'mudlet-map-renderer';
|
|
3
|
+
import type { SceneHandle } from '../scene';
|
|
4
|
+
/**
|
|
5
|
+
* Draws the active route (store.route.summary.path) on top of the map: a glowing
|
|
6
|
+
* poly-line following the same geometry the renderer uses for paths (via
|
|
7
|
+
* computePathData), plus start/end rings and dots for up/down/in/out
|
|
8
|
+
* transitions. Only the portion of the path on the current area/z is drawn —
|
|
9
|
+
* computePathData filters by area/z and stubs cross-boundary hops, so a
|
|
10
|
+
* multi-area route still shows correctly as you switch areas.
|
|
11
|
+
*/
|
|
12
|
+
export declare class RouteEffect implements LiveEffect {
|
|
13
|
+
private readonly sceneRef;
|
|
14
|
+
private layer?;
|
|
15
|
+
private unsubscribe?;
|
|
16
|
+
private nodes;
|
|
17
|
+
/** Lines whose width must track zoom (node + width multiplier). */
|
|
18
|
+
private widthLines;
|
|
19
|
+
private rings;
|
|
20
|
+
private scale;
|
|
21
|
+
/** Last-drawn signature, so pointer-move store churn doesn't rebuild the path. */
|
|
22
|
+
private lastSummary;
|
|
23
|
+
private lastArea;
|
|
24
|
+
private lastZ;
|
|
25
|
+
private lastDataVersion;
|
|
26
|
+
constructor(sceneRef: {
|
|
27
|
+
current: SceneHandle | null;
|
|
28
|
+
});
|
|
29
|
+
attach(layer: Konva.Layer): void;
|
|
30
|
+
updateViewport(_bounds: ViewportBounds, scale: number, _transform: CoordinateTransform): void;
|
|
31
|
+
syncPositions(): void;
|
|
32
|
+
destroy(): void;
|
|
33
|
+
private clear;
|
|
34
|
+
private addLine;
|
|
35
|
+
private addRing;
|
|
36
|
+
private sync;
|
|
37
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type PathFindingAlgorithm } from 'mudlet-map-renderer';
|
|
2
|
+
import type { MudletMap } from '../mapIO';
|
|
3
|
+
import type { EditorMapReader } from './reader/EditorMapReader';
|
|
4
|
+
export type { PathFindingAlgorithm };
|
|
5
|
+
export type RouteStep = {
|
|
6
|
+
/** Room you leave from. */
|
|
7
|
+
fromId: number;
|
|
8
|
+
/** Room you arrive at. */
|
|
9
|
+
toId: number;
|
|
10
|
+
/** Speedwalk command — short cardinal (n, ne, up…) or the special-exit name. */
|
|
11
|
+
token: string;
|
|
12
|
+
/** Whether this hop is a cardinal exit or a named special exit. */
|
|
13
|
+
kind: 'cardinal' | 'special';
|
|
14
|
+
/** Edge weight A* / Dijkstra paid for this hop. */
|
|
15
|
+
weight: number;
|
|
16
|
+
};
|
|
17
|
+
export type RouteSummary = {
|
|
18
|
+
/** Room-id sequence start→end (length = steps + 1). */
|
|
19
|
+
path: number[];
|
|
20
|
+
steps: RouteStep[];
|
|
21
|
+
/** Sum of edge weights along the path. */
|
|
22
|
+
totalWeight: number;
|
|
23
|
+
/** Semicolon-joined command string, e.g. "n;n;ne;up". */
|
|
24
|
+
speedwalk: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Find the lowest-cost route between two rooms using the renderer's pathfinder
|
|
28
|
+
* (respects exit/room weights, locked exits, and locked special exits) and
|
|
29
|
+
* summarise it into a speedwalk string + per-hop steps. Returns null when either
|
|
30
|
+
* endpoint is missing or no route exists.
|
|
31
|
+
*
|
|
32
|
+
* A fresh PathFinder is built per call: it snapshots the graph in its constructor,
|
|
33
|
+
* and the editor mutates the map constantly, so reusing one would go stale.
|
|
34
|
+
*/
|
|
35
|
+
export declare function findRoute(reader: EditorMapReader, map: MudletMap, fromId: number, toId: number, algorithm?: PathFindingAlgorithm): RouteSummary | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
1
|
+
import type { CSSProperties, ReactNode } from 'react';
|
|
2
2
|
import type { MudletMap, MudletRoom } from '../mapIO';
|
|
3
3
|
import type { SwatchSet } from './types';
|
|
4
4
|
import type { SceneHandle } from './scene';
|
|
@@ -31,6 +31,38 @@ export interface RoomPanelSection {
|
|
|
31
31
|
id: string;
|
|
32
32
|
render(props: RoomSectionProps): ReactNode;
|
|
33
33
|
}
|
|
34
|
+
/** A button in the toolbar header row's file-action group. Plugins reshape the
|
|
35
|
+
* list via `toolbarActions` — filter to hide built-ins, map to override
|
|
36
|
+
* callbacks/labels, or push to add new entries. Built-in ids are 'new',
|
|
37
|
+
* 'load', 'loadUrl', and 'save'. */
|
|
38
|
+
export interface ToolbarAction {
|
|
39
|
+
/** Stable id; plugins target a specific action by matching on this. */
|
|
40
|
+
id: string;
|
|
41
|
+
/** Tooltip text shown on hover. */
|
|
42
|
+
title?: string;
|
|
43
|
+
/** Button contents — typically an SVG icon, but any node works. */
|
|
44
|
+
icon?: ReactNode;
|
|
45
|
+
/** Click handler. Ignored when `filePicker` is set. */
|
|
46
|
+
onClick?: () => void;
|
|
47
|
+
/** When set, the action renders as a `<label>` wrapping a hidden `<input
|
|
48
|
+
* type="file">`; clicking opens the OS file picker and the chosen file is
|
|
49
|
+
* passed to `onFile`. Used by the built-in "load .dat" entry. */
|
|
50
|
+
filePicker?: {
|
|
51
|
+
accept: string;
|
|
52
|
+
onFile: (file: File) => void;
|
|
53
|
+
};
|
|
54
|
+
/** Disable the button (greys out + ignores clicks). */
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
/** Overlay node rendered on top of the button — used by the built-in
|
|
57
|
+
* "save" entry to draw the dirty-marker asterisk. */
|
|
58
|
+
badge?: ReactNode;
|
|
59
|
+
/** Inline style for the button/label root. */
|
|
60
|
+
style?: CSSProperties;
|
|
61
|
+
/** Escape hatch: when set, every other field except `id` is ignored and
|
|
62
|
+
* this node is rendered in place. Use for controls that aren't a single
|
|
63
|
+
* button (e.g. a dropdown). */
|
|
64
|
+
render?: () => ReactNode;
|
|
65
|
+
}
|
|
34
66
|
export interface EditorPlugin {
|
|
35
67
|
/** Stable identifier used to namespace plugin warning ack keys. Defaults to array index if omitted. */
|
|
36
68
|
id?: string;
|
|
@@ -39,6 +71,22 @@ export interface EditorPlugin {
|
|
|
39
71
|
onMapClosed?(): void;
|
|
40
72
|
onMapSave?(bytes: Uint8Array): void;
|
|
41
73
|
renderOverlay?(): ReactNode;
|
|
74
|
+
/** Replace the toolbar logo. The first plugin that defines this hook claims
|
|
75
|
+
* the slot — its return value is rendered as-is (including `null`, which
|
|
76
|
+
* hides the logo entirely). When no plugin defines it, the built-in Mudlet
|
|
77
|
+
* logo appears. */
|
|
78
|
+
renderLogo?(): ReactNode;
|
|
79
|
+
/** Reshape the toolbar's file-action button list. The hook receives the
|
|
80
|
+
* current list (built-ins first, then any earlier-plugin additions) and
|
|
81
|
+
* returns a new list. Typical uses:
|
|
82
|
+
* - **Hide** a built-in: `actions.filter(a => a.id !== 'loadUrl')`
|
|
83
|
+
* - **Replace a callback**: `actions.map(a => a.id === 'save'
|
|
84
|
+
* ? { ...a, onClick: mySave } : a)` (keeps the button visuals,
|
|
85
|
+
* swaps the behaviour — onMapSave still fires when the editor
|
|
86
|
+
* serialises the map elsewhere)
|
|
87
|
+
* - **Add** a custom button: `[...actions, { id, title, icon, onClick }]`
|
|
88
|
+
* Plugin transforms are applied in plugin order. */
|
|
89
|
+
toolbarActions?(actions: ToolbarAction[]): ToolbarAction[];
|
|
42
90
|
sidebarTabs?(): SidebarTab[];
|
|
43
91
|
swatchSets?(): SwatchSet[];
|
|
44
92
|
/** Contribute additional sections rendered at the bottom of the room selection panel. */
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { MudletMap, MudletRoom } from '../mapIO';
|
|
2
|
+
/**
|
|
3
|
+
* Structured room-search query language used by the Search panel.
|
|
4
|
+
*
|
|
5
|
+
* A query is a whitespace-separated list of tokens. Each token is either:
|
|
6
|
+
* - a structured filter `key:value` (e.g. `env:3`, `weight:>5`, `stubs:>0`,
|
|
7
|
+
* `exits:1`, `door:yes`, `locked:no`), optionally negated with a leading
|
|
8
|
+
* `-` or `!` (e.g. `-door:yes`); or
|
|
9
|
+
* - free text, matched against room name / id / userData (legacy behaviour).
|
|
10
|
+
*
|
|
11
|
+
* All structured filters are ANDed together; free-text tokens are joined with a
|
|
12
|
+
* space and matched as a single substring. A token is treated as a filter only
|
|
13
|
+
* when the part before the first colon is a recognised key — anything else
|
|
14
|
+
* (including URLs and colon-bearing free text) falls through to free text, so
|
|
15
|
+
* filters never produce surprising "unknown" errors for ordinary searches.
|
|
16
|
+
*/
|
|
17
|
+
export interface RoomMatchContext {
|
|
18
|
+
room: MudletRoom;
|
|
19
|
+
id: number;
|
|
20
|
+
map: MudletMap;
|
|
21
|
+
}
|
|
22
|
+
export type RoomPredicate = (ctx: RoomMatchContext) => boolean;
|
|
23
|
+
export interface ParsedQuery {
|
|
24
|
+
/** Free-text portion, lowercased; '' when the query is filters-only. */
|
|
25
|
+
text: string;
|
|
26
|
+
/** Structured predicates; a room must satisfy all of them. */
|
|
27
|
+
predicates: RoomPredicate[];
|
|
28
|
+
/** Canonical keys of the active filters, in first-seen order (for result summaries). */
|
|
29
|
+
filterKeys: string[];
|
|
30
|
+
/** The original token of the first malformed filter, or null. */
|
|
31
|
+
error: string | null;
|
|
32
|
+
}
|
|
33
|
+
/** Count of real outgoing exits: cardinal/vertical exits plus special exits. */
|
|
34
|
+
export declare function exitCount(room: MudletRoom): number;
|
|
35
|
+
export declare function parseRoomQuery(input: string): ParsedQuery;
|
|
36
|
+
/**
|
|
37
|
+
* Build a compact, human-readable summary of the room values relevant to the
|
|
38
|
+
* active filter keys — shown as the result row's reason when no free text
|
|
39
|
+
* supplied a match reason.
|
|
40
|
+
*/
|
|
41
|
+
export declare function describeRoom(ctx: RoomMatchContext, keys: string[]): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MudletMap, MudletRoom } from '../mapIO';
|
|
2
2
|
import type { Command, HitItem, HoverTarget, LoadedMap, Pending, Selection, SwatchSet, ToolId } from './types';
|
|
3
3
|
import type { MapWarning } from './warnings';
|
|
4
|
+
import type { PathFindingAlgorithm, RouteSummary } from './pathfinding';
|
|
4
5
|
export type RoomClipboard = {
|
|
5
6
|
/** Rooms captured at copy time; origId preserved for internal-exit remap. */
|
|
6
7
|
rooms: Array<{
|
|
@@ -26,6 +27,14 @@ export type SpreadShrinkState = {
|
|
|
26
27
|
centerMode: 'centroid' | 'anchor';
|
|
27
28
|
anchorRoomId: number | null;
|
|
28
29
|
};
|
|
30
|
+
/** Route-finder (speedwalk) panel state. `summary.path` is what RouteEffect draws. */
|
|
31
|
+
export type RouteState = {
|
|
32
|
+
fromId: number | null;
|
|
33
|
+
toId: number | null;
|
|
34
|
+
algorithm: PathFindingAlgorithm;
|
|
35
|
+
summary: RouteSummary | null;
|
|
36
|
+
status: 'idle' | 'found' | 'noPath' | 'sameRoom' | 'missing';
|
|
37
|
+
};
|
|
29
38
|
export interface EditorState {
|
|
30
39
|
map: MudletMap | null;
|
|
31
40
|
loaded: LoadedMap | null;
|
|
@@ -90,6 +99,8 @@ export interface EditorState {
|
|
|
90
99
|
swatchPaletteOpen: boolean;
|
|
91
100
|
sessionId: string | null;
|
|
92
101
|
spreadShrink: SpreadShrinkState | null;
|
|
102
|
+
/** Route-finder panel + the path RouteEffect renders. */
|
|
103
|
+
route: RouteState;
|
|
93
104
|
warningAckVersion: number;
|
|
94
105
|
/** Cached map warnings; recomputed in App after each command lands. */
|
|
95
106
|
warnings: MapWarning[];
|
|
@@ -19,6 +19,11 @@ export interface Tool {
|
|
|
19
19
|
}
|
|
20
20
|
type PickSpecialExitCb = (roomId: number) => void;
|
|
21
21
|
export declare function registerSpecialExitPickCb(cb: PickSpecialExitCb | null): void;
|
|
22
|
+
/** Generic room-pick callback for `pending.kind === 'pickRoom'`. Receives the
|
|
23
|
+
* clicked room id; the registrant reads `pending.target` to know which slot
|
|
24
|
+
* asked. Unlike the special-exit pick, this does NOT reset the selection. */
|
|
25
|
+
type PickRoomCb = (roomId: number) => void;
|
|
26
|
+
export declare function registerRoomPickCb(cb: PickRoomCb | null): void;
|
|
22
27
|
/**
|
|
23
28
|
* Combo tool: click a room to select it, drag to move it, click empty to clear selection.
|
|
24
29
|
* Drag vs click is detected naturally — a drag only "moves" when the snapped cursor cell
|
|
@@ -199,6 +199,12 @@ export type PendingPickSpecialExit = {
|
|
|
199
199
|
kind: 'pickSpecialExit';
|
|
200
200
|
fromId: number;
|
|
201
201
|
};
|
|
202
|
+
/** Generic "click a room to fill a field" pick. `target` identifies which
|
|
203
|
+
* field/slot requested the pick so the requester can route the result. */
|
|
204
|
+
export type PendingPickRoom = {
|
|
205
|
+
kind: 'pickRoom';
|
|
206
|
+
target: string;
|
|
207
|
+
};
|
|
202
208
|
export type LabelResizeHandle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w';
|
|
203
209
|
export type PendingLabelDrag = {
|
|
204
210
|
kind: 'labelDrag';
|
|
@@ -252,7 +258,7 @@ export type PendingPaint = {
|
|
|
252
258
|
export type PendingPickSwatch = {
|
|
253
259
|
kind: 'pickSwatch';
|
|
254
260
|
};
|
|
255
|
-
export type Pending = PendingDrag | PendingConnect | PendingCustomLine | PendingCustomLinePoint | PendingPickExit | PendingPickSpecialExit | PendingMarquee | PendingLabelDrag | PendingLabelRect | PendingLabelResize | PendingPaint | PendingPickSwatch | null;
|
|
261
|
+
export type Pending = PendingDrag | PendingConnect | PendingCustomLine | PendingCustomLinePoint | PendingPickExit | PendingPickSpecialExit | PendingPickRoom | PendingMarquee | PendingLabelDrag | PendingLabelRect | PendingLabelResize | PendingPaint | PendingPickSwatch | null;
|
|
256
262
|
export type RoomSnapshot = MudletRoom;
|
|
257
263
|
export type CustomLineSnapshot = {
|
|
258
264
|
points: [number, number][];
|
|
@@ -8,6 +8,7 @@ export declare const panelsEn: {
|
|
|
8
8
|
readonly historyShort: "Hist";
|
|
9
9
|
readonly map: "Map";
|
|
10
10
|
readonly script: "Script";
|
|
11
|
+
readonly route: "Route";
|
|
11
12
|
readonly expandPanel: "Expand panel";
|
|
12
13
|
readonly restorePanel: "Restore panel";
|
|
13
14
|
readonly collapsePanel: "Collapse panel";
|
|
@@ -335,6 +336,34 @@ export declare const panelsEn: {
|
|
|
335
336
|
readonly label: "Click a label to select and edit its properties.";
|
|
336
337
|
};
|
|
337
338
|
};
|
|
339
|
+
readonly route: {
|
|
340
|
+
readonly title: "Route / speedwalk";
|
|
341
|
+
readonly description: "Find the lowest-cost path between two rooms using room & exit weights, honouring locked exits and rooms.";
|
|
342
|
+
readonly from: "From";
|
|
343
|
+
readonly to: "To";
|
|
344
|
+
readonly pickFrom: "Pick start room from map";
|
|
345
|
+
readonly pickTo: "Pick destination room from map";
|
|
346
|
+
readonly pickCancel: "Click a room on the map (Esc to cancel)";
|
|
347
|
+
readonly swap: "Swap";
|
|
348
|
+
readonly algorithm: "Algorithm";
|
|
349
|
+
readonly astar: "A* (fast)";
|
|
350
|
+
readonly dijkstra: "Dijkstra";
|
|
351
|
+
readonly find: "Find route";
|
|
352
|
+
readonly clear: "Clear";
|
|
353
|
+
readonly rooms: "{{count}} rooms";
|
|
354
|
+
readonly steps: "{{count}} steps";
|
|
355
|
+
readonly cost: "Cost {{weight}}";
|
|
356
|
+
readonly speedwalk: "Speedwalk";
|
|
357
|
+
readonly copy: "Copy";
|
|
358
|
+
readonly copied: "Copied!";
|
|
359
|
+
readonly goStart: "Go to start";
|
|
360
|
+
readonly goEnd: "Go to end";
|
|
361
|
+
readonly noPath: "No route found between #{{from}} and #{{to}}.";
|
|
362
|
+
readonly sameRoom: "Start and destination are the same room.";
|
|
363
|
+
readonly missing: "Enter a valid start and destination room ID.";
|
|
364
|
+
readonly pickHint: "Tip: click “⊕” next to From or To, then click a room on the map.";
|
|
365
|
+
readonly stepList: "Steps";
|
|
366
|
+
};
|
|
338
367
|
};
|
|
339
368
|
export type PanelsLocale = {
|
|
340
369
|
[K in keyof typeof panelsEn]: typeof panelsEn[K] extends string ? string : {
|
|
@@ -2,13 +2,30 @@ export declare const searchEn: {
|
|
|
2
2
|
readonly tabRooms: "Rooms";
|
|
3
3
|
readonly tabLabels: "Labels";
|
|
4
4
|
readonly closeTitle: "Close (Esc)";
|
|
5
|
-
readonly placeholderRooms: "name, ID, or
|
|
5
|
+
readonly placeholderRooms: "name, ID, or filters like env:3 stubs:>0… (Tab to switch)";
|
|
6
6
|
readonly placeholderLabels: "label text… (Tab to switch)";
|
|
7
7
|
readonly clearTitle: "Clear";
|
|
8
8
|
readonly noMatches: "No matches";
|
|
9
9
|
readonly tooManyResults: "Showing first 100 results — refine your query";
|
|
10
10
|
readonly unnamed: "unnamed";
|
|
11
11
|
readonly emptyLabel: "empty label";
|
|
12
|
+
readonly filtersToggle: "Filters";
|
|
13
|
+
readonly filtersHelpTitle: "Filter syntax";
|
|
14
|
+
readonly filtersHelpHint: "Combine filters and free text. Use >, <, ranges (1-5), or yes/no. Prefix with - to negate.";
|
|
15
|
+
readonly invalidFilter: "Invalid filter: {{token}}";
|
|
16
|
+
readonly helpEnv: "environment id";
|
|
17
|
+
readonly helpWeight: "room weight above 5";
|
|
18
|
+
readonly helpExits: "exactly one exit";
|
|
19
|
+
readonly helpDeadend: "dead-ends (single exit)";
|
|
20
|
+
readonly helpStubs: "has exit stubs";
|
|
21
|
+
readonly helpDoor: "has a door";
|
|
22
|
+
readonly helpLocked: "not locked";
|
|
23
|
+
readonly helpSpecial: "has special exits";
|
|
24
|
+
readonly helpCustomLine: "has custom lines";
|
|
25
|
+
readonly helpSymbol: "has a symbol";
|
|
26
|
+
readonly helpArea: "area name contains";
|
|
27
|
+
readonly helpZ: "on z-level 0";
|
|
28
|
+
readonly helpNegate: "negate: no door";
|
|
12
29
|
};
|
|
13
30
|
export type SearchLocale = {
|
|
14
31
|
[K in keyof typeof searchEn]: string;
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import './styles.css';
|
|
2
2
|
export { default as App } from './App';
|
|
3
|
-
export type { EditorPlugin, SidebarTab, RoomPanelSection, RoomSectionProps, PluginCheckResult } from './editor/plugin';
|
|
3
|
+
export type { EditorPlugin, SidebarTab, RoomPanelSection, RoomSectionProps, PluginCheckResult, ToolbarAction } from './editor/plugin';
|
|
4
4
|
export type { MudletMap, MudletRoom, MudletColor } from './mapIO';
|
|
5
5
|
export type { SwatchSet, Swatch } from './editor/types';
|
|
6
6
|
export { loadUrlIntoStore } from './editor/loadFile';
|