@kubohiroya/turbowarp-plus-editor-layout 0.1.6 → 0.1.7

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.
@@ -1,4 +1,49 @@
1
1
  import React from 'react';
2
+ import type { SpriteAssetPreviewItem } from './sprite-tabs.ts';
3
+ interface NativeAssetPanelProps {
4
+ children?: React.ReactNode;
5
+ buttons: readonly {
6
+ title: string;
7
+ img: string;
8
+ onClick(): void;
9
+ }[];
10
+ items: readonly {
11
+ dragPayload?: object;
12
+ name: string;
13
+ url?: string;
14
+ details?: React.ReactNode;
15
+ }[];
16
+ isRtl?: boolean;
17
+ listActions?: React.ElementType;
18
+ selectedItemIndex: number;
19
+ onItemClick(index: number): void;
20
+ }
21
+ interface NativeSelectorProps extends NativeAssetPanelProps {
22
+ wrapperAttributes?: Record<string, string>;
23
+ listRef?(node: HTMLElement | null): void;
24
+ onListScroll?(event: React.UIEvent<HTMLElement>): void;
25
+ draggingType?: null;
26
+ onAddSortable?(): void;
27
+ onRemoveSortable?(): void;
28
+ }
29
+ export type NativeAssetSelector = React.ComponentType<NativeSelectorProps>;
30
+ export type NativeAssetPanelRenderer = (props: NativeAssetPanelProps, options: {
31
+ panelAttributes: Record<string, string>;
32
+ detailAttributes: Record<string, string>;
33
+ renderSelector(Component: NativeAssetSelector, props: NativeSelectorProps): React.ReactNode;
34
+ }) => React.ReactNode;
35
+ export type SpriteAssetLayout = (items: readonly SpriteAssetPreviewItem[], index: number, detail: React.ReactNode, scrollTop: number) => React.ReactNode;
36
+ interface PanelState {
37
+ rememberLayout(render: SpriteAssetLayout): void;
38
+ getScrollTop(): number;
39
+ rememberScrollTop(position: number): void;
40
+ }
41
+ export declare function NativeAssetPanel({ render, previewSelector, panel, state }: {
42
+ render: NativeAssetPanelRenderer;
43
+ previewSelector: NativeAssetSelector;
44
+ panel: NativeAssetPanelProps;
45
+ state: PanelState;
46
+ }): React.JSX.Element;
2
47
  export interface SpriteMediaAsset {
3
48
  /** Host identity for this item within its target, independent of the list order. */
4
49
  id: string;
@@ -1,4 +1,47 @@
1
1
  import React from 'react';
2
+ const noop = () => { };
3
+ function NativeAssetPanelSurface({ render, panel, state, previewSelector, scrollTop }) {
4
+ const list = React.useRef(null);
5
+ const setList = React.useCallback((node) => {
6
+ list.current = node;
7
+ }, []);
8
+ React.useLayoutEffect(() => {
9
+ const position = state?.getScrollTop() ?? scrollTop;
10
+ if (list.current && position !== undefined)
11
+ list.current.scrollTop = position;
12
+ }, [state, scrollTop]);
13
+ return render(panel, {
14
+ panelAttributes: { 'data-layout-asset-panel': '' },
15
+ detailAttributes: { 'data-layout-asset-detail': '' },
16
+ renderSelector(Component, props) {
17
+ const selectorProps = { ...props, listRef: setList, wrapperAttributes: { 'data-layout-asset-list': '' } };
18
+ if (previewSelector) {
19
+ return React.createElement("div", { inert: '', style: { display: 'contents' } }, React.createElement(previewSelector, {
20
+ ...selectorProps, draggingType: null, onAddSortable: noop, onRemoveSortable: noop
21
+ }));
22
+ }
23
+ return React.createElement(Component, { ...selectorProps, onListScroll: event => {
24
+ state?.rememberScrollTop(event.currentTarget.scrollTop);
25
+ } });
26
+ }
27
+ });
28
+ }
29
+ export function NativeAssetPanel({ render, previewSelector, panel, state }) {
30
+ const { items, buttons, isRtl, listActions } = panel;
31
+ React.useLayoutEffect(() => {
32
+ const previousItems = new Map(items.map(item => [item.dragPayload, item]));
33
+ const readOnlyButtons = buttons.map(({ title, img }) => ({ title, img, onClick: noop }));
34
+ state.rememberLayout((previewItems, selectedItemIndex, detail, scrollTop) => React.createElement(NativeAssetPanelSurface, { render: render, previewSelector: previewSelector, scrollTop: scrollTop, panel: {
35
+ isRtl, listActions, selectedItemIndex, buttons: readOnlyButtons,
36
+ onItemClick: noop, children: detail,
37
+ items: previewItems.map(item => {
38
+ const previous = previousItems.get(item.asset);
39
+ return { name: item.name, url: item.imageUrl || previous?.url, details: previous?.details };
40
+ })
41
+ } }));
42
+ }, [state, render, previewSelector, items, buttons, isRtl, listActions]);
43
+ return React.createElement(NativeAssetPanelSurface, { render: render, panel: panel, state: state });
44
+ }
2
45
  const english = {
3
46
  costumes: 'Costumes', sounds: 'Sounds', variables: 'Variables', name: 'Name', value: 'Value',
4
47
  local: 'Sprite', global: 'Project', emptyCostumes: 'No costumes', emptySounds: 'No sounds',
@@ -1,4 +1,6 @@
1
1
  import React from 'react';
2
+ import { NativeAssetPanel } from './sprite-asset-pane.tsx';
3
+ import type { NativeAssetPanelRenderer, NativeAssetSelector, SpriteAssetLayout } from './sprite-asset-pane.tsx';
2
4
  import type { SpriteVariable } from './sprite-asset-pane.tsx';
3
5
  import type { SpriteSearchResult } from './sprite-editor-toolbar.tsx';
4
6
  import type { DockviewStagePane } from './dockview-stage.ts';
@@ -49,6 +51,7 @@ export interface SpriteTabAdapter<Snapshot = unknown> {
49
51
  export interface SpriteAssetSelection {
50
52
  restore(target: object, assets: readonly object[]): number | undefined;
51
53
  remember(target: object, asset: object | undefined, select?: (index: number) => void): void;
54
+ renderPanel?(render: NativeAssetPanelRenderer, previewSelector: NativeAssetSelector, panel: React.ComponentProps<typeof NativeAssetPanel>['panel']): React.ReactNode;
52
55
  rememberLayout?(render: SpriteAssetLayout): void;
53
56
  getScrollTop?(): number;
54
57
  rememberScrollTop?(position: number): void;
@@ -62,7 +65,6 @@ export interface SpriteAssetPreviewItem {
62
65
  buffer?: AudioBuffer;
63
66
  duration?: number;
64
67
  }
65
- type SpriteAssetLayout = (items: readonly SpriteAssetPreviewItem[], index: number, detail: React.ReactNode, scrollTop: number) => React.ReactNode;
66
68
  export interface SpriteAssetPreviewData {
67
69
  items: readonly SpriteAssetPreviewItem[];
68
70
  defaultAsset?: object;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
  import { createDockview, DefaultTab, themeLight } from 'dockview';
4
4
  import { createSpriteLayoutMenu } from "./sprite-layout-menu.js";
5
- import { SpriteAssetPane } from "./sprite-asset-pane.js";
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
8
  import { readWorkspaceLayout } from "./workspace-layout.js";
@@ -14,32 +14,38 @@ function createAssetSelections(target, source) {
14
14
  const layouts = new Map(source?.layouts);
15
15
  const scroll = new Map(source?.scroll);
16
16
  const selectors = new Map();
17
- const forKind = (kind) => ({
18
- restore(current, assets) {
19
- if (current !== target)
20
- return;
21
- const index = assets.indexOf(selected.get(kind));
22
- if (index >= 0)
23
- return index;
24
- },
25
- remember(current, asset, select) {
26
- if (current !== target)
27
- return;
28
- if (asset)
29
- selected.set(kind, asset);
30
- else
31
- selected.delete(kind);
32
- if (select)
33
- selectors.set(kind, select);
34
- },
35
- rememberLayout(render) {
36
- layouts.set(kind, render);
37
- },
38
- getScrollTop: () => scroll.get(kind) ?? 0,
39
- rememberScrollTop(position) {
40
- scroll.set(kind, position);
41
- }
42
- });
17
+ const forKind = (kind) => {
18
+ const state = {
19
+ rememberLayout(render) {
20
+ layouts.set(kind, render);
21
+ },
22
+ getScrollTop: () => scroll.get(kind) ?? 0,
23
+ rememberScrollTop(position) {
24
+ scroll.set(kind, position);
25
+ }
26
+ };
27
+ return {
28
+ ...state,
29
+ renderPanel: (render, previewSelector, panel) => React.createElement(NativeAssetPanel, { render, previewSelector, panel, state }),
30
+ restore(current, assets) {
31
+ if (current !== target)
32
+ return;
33
+ const index = assets.indexOf(selected.get(kind));
34
+ if (index >= 0)
35
+ return index;
36
+ },
37
+ remember(current, asset, select) {
38
+ if (current !== target)
39
+ return;
40
+ if (asset)
41
+ selected.set(kind, asset);
42
+ else
43
+ selected.delete(kind);
44
+ if (select)
45
+ selectors.set(kind, select);
46
+ },
47
+ };
48
+ };
43
49
  const ports = { costumes: forKind('costumes'), sounds: forKind('sounds') };
44
50
  return { selected, ports, layouts, selectors, scroll };
45
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-plus-editor-layout",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Resizable panes and layout for TurboWarp Plus Editor",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -1,4 +1,100 @@
1
1
  import React from 'react';
2
+ import type {SpriteAssetPreviewItem} from './sprite-tabs.ts';
3
+
4
+ interface NativeAssetPanelProps {
5
+ children?: React.ReactNode;
6
+ buttons: readonly {title: string; img: string; onClick(): void}[];
7
+ items: readonly {dragPayload?: object; name: string; url?: string; details?: React.ReactNode}[];
8
+ isRtl?: boolean;
9
+ listActions?: React.ElementType;
10
+ selectedItemIndex: number;
11
+ onItemClick(index: number): void;
12
+ }
13
+
14
+ interface NativeSelectorProps extends NativeAssetPanelProps {
15
+ wrapperAttributes?: Record<string, string>;
16
+ listRef?(node: HTMLElement | null): void;
17
+ onListScroll?(event: React.UIEvent<HTMLElement>): void;
18
+ draggingType?: null;
19
+ onAddSortable?(): void;
20
+ onRemoveSortable?(): void;
21
+ }
22
+
23
+ export type NativeAssetSelector = React.ComponentType<NativeSelectorProps>;
24
+ export type NativeAssetPanelRenderer = (props: NativeAssetPanelProps, options: {
25
+ panelAttributes: Record<string, string>;
26
+ detailAttributes: Record<string, string>;
27
+ renderSelector(Component: NativeAssetSelector, props: NativeSelectorProps): React.ReactNode;
28
+ }) => React.ReactNode;
29
+
30
+ export type SpriteAssetLayout = (items: readonly SpriteAssetPreviewItem[], index: number,
31
+ detail: React.ReactNode, scrollTop: number) => React.ReactNode;
32
+
33
+ interface PanelState {
34
+ rememberLayout(render: SpriteAssetLayout): void;
35
+ getScrollTop(): number;
36
+ rememberScrollTop(position: number): void;
37
+ }
38
+
39
+ const noop = () => {};
40
+
41
+ function NativeAssetPanelSurface({render, panel, state, previewSelector, scrollTop}: {
42
+ render: NativeAssetPanelRenderer;
43
+ panel: NativeAssetPanelProps;
44
+ state?: PanelState;
45
+ previewSelector?: NativeAssetSelector;
46
+ scrollTop?: number;
47
+ }) {
48
+ const list = React.useRef<HTMLElement | null>(null);
49
+ const setList = React.useCallback((node: HTMLElement | null) => {
50
+ list.current = node;
51
+ }, []);
52
+ React.useLayoutEffect(() => {
53
+ const position = state?.getScrollTop() ?? scrollTop;
54
+ if (list.current && position !== undefined) list.current.scrollTop = position;
55
+ }, [state, scrollTop]);
56
+
57
+ return render(panel, {
58
+ panelAttributes: {'data-layout-asset-panel': ''},
59
+ detailAttributes: {'data-layout-asset-detail': ''},
60
+ renderSelector(Component, props) {
61
+ const selectorProps = {...props, listRef: setList, wrapperAttributes: {'data-layout-asset-list': ''}};
62
+ if (previewSelector) {
63
+ return <div {...{inert: ''}} style={{display: 'contents'}}>
64
+ {React.createElement(previewSelector, {
65
+ ...selectorProps, draggingType: null, onAddSortable: noop, onRemoveSortable: noop
66
+ })}
67
+ </div>;
68
+ }
69
+ return <Component {...selectorProps} onListScroll={event => {
70
+ state?.rememberScrollTop(event.currentTarget.scrollTop);
71
+ }} />;
72
+ }
73
+ });
74
+ }
75
+
76
+ export function NativeAssetPanel({render, previewSelector, panel, state}: {
77
+ render: NativeAssetPanelRenderer;
78
+ previewSelector: NativeAssetSelector;
79
+ panel: NativeAssetPanelProps;
80
+ state: PanelState;
81
+ }) {
82
+ const {items, buttons, isRtl, listActions} = panel;
83
+ React.useLayoutEffect(() => {
84
+ const previousItems = new Map(items.map(item => [item.dragPayload, item]));
85
+ const readOnlyButtons = buttons.map(({title, img}) => ({title, img, onClick: noop}));
86
+ state.rememberLayout((previewItems, selectedItemIndex, detail, scrollTop) => <NativeAssetPanelSurface
87
+ render={render} previewSelector={previewSelector} scrollTop={scrollTop} panel={{
88
+ isRtl, listActions, selectedItemIndex, buttons: readOnlyButtons,
89
+ onItemClick: noop, children: detail,
90
+ items: previewItems.map(item => {
91
+ const previous = previousItems.get(item.asset);
92
+ return {name: item.name, url: item.imageUrl || previous?.url, details: previous?.details};
93
+ })
94
+ }} />);
95
+ }, [state, render, previewSelector, items, buttons, isRtl, listActions]);
96
+ return <NativeAssetPanelSurface render={render} panel={panel} state={state} />;
97
+ }
2
98
 
3
99
  export interface SpriteMediaAsset {
4
100
  /** Host identity for this item within its target, independent of the list order. */
@@ -3,7 +3,8 @@ import {createPortal} from 'react-dom';
3
3
  import {createDockview, DefaultTab, themeLight} from 'dockview';
4
4
  import type {DockviewGroupPanel, GroupPanelPartInitParameters, IDockviewPanel, SerializedDockview} from 'dockview';
5
5
  import {createSpriteLayoutMenu} from './sprite-layout-menu.ts';
6
- import {SpriteAssetPane} from './sprite-asset-pane.tsx';
6
+ import {NativeAssetPanel, SpriteAssetPane} from './sprite-asset-pane.tsx';
7
+ import type {NativeAssetPanelRenderer, NativeAssetSelector, SpriteAssetLayout} from './sprite-asset-pane.tsx';
7
8
  import type {SpriteVariable} from './sprite-asset-pane.tsx';
8
9
  import {SpriteEditorToolbar, spriteEditorSections} from './sprite-editor-toolbar.tsx';
9
10
  import type {SpriteSearchResult} from './sprite-editor-toolbar.tsx';
@@ -61,6 +62,8 @@ export interface SpriteTabAdapter<Snapshot = unknown> {
61
62
  export interface SpriteAssetSelection {
62
63
  restore(target: object, assets: readonly object[]): number | undefined;
63
64
  remember(target: object, asset: object | undefined, select?: (index: number) => void): void;
65
+ renderPanel?(render: NativeAssetPanelRenderer, previewSelector: NativeAssetSelector,
66
+ panel: React.ComponentProps<typeof NativeAssetPanel>['panel']): React.ReactNode;
64
67
  rememberLayout?(render: SpriteAssetLayout): void;
65
68
  getScrollTop?(): number;
66
69
  rememberScrollTop?(position: number): void;
@@ -77,7 +80,6 @@ export interface SpriteAssetPreviewItem {
77
80
  duration?: number;
78
81
  }
79
82
 
80
- type SpriteAssetLayout = (items: readonly SpriteAssetPreviewItem[], index: number, detail: React.ReactNode, scrollTop: number) => React.ReactNode;
81
83
  const variableSection = spriteEditorSections.indexOf('variables');
82
84
 
83
85
  export interface SpriteAssetPreviewData {
@@ -95,26 +97,33 @@ function createAssetSelections(target: Target, source?: {
95
97
  const layouts = new Map(source?.layouts);
96
98
  const scroll = new Map(source?.scroll);
97
99
  const selectors = new Map<AssetKind, (index: number) => void>();
98
- const forKind = (kind: AssetKind): SpriteAssetSelection => ({
99
- restore(current, assets) {
100
- if (current !== target) return;
101
- const index = assets.indexOf(selected.get(kind));
102
- if (index >= 0) return index;
103
- },
104
- remember(current, asset, select) {
105
- if (current !== target) return;
106
- if (asset) selected.set(kind, asset);
107
- else selected.delete(kind);
108
- if (select) selectors.set(kind, select);
109
- },
110
- rememberLayout(render) {
111
- layouts.set(kind, render);
112
- },
113
- getScrollTop: () => scroll.get(kind) ?? 0,
114
- rememberScrollTop(position) {
115
- scroll.set(kind, position);
116
- }
117
- });
100
+ const forKind = (kind: AssetKind): SpriteAssetSelection => {
101
+ const state = {
102
+ rememberLayout(render: SpriteAssetLayout) {
103
+ layouts.set(kind, render);
104
+ },
105
+ getScrollTop: () => scroll.get(kind) ?? 0,
106
+ rememberScrollTop(position: number) {
107
+ scroll.set(kind, position);
108
+ }
109
+ };
110
+ return {
111
+ ...state,
112
+ renderPanel: (render, previewSelector, panel) =>
113
+ React.createElement(NativeAssetPanel, {render, previewSelector, panel, state}),
114
+ restore(current, assets) {
115
+ if (current !== target) return;
116
+ const index = assets.indexOf(selected.get(kind));
117
+ if (index >= 0) return index;
118
+ },
119
+ remember(current, asset, select) {
120
+ if (current !== target) return;
121
+ if (asset) selected.set(kind, asset);
122
+ else selected.delete(kind);
123
+ if (select) selectors.set(kind, select);
124
+ },
125
+ };
126
+ };
118
127
  const ports: AssetSelections = {costumes: forKind('costumes'), sounds: forKind('sounds')};
119
128
  return {selected, ports, layouts, selectors, scroll};
120
129
  }