@kubohiroya/turbowarp-plus-editor-layout 0.1.7 → 0.1.9
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/document-workspace.d.ts +1 -0
- package/dist/sprite-asset-pane.d.ts +4 -0
- package/dist/sprite-asset-pane.js +16 -12
- package/dist/sprite-editor-toolbar.d.ts +2 -1
- package/dist/sprite-editor-toolbar.js +3 -3
- package/dist/sprite-tabs.d.ts +7 -1
- package/dist/sprite-tabs.js +31 -11
- package/dist/standard-layout.d.ts +2 -1
- package/dist/standard-layout.js +6 -5
- package/dist/targets.d.ts +2 -1
- package/dist/targets.js +8 -2
- package/dist/workspace-layout.d.ts +4 -0
- package/dist/workspace-layout.js +27 -0
- package/package.json +1 -1
- package/src/document-workspace.ts +1 -0
- package/src/editor.css +11 -0
- package/src/sprite-asset-pane.tsx +8 -1
- package/src/sprite-editor-toolbar.tsx +4 -3
- package/src/sprite-tabs.ts +32 -11
- package/src/standard-layout.ts +7 -6
- package/src/targets.ts +7 -2
- package/src/workspace-layout.ts +25 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { type SpritePropertiesPanelProps } from './sprite-properties.tsx';
|
|
2
3
|
import type { SpriteAssetPreviewItem } from './sprite-tabs.ts';
|
|
3
4
|
interface NativeAssetPanelProps {
|
|
4
5
|
children?: React.ReactNode;
|
|
@@ -75,6 +76,9 @@ export interface SpriteMediaPaneProps extends PaneProps {
|
|
|
75
76
|
export interface SpriteVariablePaneProps extends PaneProps {
|
|
76
77
|
kind: 'variables';
|
|
77
78
|
variables: readonly SpriteVariable[];
|
|
79
|
+
properties?: SpritePropertiesPanelProps['values'];
|
|
80
|
+
targetId?: string;
|
|
81
|
+
onWriteProperties?: SpritePropertiesPanelProps['onChange'];
|
|
78
82
|
onWrite(id: string, value: SpriteVariable['value']): void;
|
|
79
83
|
}
|
|
80
84
|
export type SpriteAssetPaneProps = SpriteMediaPaneProps | SpriteVariablePaneProps;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { SpritePropertiesPanel } from "./sprite-properties.js";
|
|
2
3
|
const noop = () => { };
|
|
3
4
|
function NativeAssetPanelSurface({ render, panel, state, previewSelector, scrollTop }) {
|
|
4
5
|
const list = React.useRef(null);
|
|
@@ -133,19 +134,22 @@ function VariableField({ variable, editable, onWrite }) {
|
|
|
133
134
|
finish(event.key === 'Enter');
|
|
134
135
|
} });
|
|
135
136
|
}
|
|
136
|
-
function VariablePane({ variables, editable, onWrite, locale }) {
|
|
137
|
+
function VariablePane({ variables, editable, onWrite, locale, properties, targetId, onWriteProperties }) {
|
|
137
138
|
const labels = labelsFor(locale);
|
|
138
|
-
return React.createElement("section", { className: "tw-sprite-variables", "aria-label": labels.variables },
|
|
139
|
-
|
|
140
|
-
React.createElement(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
139
|
+
return React.createElement("section", { className: "tw-sprite-variables", "aria-label": labels.variables },
|
|
140
|
+
properties && targetId && onWriteProperties &&
|
|
141
|
+
React.createElement(SpritePropertiesPanel, { targetId: targetId, values: properties, locale: locale, disabled: !editable, onChange: onWriteProperties }),
|
|
142
|
+
variables.length ? React.createElement("table", { className: "tw-sprite-variables__table" },
|
|
143
|
+
React.createElement("thead", null,
|
|
144
|
+
React.createElement("tr", null,
|
|
145
|
+
React.createElement("th", { scope: "col" }, labels.name),
|
|
146
|
+
React.createElement("th", { scope: "col" }, labels.value))),
|
|
147
|
+
React.createElement("tbody", null, variables.map(variable => React.createElement("tr", { key: variable.id },
|
|
148
|
+
React.createElement("th", { scope: "row" },
|
|
149
|
+
variable.name,
|
|
150
|
+
variable.scope && React.createElement("span", { className: "tw-sprite-variables__scope" }, labels[variable.scope])),
|
|
151
|
+
React.createElement("td", null,
|
|
152
|
+
React.createElement(VariableField, { variable: variable, editable: editable, onWrite: onWrite })))))) : React.createElement("p", null, labels.emptyVariables));
|
|
149
153
|
}
|
|
150
154
|
export function SpriteAssetPane(props) {
|
|
151
155
|
return props.kind === 'variables' ? React.createElement(VariablePane, { ...props }) : React.createElement(MediaPane, { ...props });
|
|
@@ -19,7 +19,8 @@ interface SpriteEditorToolbarProps {
|
|
|
19
19
|
activeGroup?: boolean;
|
|
20
20
|
search?: SpriteEditorSearch;
|
|
21
21
|
availableSections?: readonly SpriteEditorSection[];
|
|
22
|
+
propertiesInVariables?: boolean;
|
|
22
23
|
actions?: React.ReactNode;
|
|
23
24
|
}
|
|
24
|
-
export declare function SpriteEditorToolbar({ viewId, locale, section, onSectionChange, isStage, activeGroup, search, availableSections, actions }: SpriteEditorToolbarProps): React.JSX.Element;
|
|
25
|
+
export declare function SpriteEditorToolbar({ viewId, locale, section, onSectionChange, isStage, activeGroup, search, availableSections, propertiesInVariables, actions }: SpriteEditorToolbarProps): React.JSX.Element;
|
|
25
26
|
export {};
|
|
@@ -49,10 +49,10 @@ function SearchBox({ viewId, section, japanese, search }) {
|
|
|
49
49
|
} })),
|
|
50
50
|
expanded && React.createElement("ul", { role: "listbox", id: listId, "aria-label": japanese ? '検索結果' : 'Search results' }, results.map((result, position) => React.createElement("li", { key: result.id, id: optionId(position), role: "option", "aria-selected": position === index, onPointerDown: event => event.preventDefault(), onClick: () => choose(result) }, result.label))));
|
|
51
51
|
}
|
|
52
|
-
export function SpriteEditorToolbar({ viewId, locale, section, onSectionChange, isStage, activeGroup, search, availableSections = spriteEditorSections, actions }) {
|
|
52
|
+
export function SpriteEditorToolbar({ viewId, locale, section, onSectionChange, isStage, activeGroup, search, availableSections = spriteEditorSections, propertiesInVariables = false, actions }) {
|
|
53
53
|
const japanese = /^ja(?:-|$)/i.test(locale);
|
|
54
|
-
const labels = japanese ? { code: 'コード', costumes: isStage ? '背景' : 'コスチューム', sounds: '音', variables: '変数' } :
|
|
55
|
-
{ code: 'Code', costumes: isStage ? 'Backdrops' : 'Costumes', sounds: 'Sounds', variables: 'Variables' };
|
|
54
|
+
const labels = japanese ? { code: 'コード', costumes: isStage ? '背景' : 'コスチューム', sounds: '音', variables: propertiesInVariables ? 'プロパティ・変数' : '変数' } :
|
|
55
|
+
{ code: 'Code', costumes: isStage ? 'Backdrops' : 'Costumes', sounds: 'Sounds', variables: propertiesInVariables ? 'Properties & Variables' : 'Variables' };
|
|
56
56
|
return React.createElement("div", { className: "tw-plus-sprite-toolbar", "data-active-group": activeGroup },
|
|
57
57
|
React.createElement("div", { className: "tw-plus-sprite-toolbar-sections", role: "tablist", "aria-label": japanese ? 'スプライトの編集内容' : 'Sprite editor sections' }, availableSections.map((id, index) => React.createElement("button", { key: id, type: "button", role: "tab", id: viewId + '-section-' + id, "aria-selected": section === id, tabIndex: section === id ? 0 : -1, "aria-controls": viewId + '-content', onClick: () => onSectionChange(id), onKeyDown: event => {
|
|
58
58
|
const direction = event.currentTarget.ownerDocument.dir === 'rtl' ? -1 : 1;
|
package/dist/sprite-tabs.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { NativeAssetPanel } from './sprite-asset-pane.tsx';
|
|
3
3
|
import type { NativeAssetPanelRenderer, NativeAssetSelector, SpriteAssetLayout } from './sprite-asset-pane.tsx';
|
|
4
4
|
import type { SpriteVariable } from './sprite-asset-pane.tsx';
|
|
5
|
+
import type { SpritePropertiesPanelProps } from './sprite-properties.tsx';
|
|
5
6
|
import type { SpriteSearchResult } from './sprite-editor-toolbar.tsx';
|
|
6
7
|
import type { DockviewStagePane } from './dockview-stage.ts';
|
|
7
8
|
export { getDocumentWorkspace } from './document-workspace.ts';
|
|
@@ -80,7 +81,7 @@ interface TabOptions<Snapshot = unknown> {
|
|
|
80
81
|
reopenLabel: string;
|
|
81
82
|
multiView?: boolean;
|
|
82
83
|
layoutStorage?: {
|
|
83
|
-
storage: Storage
|
|
84
|
+
storage: Pick<Storage, 'getItem' | 'setItem'>;
|
|
84
85
|
key: string;
|
|
85
86
|
isProjectLoading?(): boolean;
|
|
86
87
|
subscribeProject?(listener: () => void): (() => void) | undefined;
|
|
@@ -137,6 +138,7 @@ export declare function mountSpriteTabs<Snapshot>(root: HTMLElement, content: HT
|
|
|
137
138
|
setViewSection: (id: string, section: number) => boolean;
|
|
138
139
|
syncActiveSection: (section: number) => void;
|
|
139
140
|
refreshSurfaces: () => void;
|
|
141
|
+
rename(id: string): void;
|
|
140
142
|
selectAsset(id: string, kind: AssetKind, index: number): void;
|
|
141
143
|
currentView: () => SpriteTabView;
|
|
142
144
|
views: () => SpriteTabView[];
|
|
@@ -156,6 +158,8 @@ type SpriteTabsProps<Snapshot> = Omit<TabOptions<Snapshot>, 'activateSection' |
|
|
|
156
158
|
renderToolbar?(view: SpriteTabView, actions: ToolbarActions): React.ReactNode;
|
|
157
159
|
getAssetPreview?(targetId: string, kind: SpriteAssetKind): SpriteAssetPreviewData;
|
|
158
160
|
getVariables?(targetId: string): readonly SpriteVariable[];
|
|
161
|
+
getProperties?(targetId: string): SpritePropertiesPanelProps['values'];
|
|
162
|
+
writeProperties?: SpritePropertiesPanelProps['onChange'];
|
|
159
163
|
writeVariable?(targetId: string, id: string, value: SpriteVariable['value']): void;
|
|
160
164
|
getCodeSearchItems?(targetId: string): readonly SpriteSearchResult[];
|
|
161
165
|
openCodeSearchResult?(targetId: string, id: string): void;
|
|
@@ -175,6 +179,7 @@ export declare function useSpriteTabs<Snapshot>({ available, enabled, locale, ..
|
|
|
175
179
|
setViewSection: (id: string, section: number) => boolean;
|
|
176
180
|
syncActiveSection: (section: number) => void;
|
|
177
181
|
refreshSurfaces: () => void;
|
|
182
|
+
rename(id: string): void;
|
|
178
183
|
selectAsset(id: string, kind: AssetKind, index: number): void;
|
|
179
184
|
currentView: () => SpriteTabView;
|
|
180
185
|
views: () => SpriteTabView[];
|
|
@@ -186,6 +191,7 @@ export declare function useSpriteTabs<Snapshot>({ available, enabled, locale, ..
|
|
|
186
191
|
openLabel: string;
|
|
187
192
|
select: (id: string) => void;
|
|
188
193
|
open: (id: string) => void;
|
|
194
|
+
rename: (id: string) => void;
|
|
189
195
|
};
|
|
190
196
|
renderEditorTabRow: (children: React.ReactNode) => React.FunctionComponentElement<SpriteTabsProps<Snapshot>>;
|
|
191
197
|
renderEditorPanel: (index: number, children: React.ReactNode) => React.ReactPortal;
|
package/dist/sprite-tabs.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createSpriteLayoutMenu } from "./sprite-layout-menu.js";
|
|
|
5
5
|
import { NativeAssetPanel, SpriteAssetPane } from "./sprite-asset-pane.js";
|
|
6
6
|
import { SpriteEditorToolbar, spriteEditorSections } from "./sprite-editor-toolbar.js";
|
|
7
7
|
import { getDocumentWorkspace } from "./document-workspace.js";
|
|
8
|
-
import { readWorkspaceLayout } from "./workspace-layout.js";
|
|
8
|
+
import { readWorkspaceLayout, workspaceWithoutTransientPanels } from "./workspace-layout.js";
|
|
9
9
|
export { getDocumentWorkspace } from "./document-workspace.js";
|
|
10
10
|
export { useDockviewStage } from "./dockview-stage.js";
|
|
11
11
|
const variableSection = spriteEditorSections.indexOf('variables');
|
|
@@ -227,11 +227,9 @@ export function mountSpriteTabs(root, content, options) {
|
|
|
227
227
|
});
|
|
228
228
|
return { ...menu, init() { } };
|
|
229
229
|
}
|
|
230
|
-
|
|
231
|
-
return [...views.values()].some(view => view.target === target) && vm.runtime.targets.includes(target);
|
|
232
|
-
}
|
|
230
|
+
const canRename = (target) => target.isOriginal && !target.isStage && Boolean(vm.renameSprite) && vm.runtime.targets.includes(target);
|
|
233
231
|
function openRenameDialog(target) {
|
|
234
|
-
if (renameDialog || !
|
|
232
|
+
if (renameDialog || !canRename(target))
|
|
235
233
|
return;
|
|
236
234
|
const dialog = document.createElement('dialog');
|
|
237
235
|
dialog.className = 'tw-plus-sprite-rename';
|
|
@@ -259,7 +257,7 @@ export function mountSpriteTabs(root, content, options) {
|
|
|
259
257
|
if (renameDialog !== dialog)
|
|
260
258
|
return;
|
|
261
259
|
renameDialog = undefined;
|
|
262
|
-
if (dialog.returnValue === 'save' && input.value.trim() &&
|
|
260
|
+
if (dialog.returnValue === 'save' && input.value.trim() && canRename(target) &&
|
|
263
261
|
input.value !== target.getName())
|
|
264
262
|
vm.renameSprite?.(target.id, input.value);
|
|
265
263
|
}, { once: true });
|
|
@@ -562,9 +560,17 @@ export function mountSpriteTabs(root, content, options) {
|
|
|
562
560
|
}
|
|
563
561
|
if (dockview.getPanel(stagePanelId))
|
|
564
562
|
panels[stagePanelId] = { kind: 'stage' };
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
563
|
+
const transient = new Set();
|
|
564
|
+
for (const [id, { request }] of documents) {
|
|
565
|
+
if (request.transient)
|
|
566
|
+
transient.add(id);
|
|
567
|
+
else
|
|
568
|
+
panels[id] = { kind: 'document', ...request.reference };
|
|
569
|
+
}
|
|
570
|
+
const saved = workspaceWithoutTransientPanels(grid, activeGroup, transient, active?.id);
|
|
571
|
+
if (!saved)
|
|
572
|
+
return;
|
|
573
|
+
const snapshot = { version: 1, project: projectSignature(targets), ...saved,
|
|
568
574
|
editingView: active?.id, panels };
|
|
569
575
|
try {
|
|
570
576
|
persistence.storage.setItem(persistence.key, JSON.stringify(snapshot));
|
|
@@ -758,6 +764,11 @@ export function mountSpriteTabs(root, content, options) {
|
|
|
758
764
|
refresh();
|
|
759
765
|
return {
|
|
760
766
|
select, openStage, closeStage, split, closeSplit, activateView, setViewSection, syncActiveSection, refreshSurfaces,
|
|
767
|
+
rename(id) {
|
|
768
|
+
const target = vm.runtime.targets.find(item => item.id === id);
|
|
769
|
+
if (target)
|
|
770
|
+
openRenameDialog(target);
|
|
771
|
+
},
|
|
761
772
|
selectAsset(id, kind, index) {
|
|
762
773
|
if (active?.id === id && active.section === (kind === 'costumes' ? 1 : 2)) {
|
|
763
774
|
active.assets.selectors.get(kind)?.(index);
|
|
@@ -934,7 +945,7 @@ function SpriteTabs(props) {
|
|
|
934
945
|
const toolbar = props.renderToolbar?.(view, actions) ?? (props.getVariables &&
|
|
935
946
|
React.createElement(SpriteEditorToolbar, {
|
|
936
947
|
viewId: view.id, locale: props.locale, section: currentSection, isStage: view.isStage,
|
|
937
|
-
activeGroup: view.groupActive,
|
|
948
|
+
activeGroup: view.groupActive, propertiesInVariables: Boolean(props.getProperties),
|
|
938
949
|
actions: view.active && currentSection !== 'variables' ? props.activeTools : undefined,
|
|
939
950
|
onSectionChange: section => actions.setSection(spriteEditorSections.indexOf(section)),
|
|
940
951
|
search: {
|
|
@@ -969,6 +980,12 @@ function SpriteTabs(props) {
|
|
|
969
980
|
return createPortal(toolbar, element, view.id);
|
|
970
981
|
}), assetPreviewSlots.map(({ id, targetId, kind, element, editable, selected, renderLayout, scrollTop }) => createPortal(kind === 'variables' ? React.createElement(SpriteAssetPane, {
|
|
971
982
|
kind, locale: props.locale, editable, variables: props.getVariables?.(targetId) ?? [],
|
|
983
|
+
properties: props.getProperties?.(targetId),
|
|
984
|
+
targetId,
|
|
985
|
+
onWriteProperties(id, patch) {
|
|
986
|
+
props.writeProperties?.(id, patch);
|
|
987
|
+
workspace?.refreshSurfaces();
|
|
988
|
+
},
|
|
972
989
|
onWrite(variableId, value) {
|
|
973
990
|
props.writeVariable?.(targetId, variableId, value);
|
|
974
991
|
workspace?.refreshSurfaces();
|
|
@@ -998,7 +1015,10 @@ export function useSpriteTabs({ available, enabled, locale, ...props }) {
|
|
|
998
1015
|
return {
|
|
999
1016
|
controller,
|
|
1000
1017
|
assetSelections: enabled ? selection.assets : undefined,
|
|
1001
|
-
context: enabled ? {
|
|
1018
|
+
context: enabled ? {
|
|
1019
|
+
selectedId: selection.id, openLabel: japanese ? '開く' : 'Open', select, open: select,
|
|
1020
|
+
rename: props.multiView && props.getProperties ? (id) => controller.current?.rename(id) : undefined
|
|
1021
|
+
} : null,
|
|
1002
1022
|
renderEditorTabRow: available ? (children) => React.createElement((SpriteTabs), {
|
|
1003
1023
|
...props, enabled, locale, children, slots, onAttach: () => setReady(true),
|
|
1004
1024
|
selectTarget: (id, assets) => setSelection({ id, assets }),
|
|
@@ -55,8 +55,9 @@ export declare function createStandardLayout(React: any, { styles, targetStyles,
|
|
|
55
55
|
buttons: any;
|
|
56
56
|
children: any;
|
|
57
57
|
}) => any;
|
|
58
|
-
SpritePropertiesSection: ({ fallback, children }: {
|
|
58
|
+
SpritePropertiesSection: ({ fallback, children, inEditor }: {
|
|
59
59
|
fallback: any;
|
|
60
60
|
children: any;
|
|
61
|
+
inEditor?: boolean;
|
|
61
62
|
}) => any;
|
|
62
63
|
};
|
package/dist/standard-layout.js
CHANGED
|
@@ -236,14 +236,15 @@ export function createStandardLayout(React, { styles, targetStyles, context, Lay
|
|
|
236
236
|
const unique = buttons.filter(({ title }, index) => buttons.findIndex(button => button.title === title) === index);
|
|
237
237
|
return element(targets.BackdropFooter, { buttons: unique, label: isJapanese(locale) ? '背景を追加' : 'Add a backdrop' });
|
|
238
238
|
}
|
|
239
|
-
function SpritePropertiesSection({ fallback, children }) {
|
|
239
|
+
function SpritePropertiesSection({ fallback, children, inEditor = false }) {
|
|
240
240
|
const { plusLayout, locale } = useContext(context);
|
|
241
241
|
const [expanded, setExpanded] = useState(false);
|
|
242
242
|
const tools = useContext(targetTools);
|
|
243
|
-
return plusLayout ? element('div', { className: styles.spriteProperties }, element('
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
243
|
+
return plusLayout ? element('div', { className: styles.spriteProperties }, inEditor ? element('span', null, isJapanese(locale) ? 'スプライト' : 'Sprite') :
|
|
244
|
+
element('button', {
|
|
245
|
+
type: 'button', className: styles.spritePropertiesToggle, 'aria-expanded': expanded,
|
|
246
|
+
onClick: () => setExpanded(open => !open)
|
|
247
|
+
}, isJapanese(locale) ? 'スプライト' : 'Sprite'), element('div', { ref: tools?.[1] }), !inEditor && element('div', { className: styles.spritePropertiesContent, hidden: !expanded }, children)) : fallback;
|
|
247
248
|
}
|
|
248
249
|
function useSpriteListTools() {
|
|
249
250
|
const { locale, targetsPinned, setTargetsPinned } = useContext(context);
|
package/dist/targets.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export declare function createTargetList(React: any, styles: any): {
|
|
2
|
-
SpriteGrid: ({ className, compact, standard, stageTile, children, query }: {
|
|
2
|
+
SpriteGrid: ({ className, compact, standard, stageTile, children, query, onRename }: {
|
|
3
3
|
className: any;
|
|
4
4
|
compact: any;
|
|
5
5
|
standard: any;
|
|
6
6
|
stageTile: any;
|
|
7
7
|
children: any;
|
|
8
8
|
query?: string;
|
|
9
|
+
onRename: any;
|
|
9
10
|
}) => any;
|
|
10
11
|
BackdropTile: ({ url, label, selected, highlighted, containerRef, onClick, onMouseEnter, onMouseLeave }: {
|
|
11
12
|
url: any;
|
package/dist/targets.js
CHANGED
|
@@ -4,7 +4,7 @@ const gap = 6;
|
|
|
4
4
|
const padding = gap * 2;
|
|
5
5
|
export function createTargetList(React, styles) {
|
|
6
6
|
const { createElement: element, useLayoutEffect, useRef, useState } = React;
|
|
7
|
-
function SpriteGrid({ className, compact, standard, stageTile, children, query = '' }) {
|
|
7
|
+
function SpriteGrid({ className, compact, standard, stageTile, children, query = '', onRename }) {
|
|
8
8
|
const grid = useRef(null);
|
|
9
9
|
const [size, setSize] = useState({ width: 0, height: 0, visibleRows: 0 });
|
|
10
10
|
useLayoutEffect(() => {
|
|
@@ -53,6 +53,12 @@ export function createTargetList(React, styles) {
|
|
|
53
53
|
const placement = standard || size.width < size.height ? 'bottom-right' :
|
|
54
54
|
(columns >= 2 ? 'top-right' : 'inline');
|
|
55
55
|
const rows = Math.max(size.visibleRows, Math.ceil((React.Children.count(children) + 1) / columns));
|
|
56
|
+
const tiles = onRename ? React.Children.map(children, child => {
|
|
57
|
+
const tile = React.isValidElement(child) && child.props.children;
|
|
58
|
+
if (!React.isValidElement(tile) || !tile.props.id)
|
|
59
|
+
return child;
|
|
60
|
+
return React.cloneElement(child, {}, React.cloneElement(tile, { onRenameButtonClick: onRename }));
|
|
61
|
+
}) : children;
|
|
56
62
|
return element('div', {
|
|
57
63
|
className: compact ? `${className} ${styles.grid}` : className,
|
|
58
64
|
'data-standard-target-grid': standard || undefined,
|
|
@@ -63,7 +69,7 @@ export function createTargetList(React, styles) {
|
|
|
63
69
|
'--target-columns': columns,
|
|
64
70
|
'--target-rows-before-last': rows > 1 ? `repeat(${rows - 1}, ${tileHeight}px)` : undefined
|
|
65
71
|
} : undefined
|
|
66
|
-
},
|
|
72
|
+
}, tiles, stageTile && element('div', { className: styles.backdropCell, key: 'stage' }, stageTile));
|
|
67
73
|
}
|
|
68
74
|
function BackdropTile({ url, label, selected, highlighted, containerRef, onClick, onMouseEnter, onMouseLeave }) {
|
|
69
75
|
return element('button', {
|
|
@@ -19,6 +19,10 @@ export interface WorkspaceLayout {
|
|
|
19
19
|
editingView?: string;
|
|
20
20
|
panels: Record<string, PanelReference>;
|
|
21
21
|
}
|
|
22
|
+
export declare function workspaceWithoutTransientPanels(grid: Grid, activeGroup: string, transient: ReadonlySet<string>, editingView?: string): {
|
|
23
|
+
grid: Grid;
|
|
24
|
+
activeGroup: string;
|
|
25
|
+
} | undefined;
|
|
22
26
|
/** Rebuild only the supported docked grid and logical panel references from storage. */
|
|
23
27
|
export declare function readWorkspaceLayout(raw: string, project: string): WorkspaceLayout | undefined;
|
|
24
28
|
export {};
|
package/dist/workspace-layout.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import { Orientation } from 'dockview';
|
|
2
|
+
export function workspaceWithoutTransientPanels(grid, activeGroup, transient, editingView) {
|
|
3
|
+
if (!transient.size)
|
|
4
|
+
return { grid, activeGroup };
|
|
5
|
+
const groups = new Map();
|
|
6
|
+
const trim = (node) => {
|
|
7
|
+
if (Array.isArray(node.data)) {
|
|
8
|
+
const data = node.data.flatMap(child => {
|
|
9
|
+
const kept = trim(child);
|
|
10
|
+
return kept ? [kept] : [];
|
|
11
|
+
});
|
|
12
|
+
return data.length ? { ...node, data } : undefined;
|
|
13
|
+
}
|
|
14
|
+
const views = node.data.views.filter(id => !transient.has(id));
|
|
15
|
+
if (!views.length)
|
|
16
|
+
return;
|
|
17
|
+
const activeView = views.includes(node.data.activeView) ? node.data.activeView : views[0];
|
|
18
|
+
groups.set(node.data.id, views);
|
|
19
|
+
return { ...node, data: { ...node.data, views, activeView } };
|
|
20
|
+
};
|
|
21
|
+
const root = trim(grid.root);
|
|
22
|
+
if (!root)
|
|
23
|
+
return;
|
|
24
|
+
if (groups.has(activeGroup))
|
|
25
|
+
return { grid: { ...grid, root }, activeGroup };
|
|
26
|
+
const fallback = [...groups].find(([, views]) => editingView && views.includes(editingView));
|
|
27
|
+
return { grid: { ...grid, root }, activeGroup: fallback?.[0] ?? groups.keys().next().value };
|
|
28
|
+
}
|
|
2
29
|
const limit = 256;
|
|
3
30
|
const validId = (value) => typeof value === 'string' && value.length > 0 && value.length <= 1024;
|
|
4
31
|
const size = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 100000;
|
package/package.json
CHANGED
package/src/editor.css
CHANGED
|
@@ -191,6 +191,17 @@
|
|
|
191
191
|
min-block-size: 0;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
/* The native stage marker is shared by the editor, fullscreen and player entries. */
|
|
195
|
+
:global([data-gui-stage]) {
|
|
196
|
+
--stage-toolbar-inset: 5.75rem;
|
|
197
|
+
--stage-overlay-z-index: 500;
|
|
198
|
+
--stage-overlay-gap: 0.5rem;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
:global([data-gui-stage="fullscreen"]) {
|
|
202
|
+
--stage-toolbar-inset: 2.75rem;
|
|
203
|
+
}
|
|
204
|
+
|
|
194
205
|
.stage-frame:not([data-fullscreen]) {
|
|
195
206
|
padding: 0 0.25rem 0.25rem;
|
|
196
207
|
border: 1px solid var(--ui-black-transparent);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import {SpritePropertiesPanel, type SpritePropertiesPanelProps} from './sprite-properties.tsx';
|
|
2
3
|
import type {SpriteAssetPreviewItem} from './sprite-tabs.ts';
|
|
3
4
|
|
|
4
5
|
interface NativeAssetPanelProps {
|
|
@@ -131,6 +132,9 @@ export interface SpriteMediaPaneProps extends PaneProps {
|
|
|
131
132
|
export interface SpriteVariablePaneProps extends PaneProps {
|
|
132
133
|
kind: 'variables';
|
|
133
134
|
variables: readonly SpriteVariable[];
|
|
135
|
+
properties?: SpritePropertiesPanelProps['values'];
|
|
136
|
+
targetId?: string;
|
|
137
|
+
onWriteProperties?: SpritePropertiesPanelProps['onChange'];
|
|
134
138
|
onWrite(id: string, value: SpriteVariable['value']): void;
|
|
135
139
|
}
|
|
136
140
|
|
|
@@ -244,9 +248,12 @@ function VariableField({variable, editable, onWrite}: {
|
|
|
244
248
|
}} />;
|
|
245
249
|
}
|
|
246
250
|
|
|
247
|
-
function VariablePane({variables, editable, onWrite, locale}: SpriteVariablePaneProps) {
|
|
251
|
+
function VariablePane({variables, editable, onWrite, locale, properties, targetId, onWriteProperties}: SpriteVariablePaneProps) {
|
|
248
252
|
const labels = labelsFor(locale);
|
|
249
253
|
return <section className="tw-sprite-variables" aria-label={labels.variables}>
|
|
254
|
+
{properties && targetId && onWriteProperties &&
|
|
255
|
+
<SpritePropertiesPanel targetId={targetId} values={properties} locale={locale}
|
|
256
|
+
disabled={!editable} onChange={onWriteProperties} />}
|
|
250
257
|
{variables.length ? <table className="tw-sprite-variables__table">
|
|
251
258
|
<thead><tr><th scope="col">{labels.name}</th><th scope="col">{labels.value}</th></tr></thead>
|
|
252
259
|
<tbody>{variables.map(variable => <tr key={variable.id}>
|
|
@@ -23,6 +23,7 @@ interface SpriteEditorToolbarProps {
|
|
|
23
23
|
activeGroup?: boolean;
|
|
24
24
|
search?: SpriteEditorSearch;
|
|
25
25
|
availableSections?: readonly SpriteEditorSection[];
|
|
26
|
+
propertiesInVariables?: boolean;
|
|
26
27
|
actions?: React.ReactNode;
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -95,10 +96,10 @@ function SearchBox({viewId, section, japanese, search}: {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
export function SpriteEditorToolbar({viewId, locale, section, onSectionChange, isStage, activeGroup, search,
|
|
98
|
-
availableSections = spriteEditorSections, actions}: SpriteEditorToolbarProps) {
|
|
99
|
+
availableSections = spriteEditorSections, propertiesInVariables = false, actions}: SpriteEditorToolbarProps) {
|
|
99
100
|
const japanese = /^ja(?:-|$)/i.test(locale);
|
|
100
|
-
const labels = japanese ? {code: 'コード', costumes: isStage ? '背景' : 'コスチューム', sounds: '音', variables: '変数'} :
|
|
101
|
-
{code: 'Code', costumes: isStage ? 'Backdrops' : 'Costumes', sounds: 'Sounds', variables: 'Variables'};
|
|
101
|
+
const labels = japanese ? {code: 'コード', costumes: isStage ? '背景' : 'コスチューム', sounds: '音', variables: propertiesInVariables ? 'プロパティ・変数' : '変数'} :
|
|
102
|
+
{code: 'Code', costumes: isStage ? 'Backdrops' : 'Costumes', sounds: 'Sounds', variables: propertiesInVariables ? 'Properties & Variables' : 'Variables'};
|
|
102
103
|
return <div className="tw-plus-sprite-toolbar" data-active-group={activeGroup}>
|
|
103
104
|
<div className="tw-plus-sprite-toolbar-sections" role="tablist"
|
|
104
105
|
aria-label={japanese ? 'スプライトの編集内容' : 'Sprite editor sections'}>
|
package/src/sprite-tabs.ts
CHANGED
|
@@ -6,12 +6,13 @@ import {createSpriteLayoutMenu} from './sprite-layout-menu.ts';
|
|
|
6
6
|
import {NativeAssetPanel, SpriteAssetPane} from './sprite-asset-pane.tsx';
|
|
7
7
|
import type {NativeAssetPanelRenderer, NativeAssetSelector, SpriteAssetLayout} from './sprite-asset-pane.tsx';
|
|
8
8
|
import type {SpriteVariable} from './sprite-asset-pane.tsx';
|
|
9
|
+
import type {SpritePropertiesPanelProps} from './sprite-properties.tsx';
|
|
9
10
|
import {SpriteEditorToolbar, spriteEditorSections} from './sprite-editor-toolbar.tsx';
|
|
10
11
|
import type {SpriteSearchResult} from './sprite-editor-toolbar.tsx';
|
|
11
12
|
import type {DockviewStagePane} from './dockview-stage.ts';
|
|
12
13
|
import {getDocumentWorkspace} from './document-workspace.ts';
|
|
13
14
|
import type {DocumentPanelRequest} from './document-workspace.ts';
|
|
14
|
-
import {readWorkspaceLayout} from './workspace-layout.ts';
|
|
15
|
+
import {readWorkspaceLayout, workspaceWithoutTransientPanels} from './workspace-layout.ts';
|
|
15
16
|
import type {PanelReference, WorkspaceLayout} from './workspace-layout.ts';
|
|
16
17
|
|
|
17
18
|
export {getDocumentWorkspace} from './document-workspace.ts';
|
|
@@ -144,7 +145,7 @@ interface TabOptions<Snapshot = unknown> {
|
|
|
144
145
|
reopenLabel: string;
|
|
145
146
|
multiView?: boolean;
|
|
146
147
|
layoutStorage?: {
|
|
147
|
-
storage: Storage
|
|
148
|
+
storage: Pick<Storage, 'getItem' | 'setItem'>;
|
|
148
149
|
key: string;
|
|
149
150
|
isProjectLoading?(): boolean;
|
|
150
151
|
subscribeProject?(listener: () => void): (() => void) | undefined;
|
|
@@ -365,12 +366,11 @@ export function mountSpriteTabs<Snapshot>(root: HTMLElement, content: HTMLElemen
|
|
|
365
366
|
return {...menu, init() {}};
|
|
366
367
|
}
|
|
367
368
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
369
|
+
const canRename = (target: Target) =>
|
|
370
|
+
target.isOriginal && !target.isStage && Boolean(vm.renameSprite) && vm.runtime.targets.includes(target);
|
|
371
371
|
|
|
372
372
|
function openRenameDialog(target: Target) {
|
|
373
|
-
if (renameDialog || !
|
|
373
|
+
if (renameDialog || !canRename(target)) return;
|
|
374
374
|
const dialog = document.createElement('dialog');
|
|
375
375
|
dialog.className = 'tw-plus-sprite-rename';
|
|
376
376
|
dialog.setAttribute('aria-label', text.title);
|
|
@@ -396,7 +396,7 @@ export function mountSpriteTabs<Snapshot>(root: HTMLElement, content: HTMLElemen
|
|
|
396
396
|
dialog.remove();
|
|
397
397
|
if (renameDialog !== dialog) return;
|
|
398
398
|
renameDialog = undefined;
|
|
399
|
-
if (dialog.returnValue === 'save' && input.value.trim() &&
|
|
399
|
+
if (dialog.returnValue === 'save' && input.value.trim() && canRename(target) &&
|
|
400
400
|
input.value !== target.getName()) vm.renameSprite?.(target.id, input.value);
|
|
401
401
|
}, {once: true});
|
|
402
402
|
renameDialog = dialog;
|
|
@@ -680,8 +680,14 @@ export function mountSpriteTabs<Snapshot>(root: HTMLElement, content: HTMLElemen
|
|
|
680
680
|
panels[id] = {kind: 'sprite', name: targetName(view.target), stage: view.target.isStage, section: view.section};
|
|
681
681
|
}
|
|
682
682
|
if (dockview.getPanel(stagePanelId)) panels[stagePanelId] = {kind: 'stage'};
|
|
683
|
-
|
|
684
|
-
const
|
|
683
|
+
const transient = new Set<string>();
|
|
684
|
+
for (const [id, {request}] of documents) {
|
|
685
|
+
if (request.transient) transient.add(id);
|
|
686
|
+
else panels[id] = {kind: 'document', ...request.reference};
|
|
687
|
+
}
|
|
688
|
+
const saved = workspaceWithoutTransientPanels(grid, activeGroup, transient, active?.id);
|
|
689
|
+
if (!saved) return;
|
|
690
|
+
const snapshot: WorkspaceLayout = {version: 1, project: projectSignature(targets), ...saved,
|
|
685
691
|
editingView: active?.id, panels};
|
|
686
692
|
try { persistence.storage.setItem(persistence.key, JSON.stringify(snapshot)); } catch { /* Storage can be unavailable. */ }
|
|
687
693
|
}
|
|
@@ -850,6 +856,10 @@ export function mountSpriteTabs<Snapshot>(root: HTMLElement, content: HTMLElemen
|
|
|
850
856
|
refresh();
|
|
851
857
|
return {
|
|
852
858
|
select, openStage, closeStage, split, closeSplit, activateView, setViewSection, syncActiveSection, refreshSurfaces,
|
|
859
|
+
rename(id: string) {
|
|
860
|
+
const target = vm.runtime.targets.find(item => item.id === id);
|
|
861
|
+
if (target) openRenameDialog(target);
|
|
862
|
+
},
|
|
853
863
|
selectAsset(id: string, kind: AssetKind, index: number) {
|
|
854
864
|
if (active?.id === id && active.section === (kind === 'costumes' ? 1 : 2)) {
|
|
855
865
|
active.assets.selectors.get(kind)?.(index);
|
|
@@ -900,6 +910,8 @@ type SpriteTabsProps<Snapshot> = Omit<TabOptions<Snapshot>, 'activateSection' |
|
|
|
900
910
|
renderToolbar?(view: SpriteTabView, actions: ToolbarActions): React.ReactNode;
|
|
901
911
|
getAssetPreview?(targetId: string, kind: SpriteAssetKind): SpriteAssetPreviewData;
|
|
902
912
|
getVariables?(targetId: string): readonly SpriteVariable[];
|
|
913
|
+
getProperties?(targetId: string): SpritePropertiesPanelProps['values'];
|
|
914
|
+
writeProperties?: SpritePropertiesPanelProps['onChange'];
|
|
903
915
|
writeVariable?(targetId: string, id: string, value: SpriteVariable['value']): void;
|
|
904
916
|
getCodeSearchItems?(targetId: string): readonly SpriteSearchResult[];
|
|
905
917
|
openCodeSearchResult?(targetId: string, id: string): void;
|
|
@@ -1048,7 +1060,7 @@ function SpriteTabs<Snapshot>(props: SpriteTabsProps<Snapshot>) {
|
|
|
1048
1060
|
const toolbar = props.renderToolbar?.(view, actions) ?? (props.getVariables &&
|
|
1049
1061
|
React.createElement(SpriteEditorToolbar, {
|
|
1050
1062
|
viewId: view.id, locale: props.locale, section: currentSection, isStage: view.isStage,
|
|
1051
|
-
activeGroup: view.groupActive,
|
|
1063
|
+
activeGroup: view.groupActive, propertiesInVariables: Boolean(props.getProperties),
|
|
1052
1064
|
actions: view.active && currentSection !== 'variables' ? props.activeTools : undefined,
|
|
1053
1065
|
onSectionChange: section => actions.setSection(spriteEditorSections.indexOf(section)),
|
|
1054
1066
|
search: {
|
|
@@ -1080,6 +1092,12 @@ function SpriteTabs<Snapshot>(props: SpriteTabsProps<Snapshot>) {
|
|
|
1080
1092
|
}), assetPreviewSlots.map(({id, targetId, kind, element, editable, selected, renderLayout, scrollTop}) =>
|
|
1081
1093
|
createPortal(kind === 'variables' ? React.createElement(SpriteAssetPane, {
|
|
1082
1094
|
kind, locale: props.locale, editable, variables: props.getVariables?.(targetId) ?? [],
|
|
1095
|
+
properties: props.getProperties?.(targetId),
|
|
1096
|
+
targetId,
|
|
1097
|
+
onWriteProperties(id: string, patch: Parameters<SpritePropertiesPanelProps['onChange']>[1]) {
|
|
1098
|
+
props.writeProperties?.(id, patch);
|
|
1099
|
+
workspace?.refreshSurfaces();
|
|
1100
|
+
},
|
|
1083
1101
|
onWrite(variableId, value) {
|
|
1084
1102
|
props.writeVariable?.(targetId, variableId, value);
|
|
1085
1103
|
workspace?.refreshSurfaces();
|
|
@@ -1115,7 +1133,10 @@ export function useSpriteTabs<Snapshot>({available, enabled, locale, ...props}:
|
|
|
1115
1133
|
return {
|
|
1116
1134
|
controller,
|
|
1117
1135
|
assetSelections: enabled ? selection.assets : undefined,
|
|
1118
|
-
context: enabled ? {
|
|
1136
|
+
context: enabled ? {
|
|
1137
|
+
selectedId: selection.id, openLabel: japanese ? '開く' : 'Open', select, open: select,
|
|
1138
|
+
rename: props.multiView && props.getProperties ? (id: string) => controller.current?.rename(id) : undefined
|
|
1139
|
+
} : null,
|
|
1119
1140
|
renderEditorTabRow: available ? (children: React.ReactNode) => React.createElement(SpriteTabs<Snapshot>, {
|
|
1120
1141
|
...props, enabled, locale, children, slots, onAttach: () => setReady(true),
|
|
1121
1142
|
selectTarget: (id, assets) => setSelection({id, assets}),
|
package/src/standard-layout.ts
CHANGED
|
@@ -255,17 +255,18 @@ export function createStandardLayout(React, {styles, targetStyles, context, Layo
|
|
|
255
255
|
return element(targets.BackdropFooter, {buttons: unique, label: isJapanese(locale) ? '背景を追加' : 'Add a backdrop'});
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
function SpritePropertiesSection({fallback, children}) {
|
|
258
|
+
function SpritePropertiesSection({fallback, children, inEditor = false}) {
|
|
259
259
|
const {plusLayout, locale} = useContext(context);
|
|
260
260
|
const [expanded, setExpanded] = useState(false);
|
|
261
261
|
const tools = useContext(targetTools);
|
|
262
262
|
return plusLayout ? element('div', {className: styles.spriteProperties},
|
|
263
|
-
element('
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
inEditor ? element('span', null, isJapanese(locale) ? 'スプライト' : 'Sprite') :
|
|
264
|
+
element('button', {
|
|
265
|
+
type: 'button', className: styles.spritePropertiesToggle, 'aria-expanded': expanded,
|
|
266
|
+
onClick: () => setExpanded(open => !open)
|
|
267
|
+
}, isJapanese(locale) ? 'スプライト' : 'Sprite'),
|
|
267
268
|
element('div', {ref: tools?.[1]}),
|
|
268
|
-
element('div', {className: styles.spritePropertiesContent, hidden: !expanded}, children)
|
|
269
|
+
!inEditor && element('div', {className: styles.spritePropertiesContent, hidden: !expanded}, children)
|
|
269
270
|
) : fallback;
|
|
270
271
|
}
|
|
271
272
|
|
package/src/targets.ts
CHANGED
|
@@ -6,7 +6,7 @@ const padding = gap * 2;
|
|
|
6
6
|
export function createTargetList(React, styles) {
|
|
7
7
|
const {createElement: element, useLayoutEffect, useRef, useState} = React;
|
|
8
8
|
|
|
9
|
-
function SpriteGrid({className, compact, standard, stageTile, children, query = ''}) {
|
|
9
|
+
function SpriteGrid({className, compact, standard, stageTile, children, query = '', onRename}) {
|
|
10
10
|
const grid = useRef(null);
|
|
11
11
|
const [size, setSize] = useState({width: 0, height: 0, visibleRows: 0});
|
|
12
12
|
useLayoutEffect(() => {
|
|
@@ -51,6 +51,11 @@ export function createTargetList(React, styles) {
|
|
|
51
51
|
const placement = standard || size.width < size.height ? 'bottom-right' :
|
|
52
52
|
(columns >= 2 ? 'top-right' : 'inline');
|
|
53
53
|
const rows = Math.max(size.visibleRows, Math.ceil((React.Children.count(children) + 1) / columns));
|
|
54
|
+
const tiles = onRename ? React.Children.map(children, child => {
|
|
55
|
+
const tile = React.isValidElement(child) && child.props.children;
|
|
56
|
+
if (!React.isValidElement(tile) || !tile.props.id) return child;
|
|
57
|
+
return React.cloneElement(child, {}, React.cloneElement(tile, {onRenameButtonClick: onRename}));
|
|
58
|
+
}) : children;
|
|
54
59
|
return element('div', {
|
|
55
60
|
className: compact ? `${className} ${styles.grid}` : className,
|
|
56
61
|
'data-standard-target-grid': standard || undefined,
|
|
@@ -61,7 +66,7 @@ export function createTargetList(React, styles) {
|
|
|
61
66
|
'--target-columns': columns,
|
|
62
67
|
'--target-rows-before-last': rows > 1 ? `repeat(${rows - 1}, ${tileHeight}px)` : undefined
|
|
63
68
|
} : undefined
|
|
64
|
-
},
|
|
69
|
+
}, tiles, stageTile && element('div', {className: styles.backdropCell, key: 'stage'}, stageTile));
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
function BackdropTile({url, label, selected, highlighted, containerRef, onClick, onMouseEnter, onMouseLeave}) {
|
package/src/workspace-layout.ts
CHANGED
|
@@ -18,6 +18,31 @@ export interface WorkspaceLayout {
|
|
|
18
18
|
panels: Record<string, PanelReference>;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export function workspaceWithoutTransientPanels(grid: Grid, activeGroup: string, transient: ReadonlySet<string>,
|
|
22
|
+
editingView?: string): {grid: Grid; activeGroup: string} | undefined {
|
|
23
|
+
if (!transient.size) return {grid, activeGroup};
|
|
24
|
+
const groups = new Map<string, readonly string[]>();
|
|
25
|
+
const trim = (node: Node): Node | undefined => {
|
|
26
|
+
if (Array.isArray(node.data)) {
|
|
27
|
+
const data = node.data.flatMap(child => {
|
|
28
|
+
const kept = trim(child);
|
|
29
|
+
return kept ? [kept] : [];
|
|
30
|
+
});
|
|
31
|
+
return data.length ? {...node, data} : undefined;
|
|
32
|
+
}
|
|
33
|
+
const views = node.data.views.filter(id => !transient.has(id));
|
|
34
|
+
if (!views.length) return;
|
|
35
|
+
const activeView = views.includes(node.data.activeView!) ? node.data.activeView : views[0];
|
|
36
|
+
groups.set(node.data.id, views);
|
|
37
|
+
return {...node, data: {...node.data, views, activeView}};
|
|
38
|
+
};
|
|
39
|
+
const root = trim(grid.root);
|
|
40
|
+
if (!root) return;
|
|
41
|
+
if (groups.has(activeGroup)) return {grid: {...grid, root}, activeGroup};
|
|
42
|
+
const fallback = [...groups].find(([, views]) => editingView && views.includes(editingView));
|
|
43
|
+
return {grid: {...grid, root}, activeGroup: fallback?.[0] ?? groups.keys().next().value!};
|
|
44
|
+
}
|
|
45
|
+
|
|
21
46
|
const limit = 256;
|
|
22
47
|
const validId = (value: unknown): value is string =>
|
|
23
48
|
typeof value === 'string' && value.length > 0 && value.length <= 1024;
|