@kubohiroya/turbowarp-plus-editor-layout 0.1.1 → 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,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