@kubohiroya/turbowarp-plus-editor-layout 0.1.2 → 0.1.3

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.
@@ -0,0 +1,31 @@
1
+ import type { Context, ElementType } from 'react';
2
+ interface StageWindowConnection<Target> {
3
+ ready: Promise<Target>;
4
+ disconnected: AbortSignal;
5
+ close(): void;
6
+ }
7
+ interface StageWindowHost<Request, Target> {
8
+ open(request: Request): StageWindowConnection<Target> | null;
9
+ }
10
+ export declare function createStagePopoutHost<Request, Target extends {
11
+ mount: HTMLElement;
12
+ }>(host: StageWindowHost<Request, Target>, title: string): StageWindowHost<Request, Target>;
13
+ export declare function stageDockActionLabel(locale: string, { drawer, detached, external }: {
14
+ drawer?: boolean;
15
+ detached: boolean;
16
+ external?: boolean;
17
+ }): "ドックに戻す" | "Dock stage" | "別窓で開く" | "別ウィンドウで開く" | "Open in a separate window" | "アンドック" | "Undock stage";
18
+ export declare function createStageSurface(React: typeof import('react'), { context, EdgeDrawer, Panel }: {
19
+ context: Context<{
20
+ locale: string;
21
+ stageDrawer?: boolean;
22
+ setStageDrawerOpen?: (open: boolean) => void;
23
+ }>;
24
+ EdgeDrawer: ElementType;
25
+ Panel: ElementType;
26
+ }): ({ externalHost, onDockActionChange, ...props }: {
27
+ [x: string]: any;
28
+ externalHost: any;
29
+ onDockActionChange: any;
30
+ }) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
31
+ export {};
@@ -0,0 +1,81 @@
1
+ import { createDockview, themeLight } from 'dockview';
2
+ export function createStagePopoutHost(host, title) {
3
+ return {
4
+ open(request) {
5
+ const connection = host.open(request);
6
+ if (!connection)
7
+ return null;
8
+ const disconnected = new AbortController();
9
+ const disconnect = () => disconnected.abort();
10
+ connection.disconnected.addEventListener('abort', disconnect, { once: true });
11
+ if (connection.disconnected.aborted)
12
+ disconnect();
13
+ let dockview;
14
+ let removed;
15
+ let closed = false;
16
+ const ready = connection.ready.then(target => {
17
+ if (closed || disconnected.signal.aborted)
18
+ throw new Error('Stage window closed');
19
+ const content = target.mount.ownerDocument.createElement('div');
20
+ Object.assign(content.style, { width: '100%', height: '100%' });
21
+ dockview = createDockview(target.mount, {
22
+ createComponent: () => ({ element: content, init() { } }),
23
+ theme: themeLight,
24
+ disableDnd: true,
25
+ disableFloatingGroups: true
26
+ });
27
+ dockview.addPanel({ id: 'stage', component: 'stage', title });
28
+ removed = dockview.onDidRemovePanel(disconnect);
29
+ return { ...target, mount: content };
30
+ });
31
+ return {
32
+ ready,
33
+ disconnected: disconnected.signal,
34
+ close() {
35
+ if (closed)
36
+ return;
37
+ closed = true;
38
+ connection.disconnected.removeEventListener('abort', disconnect);
39
+ try {
40
+ removed?.dispose();
41
+ dockview?.dispose();
42
+ }
43
+ finally {
44
+ connection.close();
45
+ }
46
+ }
47
+ };
48
+ }
49
+ };
50
+ }
51
+ const isJapanese = (locale) => /^ja(?:-|$)/i.test(locale);
52
+ export function stageDockActionLabel(locale, { drawer, detached, external }) {
53
+ const japanese = isJapanese(locale);
54
+ if (detached)
55
+ return japanese ? 'ドックに戻す' : 'Dock stage';
56
+ if (external)
57
+ return japanese ? (drawer ? '別窓で開く' : '別ウィンドウで開く') : 'Open in a separate window';
58
+ return japanese ? 'アンドック' : 'Undock stage';
59
+ }
60
+ export function createStageSurface(React, { context, EdgeDrawer, Panel }) {
61
+ return function StageSurface({ externalHost, onDockActionChange, ...props }) {
62
+ const { locale, stageDrawer, setStageDrawerOpen } = React.useContext(context);
63
+ const japanese = isJapanese(locale);
64
+ const title = japanese ? 'ステージ' : 'Stage';
65
+ const host = React.useMemo(() => stageDrawer && externalHost ?
66
+ createStagePopoutHost(externalHost, title) : externalHost, [stageDrawer, externalHost, title]);
67
+ const panel = React.createElement(Panel, {
68
+ ...props,
69
+ id: 'stage',
70
+ title,
71
+ externalHost: host,
72
+ externalOnly: stageDrawer,
73
+ onDockActionChange: stageDrawer && !host ? undefined : onDockActionChange
74
+ });
75
+ return stageDrawer ? React.createElement(EdgeDrawer, {
76
+ edge: 'right',
77
+ label: japanese ? 'ステージを展開' : 'Reveal stage',
78
+ onOpenChange: setStageDrawerOpen
79
+ }, panel) : panel;
80
+ };
81
+ }
@@ -4,12 +4,13 @@ export declare function createStandardLayout(React: any, { styles, targetStyles,
4
4
  context: any;
5
5
  LayoutMenu: any;
6
6
  }): {
7
- StandardLayout: ({ enabled, splitter, windowLayout, stageActionEnabled, onPaletteModeChange, windowTools, Container, render, ...props }: {
7
+ StandardLayout: ({ enabled, splitter, windowLayout, stageActionEnabled, stageDrawer, onPaletteModeChange, windowTools, Container, render, ...props }: {
8
8
  [x: string]: any;
9
9
  enabled: any;
10
10
  splitter: any;
11
11
  windowLayout: any;
12
12
  stageActionEnabled: any;
13
+ stageDrawer?: boolean;
13
14
  onPaletteModeChange: any;
14
15
  windowTools: any;
15
16
  Container: any;
@@ -10,10 +10,11 @@ export function createStandardLayout(React, { styles, targetStyles, context, Lay
10
10
  const targets = createTargetList(React, targetStyles);
11
11
  const targetSearch = React.createContext('');
12
12
  const targetTools = React.createContext(null);
13
- function StandardLayout({ enabled, splitter, windowLayout, stageActionEnabled, onPaletteModeChange, windowTools, Container, render, ...props }) {
13
+ function StandardLayout({ enabled, splitter, windowLayout, stageActionEnabled, stageDrawer = false, onPaletteModeChange, windowTools, Container, render, ...props }) {
14
14
  const [showUndock, setShowUndock] = useState(false);
15
15
  const [targetsPinned, setTargetsPinned] = useState(false);
16
16
  const [showStage, setShowStage] = useState(true);
17
+ const [stageDrawerOpen, setStageDrawerOpen] = useState(false);
17
18
  const [size, setSize] = useState(null);
18
19
  const [targetRatio, setTargetRatio] = useState(null);
19
20
  const [preferredStageWidth, setPreferredStageWidth] = useState(null);
@@ -83,10 +84,11 @@ export function createStandardLayout(React, { styles, targetStyles, context, Lay
83
84
  if (!plusLayout)
84
85
  return shared;
85
86
  const stageOverlap = `${stageWidth + 20}px`;
87
+ const stageOverlaps = showStage && (!stageDrawer || stageDrawerOpen);
86
88
  const bodyStyle = {
87
89
  display: 'grid',
88
- '--plus-stage-overlap': showStage ? stageOverlap : '0px',
89
- '--plus-stage-tab-inset': showStage ? stageOverlap : '3rem'
90
+ '--plus-stage-overlap': stageOverlaps ? stageOverlap : '0px',
91
+ '--plus-stage-tab-inset': stageOverlaps ? stageOverlap : '3rem'
90
92
  };
91
93
  if (resizable) {
92
94
  bodyStyle['--plus-target-width'] = `${targetSplit.first}px`;
@@ -106,7 +108,7 @@ export function createStandardLayout(React, { styles, targetStyles, context, Lay
106
108
  columnSplitter,
107
109
  editorStyle: { gridArea: 'editor', minWidth: 0, minHeight: 0 },
108
110
  stageColumnStyle: { display: 'contents' },
109
- stageClassName: `${styles.stagePanel} ${styles.floatingStage}${stageHidden ? ` ${styles.stageHidden}` : ''}`,
111
+ stageClassName: `${styles.stagePanel} ${styles.floatingStage}${stageDrawer ? ` ${styles.stageDrawer}` : ''}${stageHidden ? ` ${styles.stageHidden}` : ''}`,
110
112
  targetClassName: targetsPinned ? styles.standardTargets :
111
113
  `${styles.standardTargets} ${styles.retractedTargets}`,
112
114
  stageDisplayWidth: stageWidth,
@@ -130,7 +132,8 @@ export function createStandardLayout(React, { styles, targetStyles, context, Lay
130
132
  render: slots => render(applyStandardLayout(slots), showUndock)
131
133
  });
132
134
  return element(context.Provider, { value: {
133
- showUndock, plusLayout, targetsPinned, setTargetsPinned, stageHidden, setStageVisible: setShowStage, stageResize, stageActionMount, locale: props.locale
135
+ showUndock, plusLayout, targetsPinned, setTargetsPinned, stageHidden, setStageVisible: setShowStage, stageResize, stageActionMount,
136
+ stageDrawer: plusLayout && stageDrawer, setStageDrawerOpen, locale: props.locale
134
137
  } }, Container ? element(Container, { locale: props.locale }, layout) : layout);
135
138
  }
136
139
  function EdgeDrawer({ edge, label, pinned = false, onOpenChange, children }) {
@@ -180,7 +183,7 @@ export function createStandardLayout(React, { styles, targetStyles, context, Lay
180
183
  panel.current.focus({ preventScroll: true });
181
184
  });
182
185
  }
183
- }, edge === 'left' ? '›' : '⌃'), element('div', { className: styles.edgePanel, ref: panel, tabIndex: -1, hidden: !open }, children));
186
+ }, { left: '›', right: '‹', bottom: '⌃' }[edge]), element('div', { className: styles.edgePanel, ref: panel, tabIndex: -1, hidden: !open }, children));
184
187
  }
185
188
  function TargetLayout({ compact, render }) {
186
189
  const { plusLayout, targetsPinned, locale } = useContext(context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-plus-editor-layout",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Resizable panes and layout for TurboWarp Plus Editor",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -13,6 +13,10 @@
13
13
  "types": "./dist/standard-layout.d.ts",
14
14
  "default": "./dist/standard-layout.js"
15
15
  },
16
+ "./stage-surface": {
17
+ "types": "./dist/stage-surface.d.ts",
18
+ "default": "./dist/stage-surface.js"
19
+ },
16
20
  "./editor.css": "./src/editor.css",
17
21
  "./targets.css": "./src/targets.css",
18
22
  "./sprite-properties": {
package/src/editor.css CHANGED
@@ -327,6 +327,10 @@
327
327
  z-index: 90;
328
328
  }
329
329
 
330
+ .standard-body .stage-drawer {
331
+ right: 0;
332
+ }
333
+
330
334
  .standard-body {
331
335
  --plus-extension-button-width: 2.75rem;
332
336
  --plus-extension-button-gap: 0.75rem;
@@ -470,6 +474,25 @@ details.layout-menu button {
470
474
  pointer-events: none;
471
475
  }
472
476
 
477
+ .edge-drawer[data-edge="right"] {
478
+ flex-direction: row-reverse;
479
+ pointer-events: none;
480
+ }
481
+
482
+ [dir="rtl"] .edge-drawer[data-edge="right"] {
483
+ flex-direction: row;
484
+ }
485
+
486
+ .edge-drawer[data-edge="right"] .edge-trigger {
487
+ width: 0.625rem;
488
+ min-height: 3rem;
489
+ visibility: visible;
490
+ }
491
+
492
+ .edge-drawer[data-edge="right"] .edge-panel {
493
+ background: transparent;
494
+ }
495
+
473
496
  .edge-drawer[data-edge="bottom"] {
474
497
  z-index: var(--plus-docked-utility-layer);
475
498
  flex: none;
@@ -0,0 +1,104 @@
1
+ import type {Context, ElementType} from 'react';
2
+ import {createDockview, themeLight} from 'dockview';
3
+
4
+ interface StageWindowConnection<Target> {
5
+ ready: Promise<Target>;
6
+ disconnected: AbortSignal;
7
+ close(): void;
8
+ }
9
+
10
+ interface StageWindowHost<Request, Target> {
11
+ open(request: Request): StageWindowConnection<Target> | null;
12
+ }
13
+
14
+ export function createStagePopoutHost<Request, Target extends {mount: HTMLElement}>(
15
+ host: StageWindowHost<Request, Target>, title: string
16
+ ): StageWindowHost<Request, Target> {
17
+ return {
18
+ open(request) {
19
+ const connection = host.open(request);
20
+ if (!connection) return null;
21
+
22
+ const disconnected = new AbortController();
23
+ const disconnect = () => disconnected.abort();
24
+ connection.disconnected.addEventListener('abort', disconnect, {once: true});
25
+ if (connection.disconnected.aborted) disconnect();
26
+
27
+ let dockview: ReturnType<typeof createDockview> | undefined;
28
+ let removed: {dispose(): void} | undefined;
29
+ let closed = false;
30
+ const ready = connection.ready.then(target => {
31
+ if (closed || disconnected.signal.aborted) throw new Error('Stage window closed');
32
+ const content = target.mount.ownerDocument.createElement('div');
33
+ Object.assign(content.style, {width: '100%', height: '100%'});
34
+ dockview = createDockview(target.mount, {
35
+ createComponent: () => ({element: content, init() {}}),
36
+ theme: themeLight,
37
+ disableDnd: true,
38
+ disableFloatingGroups: true
39
+ });
40
+ dockview.addPanel({id: 'stage', component: 'stage', title});
41
+ removed = dockview.onDidRemovePanel(disconnect);
42
+ return {...target, mount: content};
43
+ });
44
+
45
+ return {
46
+ ready,
47
+ disconnected: disconnected.signal,
48
+ close() {
49
+ if (closed) return;
50
+ closed = true;
51
+ connection.disconnected.removeEventListener('abort', disconnect);
52
+ try {
53
+ removed?.dispose();
54
+ dockview?.dispose();
55
+ } finally {
56
+ connection.close();
57
+ }
58
+ }
59
+ };
60
+ }
61
+ };
62
+ }
63
+
64
+ const isJapanese = (locale: string) => /^ja(?:-|$)/i.test(locale);
65
+
66
+ export function stageDockActionLabel(locale: string, {drawer, detached, external}: {
67
+ drawer?: boolean;
68
+ detached: boolean;
69
+ external?: boolean;
70
+ }) {
71
+ const japanese = isJapanese(locale);
72
+ if (detached) return japanese ? 'ドックに戻す' : 'Dock stage';
73
+ if (external) return japanese ? (drawer ? '別窓で開く' : '別ウィンドウで開く') : 'Open in a separate window';
74
+ return japanese ? 'アンドック' : 'Undock stage';
75
+ }
76
+
77
+ export function createStageSurface(React: typeof import('react'), {
78
+ context, EdgeDrawer, Panel
79
+ }: {
80
+ context: Context<{locale: string; stageDrawer?: boolean; setStageDrawerOpen?: (open: boolean) => void}>;
81
+ EdgeDrawer: ElementType;
82
+ Panel: ElementType;
83
+ }) {
84
+ return function StageSurface({externalHost, onDockActionChange, ...props}) {
85
+ const {locale, stageDrawer, setStageDrawerOpen} = React.useContext(context);
86
+ const japanese = isJapanese(locale);
87
+ const title = japanese ? 'ステージ' : 'Stage';
88
+ const host = React.useMemo(() => stageDrawer && externalHost ?
89
+ createStagePopoutHost(externalHost, title) : externalHost, [stageDrawer, externalHost, title]);
90
+ const panel = React.createElement(Panel, {
91
+ ...props,
92
+ id: 'stage',
93
+ title,
94
+ externalHost: host,
95
+ externalOnly: stageDrawer,
96
+ onDockActionChange: stageDrawer && !host ? undefined : onDockActionChange
97
+ });
98
+ return stageDrawer ? React.createElement(EdgeDrawer, {
99
+ edge: 'right',
100
+ label: japanese ? 'ステージを展開' : 'Reveal stage',
101
+ onOpenChange: setStageDrawerOpen
102
+ }, panel) : panel;
103
+ };
104
+ }
@@ -14,11 +14,13 @@ export function createStandardLayout(React, {styles, targetStyles, context, Layo
14
14
  const targetTools = React.createContext(null);
15
15
 
16
16
  function StandardLayout({
17
- enabled, splitter, windowLayout, stageActionEnabled, onPaletteModeChange, windowTools, Container, render, ...props
17
+ enabled, splitter, windowLayout, stageActionEnabled, stageDrawer = false,
18
+ onPaletteModeChange, windowTools, Container, render, ...props
18
19
  }) {
19
20
  const [showUndock, setShowUndock] = useState(false);
20
21
  const [targetsPinned, setTargetsPinned] = useState(false);
21
22
  const [showStage, setShowStage] = useState(true);
23
+ const [stageDrawerOpen, setStageDrawerOpen] = useState(false);
22
24
  const [size, setSize] = useState(null);
23
25
  const [targetRatio, setTargetRatio] = useState(null);
24
26
  const [preferredStageWidth, setPreferredStageWidth] = useState(null);
@@ -94,10 +96,11 @@ export function createStandardLayout(React, {styles, targetStyles, context, Layo
94
96
  };
95
97
  if (!plusLayout) return shared;
96
98
  const stageOverlap = `${stageWidth + 20}px`;
99
+ const stageOverlaps = showStage && (!stageDrawer || stageDrawerOpen);
97
100
  const bodyStyle = {
98
101
  display: 'grid',
99
- '--plus-stage-overlap': showStage ? stageOverlap : '0px',
100
- '--plus-stage-tab-inset': showStage ? stageOverlap : '3rem'
102
+ '--plus-stage-overlap': stageOverlaps ? stageOverlap : '0px',
103
+ '--plus-stage-tab-inset': stageOverlaps ? stageOverlap : '3rem'
101
104
  };
102
105
  if (resizable) {
103
106
  bodyStyle['--plus-target-width'] = `${targetSplit.first}px`;
@@ -117,7 +120,7 @@ export function createStandardLayout(React, {styles, targetStyles, context, Layo
117
120
  columnSplitter,
118
121
  editorStyle: {gridArea: 'editor', minWidth: 0, minHeight: 0},
119
122
  stageColumnStyle: {display: 'contents'},
120
- stageClassName: `${styles.stagePanel} ${styles.floatingStage}${stageHidden ? ` ${styles.stageHidden}` : ''}`,
123
+ stageClassName: `${styles.stagePanel} ${styles.floatingStage}${stageDrawer ? ` ${styles.stageDrawer}` : ''}${stageHidden ? ` ${styles.stageHidden}` : ''}`,
121
124
  targetClassName: targetsPinned ? styles.standardTargets :
122
125
  `${styles.standardTargets} ${styles.retractedTargets}`,
123
126
  stageDisplayWidth: stageWidth,
@@ -142,7 +145,8 @@ export function createStandardLayout(React, {styles, targetStyles, context, Layo
142
145
  render: slots => render(applyStandardLayout(slots), showUndock)
143
146
  });
144
147
  return element(context.Provider, {value: {
145
- showUndock, plusLayout, targetsPinned, setTargetsPinned, stageHidden, setStageVisible: setShowStage, stageResize, stageActionMount, locale: props.locale
148
+ showUndock, plusLayout, targetsPinned, setTargetsPinned, stageHidden, setStageVisible: setShowStage, stageResize, stageActionMount,
149
+ stageDrawer: plusLayout && stageDrawer, setStageDrawerOpen, locale: props.locale
146
150
  }},
147
151
  Container ? element(Container, {locale: props.locale}, layout) : layout);
148
152
  }
@@ -192,7 +196,7 @@ export function createStandardLayout(React, {styles, targetStyles, context, Layo
192
196
  if (panel.current && !panel.current.hidden) panel.current.focus({preventScroll: true});
193
197
  });
194
198
  }
195
- }, edge === 'left' ? '›' : '⌃'),
199
+ }, {left: '›', right: '‹', bottom: '⌃'}[edge]),
196
200
  element('div', {className: styles.edgePanel, ref: panel, tabIndex: -1, hidden: !open}, children));
197
201
  }
198
202