@kubohiroya/turbowarp-plus-editor-layout 0.1.15 → 0.1.17
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/native-tile-title.d.ts +1 -0
- package/dist/native-tile-title.js +16 -0
- package/dist/sprite-asset-pane.d.ts +5 -3
- package/dist/sprite-asset-pane.js +19 -7
- package/dist/sprite-editor-toolbar.js +4 -3
- package/dist/sprite-tabs.js +13 -5
- package/dist/standard-layout.js +1 -1
- package/dist/targets.js +2 -0
- package/package.json +1 -1
- package/src/editor.css +5 -1
- package/src/native-tile-title.ts +14 -0
- package/src/sprite-asset-pane.css +11 -0
- package/src/sprite-asset-pane.tsx +24 -10
- package/src/sprite-editor-toolbar.css +10 -3
- package/src/sprite-editor-toolbar.tsx +5 -3
- package/src/sprite-tabs.ts +14 -5
- package/src/standard-layout.ts +1 -1
- package/src/targets.ts +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function observeTileNameTitles(list: HTMLElement | null): () => void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function observeTileNameTitles(list) {
|
|
2
|
+
if (!list)
|
|
3
|
+
return;
|
|
4
|
+
const update = () => {
|
|
5
|
+
for (const tile of list.querySelectorAll('[class*="sprite-selector-item_sprite-selector-item_"]')) {
|
|
6
|
+
const name = tile.querySelector('[class*="sprite-selector-item_sprite-name_"]')?.textContent ?? '';
|
|
7
|
+
if (tile.title !== name)
|
|
8
|
+
tile.title = name;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
// The native sortable list can mount or replace its tiles after the host renders.
|
|
12
|
+
const observer = new list.ownerDocument.defaultView.MutationObserver(update);
|
|
13
|
+
observer.observe(list, { subtree: true, childList: true, characterData: true });
|
|
14
|
+
update();
|
|
15
|
+
return () => observer.disconnect();
|
|
16
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { type SpritePropertiesPanelProps } from './sprite-properties.tsx';
|
|
3
|
-
import type { SpriteAssetPreviewItem } from './sprite-tabs.ts';
|
|
3
|
+
import type { SpriteAssetKind, SpriteAssetPreviewItem } from './sprite-tabs.ts';
|
|
4
4
|
interface NativeAssetPanelProps {
|
|
5
5
|
children?: React.ReactNode;
|
|
6
6
|
buttons: readonly {
|
|
@@ -39,11 +39,13 @@ interface PanelState {
|
|
|
39
39
|
getScrollTop(): number;
|
|
40
40
|
rememberScrollTop(position: number): void;
|
|
41
41
|
}
|
|
42
|
-
export declare function NativeAssetPanel({ render, previewSelector, panel, state }: {
|
|
42
|
+
export declare function NativeAssetPanel({ render, previewSelector, panel, kind, locale, state }: {
|
|
43
43
|
render: NativeAssetPanelRenderer;
|
|
44
44
|
previewSelector: NativeAssetSelector;
|
|
45
45
|
panel: NativeAssetPanelProps;
|
|
46
|
-
|
|
46
|
+
kind?: SpriteAssetKind;
|
|
47
|
+
locale?: string;
|
|
48
|
+
state?: PanelState;
|
|
47
49
|
}): React.JSX.Element;
|
|
48
50
|
export interface SpriteMediaAsset {
|
|
49
51
|
/** Host identity for this item within its target, independent of the list order. */
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { observeTileNameTitles } from "./native-tile-title.js";
|
|
2
3
|
import { SpritePropertiesPanel } from "./sprite-properties.js";
|
|
3
4
|
const noop = () => { };
|
|
4
|
-
function NativeAssetPanelSurface({ render, panel, state, previewSelector, scrollTop }) {
|
|
5
|
+
function NativeAssetPanelSurface({ render, panel, kind, state, previewSelector, scrollTop }) {
|
|
5
6
|
const list = React.useRef(null);
|
|
7
|
+
const stopObserving = React.useRef();
|
|
6
8
|
const setList = React.useCallback((node) => {
|
|
9
|
+
stopObserving.current?.();
|
|
7
10
|
list.current = node;
|
|
11
|
+
stopObserving.current = observeTileNameTitles(node);
|
|
8
12
|
}, []);
|
|
9
13
|
React.useLayoutEffect(() => {
|
|
10
14
|
const position = state?.getScrollTop() ?? scrollTop;
|
|
@@ -12,7 +16,7 @@ function NativeAssetPanelSurface({ render, panel, state, previewSelector, scroll
|
|
|
12
16
|
list.current.scrollTop = position;
|
|
13
17
|
}, [state, scrollTop]);
|
|
14
18
|
return render(panel, {
|
|
15
|
-
panelAttributes: { 'data-layout-asset-panel': '' },
|
|
19
|
+
panelAttributes: { 'data-layout-asset-panel': kind ?? '' },
|
|
16
20
|
detailAttributes: { 'data-layout-asset-detail': '' },
|
|
17
21
|
renderSelector(Component, props) {
|
|
18
22
|
const selectorProps = { ...props, listRef: setList, wrapperAttributes: { 'data-layout-asset-list': '' } };
|
|
@@ -27,12 +31,14 @@ function NativeAssetPanelSurface({ render, panel, state, previewSelector, scroll
|
|
|
27
31
|
}
|
|
28
32
|
});
|
|
29
33
|
}
|
|
30
|
-
export function NativeAssetPanel({ render, previewSelector, panel, state }) {
|
|
34
|
+
export function NativeAssetPanel({ render, previewSelector, panel, kind, locale = 'en', state }) {
|
|
31
35
|
const { items, buttons, isRtl, listActions } = panel;
|
|
32
36
|
React.useLayoutEffect(() => {
|
|
37
|
+
if (!state)
|
|
38
|
+
return;
|
|
33
39
|
const previousItems = new Map(items.map(item => [item.dragPayload, item]));
|
|
34
40
|
const readOnlyButtons = buttons.map(({ title, img }) => ({ title, img, onClick: noop }));
|
|
35
|
-
state.rememberLayout((previewItems, selectedItemIndex, detail, scrollTop) => React.createElement(NativeAssetPanelSurface, { render: render, previewSelector: previewSelector, scrollTop: scrollTop, panel: {
|
|
41
|
+
state.rememberLayout((previewItems, selectedItemIndex, detail, scrollTop) => React.createElement(NativeAssetPanelSurface, { render: render, kind: kind, previewSelector: previewSelector, scrollTop: scrollTop, panel: {
|
|
36
42
|
isRtl, listActions, selectedItemIndex, buttons: readOnlyButtons,
|
|
37
43
|
onItemClick: noop, children: detail,
|
|
38
44
|
items: previewItems.map(item => {
|
|
@@ -40,8 +46,14 @@ export function NativeAssetPanel({ render, previewSelector, panel, state }) {
|
|
|
40
46
|
return { name: item.name, url: item.imageUrl || previous?.url, details: previous?.details };
|
|
41
47
|
})
|
|
42
48
|
} }));
|
|
43
|
-
}, [state, render, previewSelector, items, buttons, isRtl, listActions]);
|
|
44
|
-
|
|
49
|
+
}, [state, render, previewSelector, items, buttons, isRtl, listActions, kind]);
|
|
50
|
+
const activePanel = kind === 'sounds' ? {
|
|
51
|
+
...panel,
|
|
52
|
+
children: React.createElement(React.Fragment, null,
|
|
53
|
+
items.length === 0 && React.createElement("p", { className: "tw-sprite-media__empty" }, labelsFor(locale).emptySounds),
|
|
54
|
+
panel.children)
|
|
55
|
+
} : panel;
|
|
56
|
+
return React.createElement(NativeAssetPanelSurface, { render: render, panel: activePanel, kind: kind, state: state });
|
|
45
57
|
}
|
|
46
58
|
const english = {
|
|
47
59
|
costumes: 'Costumes', sounds: 'Sounds', variables: 'Variables', name: 'Name', value: 'Value',
|
|
@@ -76,7 +88,7 @@ function MediaPane({ kind, items, selectedId, onSelect, renderEditor, editable,
|
|
|
76
88
|
const duration = selected?.duration;
|
|
77
89
|
return React.createElement("section", { className: detailOnly ? 'tw-sprite-media tw-sprite-media--detail' : 'tw-sprite-media', "aria-label": labels[kind] },
|
|
78
90
|
!detailOnly && React.createElement("ul", { className: "tw-sprite-media__list", "aria-label": labels[kind] }, items.map(asset => React.createElement("li", { key: asset.id },
|
|
79
|
-
React.createElement("button", { type: "button", className: "tw-sprite-media__item", "aria-current": asset.id === selectedId || undefined, "aria-disabled": !editable || undefined, tabIndex: editable ? 0 : -1, onClick: () => {
|
|
91
|
+
React.createElement("button", { type: "button", className: "tw-sprite-media__item", title: asset.name, "aria-current": asset.id === selectedId || undefined, "aria-disabled": !editable || undefined, tabIndex: editable ? 0 : -1, onClick: () => {
|
|
80
92
|
if (editable)
|
|
81
93
|
onSelect(asset.id);
|
|
82
94
|
} },
|
|
@@ -51,7 +51,7 @@ function SearchBox({ viewId, section, japanese, search }) {
|
|
|
51
51
|
}
|
|
52
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: propertiesInVariables ? '
|
|
54
|
+
const labels = japanese ? { code: 'コード', costumes: isStage ? '背景' : 'コスチューム', sounds: '音', variables: propertiesInVariables ? '変数など' : '変数' } :
|
|
55
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 => {
|
|
@@ -67,6 +67,7 @@ export function SpriteEditorToolbar({ viewId, locale, section, onSectionChange,
|
|
|
67
67
|
} },
|
|
68
68
|
React.createElement(Icon, { kind: id }),
|
|
69
69
|
labels[id]))),
|
|
70
|
-
search && React.createElement(
|
|
71
|
-
|
|
70
|
+
(search || actions) && React.createElement("div", { className: "tw-plus-sprite-toolbar-tools" },
|
|
71
|
+
actions,
|
|
72
|
+
search && React.createElement(SearchBox, { viewId, section, japanese, search })));
|
|
72
73
|
}
|
package/dist/sprite-tabs.js
CHANGED
|
@@ -9,7 +9,7 @@ import { readWorkspaceLayout, workspaceWithoutTransientPanels } from "./workspac
|
|
|
9
9
|
export { getDocumentWorkspace } from "./document-workspace.js";
|
|
10
10
|
export { useDockviewStage } from "./dockview-stage.js";
|
|
11
11
|
const variableSection = spriteEditorSections.indexOf('variables');
|
|
12
|
-
function createAssetSelections(target, source) {
|
|
12
|
+
function createAssetSelections(target, locale, source) {
|
|
13
13
|
const selected = new Map(source?.selected);
|
|
14
14
|
const layouts = new Map(source?.layouts);
|
|
15
15
|
const scroll = new Map(source?.scroll);
|
|
@@ -26,7 +26,7 @@ function createAssetSelections(target, source) {
|
|
|
26
26
|
};
|
|
27
27
|
return {
|
|
28
28
|
...state,
|
|
29
|
-
renderPanel: (render, previewSelector, panel) => React.createElement(NativeAssetPanel, { render, previewSelector, panel, state }),
|
|
29
|
+
renderPanel: (render, previewSelector, panel) => React.createElement(NativeAssetPanel, { render, previewSelector, panel, kind, locale, state }),
|
|
30
30
|
restore(current, assets) {
|
|
31
31
|
if (current !== target)
|
|
32
32
|
return;
|
|
@@ -406,7 +406,7 @@ export function mountSpriteTabs(root, content, options) {
|
|
|
406
406
|
}
|
|
407
407
|
const source = splitPosition && views.get(splitPosition.referencePanel);
|
|
408
408
|
views.set(id, { id, target, section: section ?? remembered.get(target) ?? options.getSection(),
|
|
409
|
-
assets: createAssetSelections(target, source?.assets) });
|
|
409
|
+
assets: createAssetSelections(target, locale, source?.assets) });
|
|
410
410
|
try {
|
|
411
411
|
return dockview.addPanel({ id, title: target.getName(), component: 'sprite', inactive: !splitPosition,
|
|
412
412
|
position: splitPosition, params: { icon: getIcon(target) } });
|
|
@@ -616,7 +616,7 @@ export function mountSpriteTabs(root, content, options) {
|
|
|
616
616
|
const target = targets.find(item => item.isStage === reference.stage && targetName(item) === reference.name);
|
|
617
617
|
if (!target || id === stagePanelId || (reference.section === variableSection && !options.variables))
|
|
618
618
|
return finishRestore();
|
|
619
|
-
resolvedSprites.set(id, { id, target, section: reference.section, assets: createAssetSelections(target) });
|
|
619
|
+
resolvedSprites.set(id, { id, target, section: reference.section, assets: createAssetSelections(target, locale) });
|
|
620
620
|
panels[id] = { id, title: target.getName(), contentComponent: 'sprite', params: { icon: getIcon(target) } };
|
|
621
621
|
}
|
|
622
622
|
else if (reference.kind === 'stage') {
|
|
@@ -997,6 +997,14 @@ function SpriteTabs(props) {
|
|
|
997
997
|
export function useSpriteTabs({ available, enabled, locale, ...props }) {
|
|
998
998
|
const controller = React.useRef(null);
|
|
999
999
|
const [selection, setSelection] = React.useState({ id: '' });
|
|
1000
|
+
const defaultAssets = React.useMemo(() => {
|
|
1001
|
+
const forKind = (kind) => ({
|
|
1002
|
+
restore: () => undefined,
|
|
1003
|
+
remember() { },
|
|
1004
|
+
renderPanel: (render, previewSelector, panel) => React.createElement(NativeAssetPanel, { render, previewSelector, panel, kind, locale })
|
|
1005
|
+
});
|
|
1006
|
+
return { costumes: forKind('costumes'), sounds: forKind('sounds') };
|
|
1007
|
+
}, [locale]);
|
|
1000
1008
|
const [ready, setReady] = React.useState(false);
|
|
1001
1009
|
const [slots] = React.useState(() => available ? Array.from({ length: 3 }, () => {
|
|
1002
1010
|
const slot = document.createElement('div');
|
|
@@ -1014,7 +1022,7 @@ export function useSpriteTabs({ available, enabled, locale, ...props }) {
|
|
|
1014
1022
|
const select = (id) => controller.current?.select(id);
|
|
1015
1023
|
return {
|
|
1016
1024
|
controller,
|
|
1017
|
-
assetSelections: enabled ? selection.assets : undefined,
|
|
1025
|
+
assetSelections: (enabled ? selection.assets : undefined) ?? defaultAssets,
|
|
1018
1026
|
context: enabled ? {
|
|
1019
1027
|
selectedId: selection.id, openLabel: japanese ? '開く' : 'Open', select, open: select,
|
|
1020
1028
|
rename: props.multiView && props.getProperties ? (id) => controller.current?.rename(id) : undefined
|
package/dist/standard-layout.js
CHANGED
|
@@ -260,7 +260,7 @@ export function createStandardLayout(React, { styles, targetStyles, context, Lay
|
|
|
260
260
|
const { plusLayout, locale } = useContext(context);
|
|
261
261
|
const [expanded, setExpanded] = useState(false);
|
|
262
262
|
const tools = useContext(targetTools);
|
|
263
|
-
return plusLayout ? element('div', { className: styles.spriteProperties }, inEditor ? element('span',
|
|
263
|
+
return plusLayout ? element('div', { className: styles.spriteProperties }, inEditor ? element('span', { className: styles.spritePropertiesTitle }, isJapanese(locale) ? 'スプライト' : 'Sprite') :
|
|
264
264
|
element('button', {
|
|
265
265
|
type: 'button', className: styles.spritePropertiesToggle, 'aria-expanded': expanded,
|
|
266
266
|
onClick: () => setExpanded(open => !open)
|
package/dist/targets.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { observeTileNameTitles } from "./native-tile-title.js";
|
|
1
2
|
const tileHeight = 80;
|
|
2
3
|
const minTileWidth = 72;
|
|
3
4
|
const gap = 6;
|
|
@@ -26,6 +27,7 @@ export function createTargetList(React, styles) {
|
|
|
26
27
|
observer.observe(viewport);
|
|
27
28
|
return () => observer.disconnect();
|
|
28
29
|
}, [compact]);
|
|
30
|
+
useLayoutEffect(() => observeTileNameTitles(grid.current), []);
|
|
29
31
|
useLayoutEffect(() => {
|
|
30
32
|
const name = query.trim().toLocaleLowerCase();
|
|
31
33
|
if (!name)
|
package/package.json
CHANGED
package/src/editor.css
CHANGED
|
@@ -429,11 +429,15 @@ details.layout-menu button {
|
|
|
429
429
|
background: transparent;
|
|
430
430
|
color: inherit;
|
|
431
431
|
font: inherit;
|
|
432
|
-
font-size: 0.875rem;
|
|
433
432
|
text-align: start;
|
|
434
433
|
cursor: pointer;
|
|
435
434
|
}
|
|
436
435
|
|
|
436
|
+
.sprite-properties-title,
|
|
437
|
+
.sprite-properties-toggle {
|
|
438
|
+
font-size: 0.875rem;
|
|
439
|
+
}
|
|
440
|
+
|
|
437
441
|
.sprite-properties-content {
|
|
438
442
|
min-block-size: 0;
|
|
439
443
|
overflow: auto;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function observeTileNameTitles(list: HTMLElement | null) {
|
|
2
|
+
if (!list) return;
|
|
3
|
+
const update = () => {
|
|
4
|
+
for (const tile of list.querySelectorAll<HTMLElement>('[class*="sprite-selector-item_sprite-selector-item_"]')) {
|
|
5
|
+
const name = tile.querySelector('[class*="sprite-selector-item_sprite-name_"]')?.textContent ?? '';
|
|
6
|
+
if (tile.title !== name) tile.title = name;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
// The native sortable list can mount or replace its tiles after the host renders.
|
|
10
|
+
const observer = new list.ownerDocument.defaultView.MutationObserver(update);
|
|
11
|
+
observer.observe(list, {subtree: true, childList: true, characterData: true});
|
|
12
|
+
update();
|
|
13
|
+
return () => observer.disconnect();
|
|
14
|
+
}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
[data-layout-asset-panel="costumes"] [class*="sprite-selector-item_delete-button_"] {
|
|
2
|
+
display: none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.tw-sprite-media__empty {
|
|
6
|
+
margin: auto;
|
|
7
|
+
padding: 1rem;
|
|
8
|
+
color: var(--text-primary, #273146);
|
|
9
|
+
text-align: center;
|
|
10
|
+
}
|
|
11
|
+
|
|
1
12
|
.tw-sprite-media,
|
|
2
13
|
.tw-sprite-variables {
|
|
3
14
|
box-sizing: border-box;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import {observeTileNameTitles} from './native-tile-title.ts';
|
|
2
3
|
import {SpritePropertiesPanel, type SpritePropertiesPanelProps} from './sprite-properties.tsx';
|
|
3
|
-
import type {SpriteAssetPreviewItem} from './sprite-tabs.ts';
|
|
4
|
+
import type {SpriteAssetKind, SpriteAssetPreviewItem} from './sprite-tabs.ts';
|
|
4
5
|
|
|
5
6
|
interface NativeAssetPanelProps {
|
|
6
7
|
children?: React.ReactNode;
|
|
@@ -39,24 +40,27 @@ interface PanelState {
|
|
|
39
40
|
|
|
40
41
|
const noop = () => {};
|
|
41
42
|
|
|
42
|
-
function NativeAssetPanelSurface({render, panel, state, previewSelector, scrollTop}: {
|
|
43
|
+
function NativeAssetPanelSurface({render, panel, kind, state, previewSelector, scrollTop}: {
|
|
43
44
|
render: NativeAssetPanelRenderer;
|
|
44
45
|
panel: NativeAssetPanelProps;
|
|
46
|
+
kind?: SpriteAssetKind;
|
|
45
47
|
state?: PanelState;
|
|
46
48
|
previewSelector?: NativeAssetSelector;
|
|
47
49
|
scrollTop?: number;
|
|
48
50
|
}) {
|
|
49
51
|
const list = React.useRef<HTMLElement | null>(null);
|
|
52
|
+
const stopObserving = React.useRef<() => void>();
|
|
50
53
|
const setList = React.useCallback((node: HTMLElement | null) => {
|
|
54
|
+
stopObserving.current?.();
|
|
51
55
|
list.current = node;
|
|
56
|
+
stopObserving.current = observeTileNameTitles(node);
|
|
52
57
|
}, []);
|
|
53
58
|
React.useLayoutEffect(() => {
|
|
54
59
|
const position = state?.getScrollTop() ?? scrollTop;
|
|
55
60
|
if (list.current && position !== undefined) list.current.scrollTop = position;
|
|
56
61
|
}, [state, scrollTop]);
|
|
57
|
-
|
|
58
62
|
return render(panel, {
|
|
59
|
-
panelAttributes: {'data-layout-asset-panel': ''},
|
|
63
|
+
panelAttributes: {'data-layout-asset-panel': kind ?? ''},
|
|
60
64
|
detailAttributes: {'data-layout-asset-detail': ''},
|
|
61
65
|
renderSelector(Component, props) {
|
|
62
66
|
const selectorProps = {...props, listRef: setList, wrapperAttributes: {'data-layout-asset-list': ''}};
|
|
@@ -74,18 +78,21 @@ function NativeAssetPanelSurface({render, panel, state, previewSelector, scrollT
|
|
|
74
78
|
});
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
export function NativeAssetPanel({render, previewSelector, panel, state}: {
|
|
81
|
+
export function NativeAssetPanel({render, previewSelector, panel, kind, locale = 'en', state}: {
|
|
78
82
|
render: NativeAssetPanelRenderer;
|
|
79
83
|
previewSelector: NativeAssetSelector;
|
|
80
84
|
panel: NativeAssetPanelProps;
|
|
81
|
-
|
|
85
|
+
kind?: SpriteAssetKind;
|
|
86
|
+
locale?: string;
|
|
87
|
+
state?: PanelState;
|
|
82
88
|
}) {
|
|
83
89
|
const {items, buttons, isRtl, listActions} = panel;
|
|
84
90
|
React.useLayoutEffect(() => {
|
|
91
|
+
if (!state) return;
|
|
85
92
|
const previousItems = new Map(items.map(item => [item.dragPayload, item]));
|
|
86
93
|
const readOnlyButtons = buttons.map(({title, img}) => ({title, img, onClick: noop}));
|
|
87
94
|
state.rememberLayout((previewItems, selectedItemIndex, detail, scrollTop) => <NativeAssetPanelSurface
|
|
88
|
-
render={render} previewSelector={previewSelector} scrollTop={scrollTop} panel={{
|
|
95
|
+
render={render} kind={kind} previewSelector={previewSelector} scrollTop={scrollTop} panel={{
|
|
89
96
|
isRtl, listActions, selectedItemIndex, buttons: readOnlyButtons,
|
|
90
97
|
onItemClick: noop, children: detail,
|
|
91
98
|
items: previewItems.map(item => {
|
|
@@ -93,8 +100,15 @@ export function NativeAssetPanel({render, previewSelector, panel, state}: {
|
|
|
93
100
|
return {name: item.name, url: item.imageUrl || previous?.url, details: previous?.details};
|
|
94
101
|
})
|
|
95
102
|
}} />);
|
|
96
|
-
}, [state, render, previewSelector, items, buttons, isRtl, listActions]);
|
|
97
|
-
|
|
103
|
+
}, [state, render, previewSelector, items, buttons, isRtl, listActions, kind]);
|
|
104
|
+
const activePanel = kind === 'sounds' ? {
|
|
105
|
+
...panel,
|
|
106
|
+
children: <>
|
|
107
|
+
{items.length === 0 && <p className="tw-sprite-media__empty">{labelsFor(locale).emptySounds}</p>}
|
|
108
|
+
{panel.children}
|
|
109
|
+
</>
|
|
110
|
+
} : panel;
|
|
111
|
+
return <NativeAssetPanelSurface render={render} panel={activePanel} kind={kind} state={state} />;
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
export interface SpriteMediaAsset {
|
|
@@ -178,7 +192,7 @@ function MediaPane({kind, items, selectedId, onSelect, renderEditor, editable, l
|
|
|
178
192
|
return <section className={detailOnly ? 'tw-sprite-media tw-sprite-media--detail' : 'tw-sprite-media'} aria-label={labels[kind]}>
|
|
179
193
|
{!detailOnly && <ul className="tw-sprite-media__list" aria-label={labels[kind]}>
|
|
180
194
|
{items.map(asset => <li key={asset.id}>
|
|
181
|
-
<button type="button" className="tw-sprite-media__item" aria-current={asset.id === selectedId || undefined}
|
|
195
|
+
<button type="button" className="tw-sprite-media__item" title={asset.name} aria-current={asset.id === selectedId || undefined}
|
|
182
196
|
aria-disabled={!editable || undefined} tabIndex={editable ? 0 : -1} onClick={() => {
|
|
183
197
|
if (editable) onSelect(asset.id);
|
|
184
198
|
}}>
|
|
@@ -58,11 +58,18 @@
|
|
|
58
58
|
|
|
59
59
|
.tw-plus-sprite-toolbar svg { width: 16px; height: 16px; flex: none; }
|
|
60
60
|
|
|
61
|
+
.tw-plus-sprite-toolbar-tools {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 8px;
|
|
65
|
+
min-inline-size: 0;
|
|
66
|
+
margin-inline-start: auto;
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
.tw-plus-sprite-search {
|
|
62
70
|
position: relative;
|
|
63
|
-
|
|
64
|
-
min-
|
|
65
|
-
margin-inline-start: auto;
|
|
71
|
+
inline-size: 132px;
|
|
72
|
+
min-inline-size: 0;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
.tw-plus-sprite-search-field { position: relative; }
|
|
@@ -98,7 +98,7 @@ function SearchBox({viewId, section, japanese, search}: {
|
|
|
98
98
|
export function SpriteEditorToolbar({viewId, locale, section, onSectionChange, isStage, activeGroup, search,
|
|
99
99
|
availableSections = spriteEditorSections, propertiesInVariables = false, actions}: SpriteEditorToolbarProps) {
|
|
100
100
|
const japanese = /^ja(?:-|$)/i.test(locale);
|
|
101
|
-
const labels = japanese ? {code: 'コード', costumes: isStage ? '背景' : 'コスチューム', sounds: '音', variables: propertiesInVariables ? '
|
|
101
|
+
const labels = japanese ? {code: 'コード', costumes: isStage ? '背景' : 'コスチューム', sounds: '音', variables: propertiesInVariables ? '変数など' : '変数'} :
|
|
102
102
|
{code: 'Code', costumes: isStage ? 'Backdrops' : 'Costumes', sounds: 'Sounds', variables: propertiesInVariables ? 'Properties & Variables' : 'Variables'};
|
|
103
103
|
return <div className="tw-plus-sprite-toolbar" data-active-group={activeGroup}>
|
|
104
104
|
<div className="tw-plus-sprite-toolbar-sections" role="tablist"
|
|
@@ -119,7 +119,9 @@ export function SpriteEditorToolbar({viewId, locale, section, onSectionChange, i
|
|
|
119
119
|
{labels[id]}
|
|
120
120
|
</button>)}
|
|
121
121
|
</div>
|
|
122
|
-
{search && <
|
|
123
|
-
|
|
122
|
+
{(search || actions) && <div className="tw-plus-sprite-toolbar-tools">
|
|
123
|
+
{actions}
|
|
124
|
+
{search && <SearchBox {...{viewId, section, japanese, search}} />}
|
|
125
|
+
</div>}
|
|
124
126
|
</div>;
|
|
125
127
|
}
|
package/src/sprite-tabs.ts
CHANGED
|
@@ -89,7 +89,7 @@ export interface SpriteAssetPreviewData {
|
|
|
89
89
|
}
|
|
90
90
|
type AssetSelections = Readonly<Record<AssetKind, SpriteAssetSelection>>;
|
|
91
91
|
|
|
92
|
-
function createAssetSelections(target: Target, source?: {
|
|
92
|
+
function createAssetSelections(target: Target, locale: string, source?: {
|
|
93
93
|
selected: Map<AssetKind, object>;
|
|
94
94
|
layouts: Map<AssetKind, SpriteAssetLayout>;
|
|
95
95
|
scroll: Map<AssetKind, number>;
|
|
@@ -111,7 +111,7 @@ function createAssetSelections(target: Target, source?: {
|
|
|
111
111
|
return {
|
|
112
112
|
...state,
|
|
113
113
|
renderPanel: (render, previewSelector, panel) =>
|
|
114
|
-
React.createElement(NativeAssetPanel, {render, previewSelector, panel, state}),
|
|
114
|
+
React.createElement(NativeAssetPanel, {render, previewSelector, panel, kind, locale, state}),
|
|
115
115
|
restore(current, assets) {
|
|
116
116
|
if (current !== target) return;
|
|
117
117
|
const index = assets.indexOf(selected.get(kind));
|
|
@@ -532,7 +532,7 @@ export function mountSpriteTabs<Snapshot>(root: HTMLElement, content: HTMLElemen
|
|
|
532
532
|
}
|
|
533
533
|
const source = splitPosition && views.get(splitPosition.referencePanel);
|
|
534
534
|
views.set(id, {id, target, section: section ?? remembered.get(target) ?? options.getSection(),
|
|
535
|
-
assets: createAssetSelections(target, source?.assets)});
|
|
535
|
+
assets: createAssetSelections(target, locale, source?.assets)});
|
|
536
536
|
try {
|
|
537
537
|
return dockview.addPanel({id, title: target.getName(), component: 'sprite', inactive: !splitPosition,
|
|
538
538
|
position: splitPosition, params: {icon: getIcon(target)}});
|
|
@@ -728,7 +728,7 @@ export function mountSpriteTabs<Snapshot>(root: HTMLElement, content: HTMLElemen
|
|
|
728
728
|
if (reference.kind === 'sprite') {
|
|
729
729
|
const target = targets.find(item => item.isStage === reference.stage && targetName(item) === reference.name);
|
|
730
730
|
if (!target || id === stagePanelId || (reference.section === variableSection && !options.variables)) return finishRestore();
|
|
731
|
-
resolvedSprites.set(id, {id, target, section: reference.section, assets: createAssetSelections(target)});
|
|
731
|
+
resolvedSprites.set(id, {id, target, section: reference.section, assets: createAssetSelections(target, locale)});
|
|
732
732
|
panels[id] = {id, title: target.getName(), contentComponent: 'sprite', params: {icon: getIcon(target)}};
|
|
733
733
|
} else if (reference.kind === 'stage') {
|
|
734
734
|
if (!stagePane || id !== stagePanelId) return finishRestore();
|
|
@@ -1116,6 +1116,15 @@ export function useSpriteTabs<Snapshot>({available, enabled, locale, ...props}:
|
|
|
1116
1116
|
}) {
|
|
1117
1117
|
const controller = React.useRef<SpriteTabsController<Snapshot>>(null);
|
|
1118
1118
|
const [selection, setSelection] = React.useState<{id: string; assets?: AssetSelections}>({id: ''});
|
|
1119
|
+
const defaultAssets = React.useMemo<AssetSelections>(() => {
|
|
1120
|
+
const forKind = (kind: AssetKind): SpriteAssetSelection => ({
|
|
1121
|
+
restore: () => undefined,
|
|
1122
|
+
remember() {},
|
|
1123
|
+
renderPanel: (render, previewSelector, panel) =>
|
|
1124
|
+
React.createElement(NativeAssetPanel, {render, previewSelector, panel, kind, locale})
|
|
1125
|
+
});
|
|
1126
|
+
return {costumes: forKind('costumes'), sounds: forKind('sounds')};
|
|
1127
|
+
}, [locale]);
|
|
1119
1128
|
const [ready, setReady] = React.useState(false);
|
|
1120
1129
|
const [slots] = React.useState(() => available ? Array.from({length: 3}, () => {
|
|
1121
1130
|
const slot = document.createElement('div');
|
|
@@ -1132,7 +1141,7 @@ export function useSpriteTabs<Snapshot>({available, enabled, locale, ...props}:
|
|
|
1132
1141
|
const select = (id: string) => controller.current?.select(id);
|
|
1133
1142
|
return {
|
|
1134
1143
|
controller,
|
|
1135
|
-
assetSelections: enabled ? selection.assets : undefined,
|
|
1144
|
+
assetSelections: (enabled ? selection.assets : undefined) ?? defaultAssets,
|
|
1136
1145
|
context: enabled ? {
|
|
1137
1146
|
selectedId: selection.id, openLabel: japanese ? '開く' : 'Open', select, open: select,
|
|
1138
1147
|
rename: props.multiView && props.getProperties ? (id: string) => controller.current?.rename(id) : undefined
|
package/src/standard-layout.ts
CHANGED
|
@@ -280,7 +280,7 @@ export function createStandardLayout(React, {styles, targetStyles, context, Layo
|
|
|
280
280
|
const [expanded, setExpanded] = useState(false);
|
|
281
281
|
const tools = useContext(targetTools);
|
|
282
282
|
return plusLayout ? element('div', {className: styles.spriteProperties},
|
|
283
|
-
inEditor ? element('span',
|
|
283
|
+
inEditor ? element('span', {className: styles.spritePropertiesTitle}, isJapanese(locale) ? 'スプライト' : 'Sprite') :
|
|
284
284
|
element('button', {
|
|
285
285
|
type: 'button', className: styles.spritePropertiesToggle, 'aria-expanded': expanded,
|
|
286
286
|
onClick: () => setExpanded(open => !open)
|
package/src/targets.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {observeTileNameTitles} from './native-tile-title.ts';
|
|
2
|
+
|
|
1
3
|
const tileHeight = 80;
|
|
2
4
|
const minTileWidth = 72;
|
|
3
5
|
const gap = 6;
|
|
@@ -28,6 +30,8 @@ export function createTargetList(React, styles) {
|
|
|
28
30
|
return () => observer.disconnect();
|
|
29
31
|
}, [compact]);
|
|
30
32
|
|
|
33
|
+
useLayoutEffect(() => observeTileNameTitles(grid.current), []);
|
|
34
|
+
|
|
31
35
|
useLayoutEffect(() => {
|
|
32
36
|
const name = query.trim().toLocaleLowerCase();
|
|
33
37
|
if (!name) return;
|