@weasel-js/labkit 1.2.0 → 1.3.0-pre.0
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/_dts/{DrawCommand-C_XboUpz.d.ts → DrawCommand-DBB45NfN.d.ts} +196 -52
- package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
- package/dist/_dts/index-DgcNdEdh.d.ts +314 -0
- package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
- package/dist/_dts/types-AHlQxBNN.d.ts +162 -0
- package/dist/_dts/{useTrialState-BMNIx3Cy.d.ts → useTrialState-CsGMjhu9.d.ts} +5 -87
- package/dist/canvas/index.d.ts +7 -3
- package/dist/canvas/index.js +2 -1
- package/dist/chrome/index.d.ts +86 -0
- package/dist/chrome/index.js +8 -0
- package/dist/chrome/index.js.map +1 -0
- package/dist/{chunk-VUU5UXHE.js → chunk-4TMMVIDM.js} +8 -5
- package/dist/chunk-4TMMVIDM.js.map +1 -0
- package/dist/{chunk-THBG7FQZ.js → chunk-HFDVGF4X.js} +15 -12
- package/dist/chunk-HFDVGF4X.js.map +1 -0
- package/dist/{chunk-574LJAV4.js → chunk-NS54R52R.js} +31 -21
- package/dist/chunk-NS54R52R.js.map +1 -0
- package/dist/{chunk-G5TJVQQT.js → chunk-O2BULFHX.js} +5 -14
- package/dist/chunk-O2BULFHX.js.map +1 -0
- package/dist/chunk-O7NVSCLN.js +266 -0
- package/dist/chunk-O7NVSCLN.js.map +1 -0
- package/dist/chunk-PO6L3NSH.js +366 -0
- package/dist/chunk-PO6L3NSH.js.map +1 -0
- package/dist/chunk-PQJ5B2U2.js +27 -0
- package/dist/chunk-PQJ5B2U2.js.map +1 -0
- package/dist/chunk-RVNN4CHQ.js +212 -0
- package/dist/chunk-RVNN4CHQ.js.map +1 -0
- package/dist/chunk-TLGRYALP.js +6151 -0
- package/dist/chunk-TLGRYALP.js.map +1 -0
- package/dist/{chunk-SFL7NFKN.js → chunk-V7PRSIRQ.js} +15 -7
- package/dist/chunk-V7PRSIRQ.js.map +1 -0
- package/dist/{chunk-BOHF3PQO.js → chunk-Z6HJ5FEM.js} +8111 -5190
- package/dist/chunk-Z6HJ5FEM.js.map +1 -0
- package/dist/chunk-ZZAYVX4X.js +505 -0
- package/dist/chunk-ZZAYVX4X.js.map +1 -0
- package/dist/controls/index.d.ts +19 -5
- package/dist/controls/index.js +3 -1
- package/dist/dragdrop/index.d.ts +4 -2
- package/dist/index.d.ts +246 -106
- package/dist/index.js +673 -559
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +5 -3
- package/dist/layers/index.js +2 -1
- package/dist/passthrough/weasel-canvas.d.ts +69 -13
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +3799 -3672
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/primitives/index.d.ts +102 -8
- package/dist/primitives/index.js +4 -1
- package/dist/state/index.d.ts +3 -2
- package/dist/state/index.js +2 -2
- package/dist/styles.css +542 -153
- package/dist/surface/index.js +2 -1
- package/dist/ui/layers/index.js +4 -3
- package/dist/undo/index.d.ts +4 -2
- package/package.json +16 -7
- package/src/canvas/AGENTS.md +4 -0
- package/src/canvas/CanvasStack.tsx +6 -2
- package/src/canvas/useLayerScheduler.ts +22 -15
- package/src/canvas/usePanZoom.test.ts +80 -0
- package/src/canvas/usePanZoom.ts +16 -2
- package/src/chrome/ChromeRegions.stories.tsx +106 -0
- package/src/chrome/builtins.test.ts +153 -0
- package/src/chrome/builtins.tsx +205 -0
- package/src/chrome/index.ts +25 -0
- package/src/chrome/merge.test.ts +51 -0
- package/src/chrome/merge.ts +52 -0
- package/src/chrome/regions/PaletteRegion.less +14 -0
- package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
- package/src/chrome/regions/PaletteRegion.tsx +41 -0
- package/src/chrome/regions/SidebarRegion.tsx +56 -0
- package/src/chrome/regions/StatusRegion.tsx +31 -0
- package/src/chrome/regions/TitleBarRegion.tsx +41 -0
- package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
- package/src/chrome/regions/ToolbarRegion.tsx +71 -0
- package/src/chrome/regions/ViewportRegion.less +42 -0
- package/src/chrome/regions/ViewportRegion.tsx +37 -0
- package/src/chrome/regions/regions.test.tsx +81 -0
- package/src/chrome/types.ts +127 -0
- package/src/config/builder.test.ts +87 -0
- package/src/config/builder.ts +224 -0
- package/src/config/fromConfigField.test.ts +93 -0
- package/src/config/fromConfigField.ts +74 -0
- package/src/config/index.ts +22 -0
- package/src/config/resolve.test.ts +120 -0
- package/src/config/resolve.ts +67 -0
- package/src/config/rules.ts +68 -0
- package/src/config/types.ts +108 -0
- package/src/config/useConfigSchema.ts +30 -0
- package/src/config/visible.ts +20 -0
- package/src/controls/ControlPanel.less +4 -58
- package/src/controls/ControlPanel.stories.tsx +86 -0
- package/src/controls/ControlPanel.test.tsx +223 -10
- package/src/controls/ControlPanel.tsx +202 -203
- package/src/dragdrop/DragDropRuntime.tsx +36 -18
- package/src/dragdrop/DragGhost.less +1 -1
- package/src/dragdrop/Palette.less +1 -1
- package/src/index.ts +25 -3
- package/src/instrument/SineWave.smoke.test.tsx +8 -1
- package/src/instrument/defineInstrument.test.ts +38 -1
- package/src/instrument/defineInstrument.ts +31 -4
- package/src/instrument/index.ts +1 -0
- package/src/instrument/types.ts +27 -4
- package/src/instrument/validateConfigSchema.test.ts +13 -3
- package/src/instrument/validateConfigSchema.ts +7 -10
- package/src/lab/Lab.chrome.test.tsx +65 -0
- package/src/lab/Lab.tsx +58 -15
- package/src/lab/LabContext.ts +6 -0
- package/src/lab/LabFullChrome.stories.tsx +308 -0
- package/src/lab/LabHeader.test.tsx +42 -0
- package/src/lab/LabHeader.tsx +57 -0
- package/src/lab/LabPalette.test.tsx +35 -0
- package/src/lab/LabPalette.tsx +34 -0
- package/src/lab/LabShell.less +50 -3
- package/src/lab/Workspace.less +1 -30
- package/src/lab/Workspace.test.tsx +20 -10
- package/src/lab/Workspace.tsx +5 -8
- package/src/lab/index.ts +2 -0
- package/src/layers/AGENTS.md +1 -1
- package/src/layers/LayerList.less +31 -7
- package/src/layers/LayerList.tsx +17 -4
- package/src/passthrough/weasel-ui.ts +23 -0
- package/src/primitives/DragHandleGlyph.tsx +29 -0
- package/src/primitives/FloatingPanel.less +28 -0
- package/src/primitives/FloatingPanel.stories.tsx +46 -0
- package/src/primitives/FloatingPanel.test.tsx +240 -0
- package/src/primitives/FloatingPanel.tsx +190 -0
- package/src/primitives/FpsMeter.test.tsx +44 -0
- package/src/primitives/FpsMeter.tsx +20 -9
- package/src/primitives/JobProgress.less +69 -0
- package/src/primitives/JobProgress.stories.tsx +48 -0
- package/src/primitives/JobProgress.tsx +65 -0
- package/src/primitives/Legend.less +47 -0
- package/src/primitives/Legend.stories.tsx +32 -0
- package/src/primitives/Legend.test.tsx +57 -0
- package/src/primitives/Legend.tsx +40 -0
- package/src/primitives/Sidebar.less +32 -2
- package/src/primitives/StatusBar.less +7 -0
- package/src/primitives/StatusBar.tsx +10 -3
- package/src/primitives/Toolbar.less +50 -3
- package/src/primitives/Toolbar.test.tsx +67 -1
- package/src/primitives/Toolbar.tsx +72 -6
- package/src/primitives/ZoomControl.less +31 -0
- package/src/primitives/ZoomControl.tsx +76 -0
- package/src/primitives/floating.entry.test.tsx +14 -0
- package/src/primitives/index.ts +10 -2
- package/src/primitives/useRovingTabIndex.test.ts +27 -0
- package/src/primitives/useRovingTabIndex.ts +79 -0
- package/src/state/store.ts +11 -3
- package/src/state/toolSlot.test.ts +36 -0
- package/src/state/types.ts +4 -0
- package/src/styles.less +6 -0
- package/src/surface/useTiledSurface.ts +14 -10
- package/src/test-setup.ts +26 -0
- package/src/theme/base.less +19 -2
- package/src/theme/interstellar.tokens.json +6 -2
- package/src/tools/types.ts +24 -0
- package/src/trial/Trial.config.test.tsx +113 -0
- package/src/trial/Trial.job.test.tsx +13 -15
- package/src/trial/Trial.less +115 -10
- package/src/trial/Trial.stories.tsx +1 -0
- package/src/trial/Trial.test.tsx +67 -96
- package/src/trial/Trial.tsx +80 -39
- package/src/trial/TrialChrome.tsx +99 -80
- package/src/trial/TrialDragContext.ts +10 -0
- package/src/trial/TrialTitleBar.tsx +41 -0
- package/src/trial/index.ts +1 -15
- package/src/trial/trialChrome.borders.test.ts +38 -0
- package/src/ui/layers/LayerStack.less +9 -9
- package/src/ui/layers/LayerStack.tsx +1 -13
- package/src/ui/properties/CurveField.less +2 -2
- package/src/ui/properties/CurveField.test.tsx +11 -11
- package/src/ui/properties/PropertyGroup.less +2 -2
- package/src/ui/properties/PropertyPanel.less +55 -41
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +4 -2
- package/src/ui/properties/storyLayouts.tsx +2 -2
- package/dist/_dts/index-JFAYj5Tv.d.ts +0 -144
- package/dist/_dts/types-x92Kfeme.d.ts +0 -62
- package/dist/chunk-574LJAV4.js.map +0 -1
- package/dist/chunk-5R2ATYPJ.js +0 -201
- package/dist/chunk-5R2ATYPJ.js.map +0 -1
- package/dist/chunk-73KA7WBO.js +0 -5992
- package/dist/chunk-73KA7WBO.js.map +0 -1
- package/dist/chunk-BOHF3PQO.js.map +0 -1
- package/dist/chunk-G5TJVQQT.js.map +0 -1
- package/dist/chunk-PMAU3SEE.js +0 -126
- package/dist/chunk-PMAU3SEE.js.map +0 -1
- package/dist/chunk-SFL7NFKN.js.map +0 -1
- package/dist/chunk-THBG7FQZ.js.map +0 -1
- package/dist/chunk-VUU5UXHE.js.map +0 -1
- package/src/instrument/capabilityDetector.test.ts +0 -64
- package/src/instrument/capabilityDetector.ts +0 -20
- package/src/trial/DefaultSidebar.tsx +0 -29
- package/src/trial/DefaultStatusBar.tsx +0 -19
- package/src/trial/DefaultToolbar.tsx +0 -92
- package/src/trial/slotTypes.ts +0 -54
package/src/trial/Trial.test.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from '@testing-library/react';
|
|
1
|
+
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import type { Instrument } from '../instrument/types';
|
|
@@ -6,92 +6,9 @@ import { Lab } from '../lab/Lab';
|
|
|
6
6
|
import { LabContext, type LabContextValue } from '../lab/LabContext';
|
|
7
7
|
import { LabStoreContext } from '../state/context';
|
|
8
8
|
import { createLabStore } from '../state/store';
|
|
9
|
-
import type { TrialRecord } from '../state/types';
|
|
10
|
-
import { DefaultToolbar } from './DefaultToolbar';
|
|
11
|
-
import type { TrialToolbarContext } from './slotTypes';
|
|
9
|
+
import type { SavedSnapshot, TrialRecord } from '../state/types';
|
|
12
10
|
import { TrialChrome } from './TrialChrome';
|
|
13
11
|
|
|
14
|
-
function makeCtx(overrides: Partial<TrialToolbarContext> = {}): TrialToolbarContext {
|
|
15
|
-
return {
|
|
16
|
-
trialId: 'ws-1',
|
|
17
|
-
instrumentName: 'Stub',
|
|
18
|
-
hasUndo: false,
|
|
19
|
-
canUndo: false,
|
|
20
|
-
canRedo: false,
|
|
21
|
-
undo: vi.fn(),
|
|
22
|
-
redo: vi.fn(),
|
|
23
|
-
zoom: 1,
|
|
24
|
-
setZoom: vi.fn(),
|
|
25
|
-
zoomIn: vi.fn(),
|
|
26
|
-
zoomOut: vi.fn(),
|
|
27
|
-
resetZoom: vi.fn(),
|
|
28
|
-
hasCanvas: false,
|
|
29
|
-
savedSnapshots: [],
|
|
30
|
-
saveSnapshot: vi.fn(),
|
|
31
|
-
loadSnapshot: vi.fn(),
|
|
32
|
-
clone: vi.fn(),
|
|
33
|
-
reset: vi.fn(),
|
|
34
|
-
close: vi.fn(),
|
|
35
|
-
isLastTrial: false,
|
|
36
|
-
...overrides,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
describe('<DefaultToolbar>', () => {
|
|
41
|
-
it('renders close button', () => {
|
|
42
|
-
render(<DefaultToolbar ctx={makeCtx()} />);
|
|
43
|
-
expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('disables close when isLastTrial is true', () => {
|
|
47
|
-
render(<DefaultToolbar ctx={makeCtx({ isLastTrial: true })} />);
|
|
48
|
-
expect(screen.getByRole('button', { name: /close/i })).toBeDisabled();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('omits undo/redo buttons when hasUndo is false', () => {
|
|
52
|
-
render(<DefaultToolbar ctx={makeCtx({ hasUndo: false })} />);
|
|
53
|
-
expect(screen.queryByRole('button', { name: /undo/i })).toBeNull();
|
|
54
|
-
expect(screen.queryByRole('button', { name: /redo/i })).toBeNull();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('shows undo/redo buttons when hasUndo is true', () => {
|
|
58
|
-
render(<DefaultToolbar ctx={makeCtx({ hasUndo: true, canUndo: true, canRedo: false })} />);
|
|
59
|
-
expect(screen.getByRole('button', { name: /undo/i })).not.toBeDisabled();
|
|
60
|
-
expect(screen.getByRole('button', { name: /redo/i })).toBeDisabled();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('omits zoom buttons when hasCanvas is false', () => {
|
|
64
|
-
render(<DefaultToolbar ctx={makeCtx({ hasCanvas: false })} />);
|
|
65
|
-
expect(screen.queryByTitle('Zoom in')).toBeNull();
|
|
66
|
-
expect(screen.queryByTitle('Zoom out')).toBeNull();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('shows zoom controls when hasCanvas is true', () => {
|
|
70
|
-
render(<DefaultToolbar ctx={makeCtx({ hasCanvas: true, zoom: 1.5 })} />);
|
|
71
|
-
expect(screen.getByTitle('Zoom in')).toBeInTheDocument();
|
|
72
|
-
expect(screen.getByTitle('Zoom out')).toBeInTheDocument();
|
|
73
|
-
expect(screen.getByText('150%')).toBeInTheDocument();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('shows load select when snapshots exist', () => {
|
|
77
|
-
const ctx = makeCtx({
|
|
78
|
-
savedSnapshots: [
|
|
79
|
-
{
|
|
80
|
-
id: 's1',
|
|
81
|
-
name: 'First',
|
|
82
|
-
trialId: 'ws-1',
|
|
83
|
-
instrumentName: 'Stub',
|
|
84
|
-
config: {},
|
|
85
|
-
state: {},
|
|
86
|
-
savedAt: 1,
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
|
-
});
|
|
90
|
-
render(<DefaultToolbar ctx={ctx} />);
|
|
91
|
-
expect(screen.getByRole('combobox', { name: /load snapshot/i })).toBeInTheDocument();
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
12
|
const stubInstrument: Instrument = {
|
|
96
13
|
name: 'Stub',
|
|
97
14
|
defaultConfig: () => ({}),
|
|
@@ -113,6 +30,7 @@ type ChromeProps = Parameters<typeof TrialChrome>[0];
|
|
|
113
30
|
function ChromeHarness({
|
|
114
31
|
children,
|
|
115
32
|
labOverrides,
|
|
33
|
+
instrument = stubInstrument,
|
|
116
34
|
...props
|
|
117
35
|
}: { children?: ReactNode; labOverrides?: Partial<LabContextValue> } & Partial<ChromeProps>) {
|
|
118
36
|
const store = createLabStore({
|
|
@@ -120,7 +38,7 @@ function ChromeHarness({
|
|
|
120
38
|
storage: { read: () => null, write: () => {} },
|
|
121
39
|
});
|
|
122
40
|
const labCtx: LabContextValue = {
|
|
123
|
-
instruments: [
|
|
41
|
+
instruments: [instrument],
|
|
124
42
|
trials: [stubRecord],
|
|
125
43
|
addTrial: vi.fn(),
|
|
126
44
|
cloneTrial: vi.fn(),
|
|
@@ -141,7 +59,7 @@ function ChromeHarness({
|
|
|
141
59
|
<TrialChrome
|
|
142
60
|
trialId="ws-1"
|
|
143
61
|
record={stubRecord}
|
|
144
|
-
instrument={
|
|
62
|
+
instrument={instrument}
|
|
145
63
|
isLastTrial={false}
|
|
146
64
|
{...props}
|
|
147
65
|
>
|
|
@@ -158,15 +76,47 @@ describe('<TrialChrome>', () => {
|
|
|
158
76
|
expect(screen.getByTestId('content')).toBeInTheDocument();
|
|
159
77
|
});
|
|
160
78
|
|
|
161
|
-
it('renders
|
|
79
|
+
it('renders the built-in trial actions', () => {
|
|
80
|
+
render(<ChromeHarness />);
|
|
81
|
+
expect(screen.getByRole('button', { name: 'Close trial' })).toBeInTheDocument();
|
|
82
|
+
expect(screen.getByRole('button', { name: 'Clone trial' })).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('disables close on the last trial', () => {
|
|
86
|
+
render(<ChromeHarness isLastTrial />);
|
|
87
|
+
expect(screen.getByRole('button', { name: /close/i })).toBeDisabled();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('omits undo and redo unless the instrument declares undo', () => {
|
|
162
91
|
render(<ChromeHarness />);
|
|
163
|
-
expect(screen.
|
|
92
|
+
expect(screen.queryByRole('button', { name: 'Undo' })).toBeNull();
|
|
93
|
+
render(
|
|
94
|
+
<ChromeHarness
|
|
95
|
+
instrument={{ ...stubInstrument, undo: {} }}
|
|
96
|
+
undoBindings={{ canUndo: true, canRedo: false, undo: vi.fn(), redo: vi.fn() }}
|
|
97
|
+
/>,
|
|
98
|
+
);
|
|
99
|
+
expect(screen.getByRole('button', { name: 'Undo' })).not.toBeDisabled();
|
|
100
|
+
expect(screen.getByRole('button', { name: 'Redo' })).toBeDisabled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('offers the snapshot loader once a snapshot exists', () => {
|
|
104
|
+
const snapshot: SavedSnapshot = {
|
|
105
|
+
id: 's1',
|
|
106
|
+
name: 'First',
|
|
107
|
+
trialId: 'ws-1',
|
|
108
|
+
instrumentName: 'Stub',
|
|
109
|
+
config: {},
|
|
110
|
+
state: {},
|
|
111
|
+
savedAt: 1,
|
|
112
|
+
};
|
|
113
|
+
render(<ChromeHarness labOverrides={{ savedSnapshots: [snapshot] }} />);
|
|
114
|
+
expect(screen.getByRole('button', { name: /load snapshot/i })).toBeInTheDocument();
|
|
164
115
|
});
|
|
165
116
|
|
|
166
|
-
it('renders
|
|
167
|
-
render(<ChromeHarness
|
|
168
|
-
expect(screen.
|
|
169
|
-
expect(screen.queryByRole('button', { name: /close/i })).toBeNull();
|
|
117
|
+
it('renders a consumer contribution alongside the built-ins', () => {
|
|
118
|
+
render(<ChromeHarness chrome={[{ id: 'mine', region: 'status', item: { text: 'ready' } }]} />);
|
|
119
|
+
expect(screen.getByText('ready')).toBeInTheDocument();
|
|
170
120
|
});
|
|
171
121
|
|
|
172
122
|
it('Cmd+S triggers saveSnapshot', () => {
|
|
@@ -178,10 +128,31 @@ describe('<TrialChrome>', () => {
|
|
|
178
128
|
});
|
|
179
129
|
});
|
|
180
130
|
|
|
181
|
-
describe('
|
|
131
|
+
describe('chrome regions in a mounted lab', () => {
|
|
132
|
+
function renderLabWith(instrument: Instrument) {
|
|
133
|
+
return render(<Lab title="T" instruments={[instrument]} defaultInstrument={instrument.name} />);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
it('puts zoom in the viewport region and not in the toolbar', () => {
|
|
137
|
+
renderLabWith({ ...stubInstrument, canvas: { layers: [] }, undo: {} });
|
|
138
|
+
const toolbar = document.querySelector('.lk-trial__toolbar') as HTMLElement;
|
|
139
|
+
const viewport = document.querySelector('.lk-viewport-controls') as HTMLElement;
|
|
140
|
+
expect(within(viewport).getByRole('button', { name: 'Zoom in' })).toBeInTheDocument();
|
|
141
|
+
expect(within(toolbar).queryByRole('button', { name: 'Zoom in' })).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('renders no undo group for an instrument that does not declare undo', () => {
|
|
145
|
+
renderLabWith({ ...stubInstrument, canvas: { layers: [] } });
|
|
146
|
+
expect(screen.queryByRole('button', { name: 'Undo' })).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('renders no viewport region for an instrument with no canvas', () => {
|
|
150
|
+
renderLabWith(stubInstrument);
|
|
151
|
+
expect(document.querySelector('.lk-viewport-controls')).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
|
|
182
154
|
it('Lab provides context for nested TrialChrome', () => {
|
|
183
|
-
|
|
184
|
-
// Lab seeds a trial; no chrome rendered without children — sanity check Lab mounts.
|
|
155
|
+
renderLabWith(stubInstrument);
|
|
185
156
|
expect(document.querySelector('.lk-lab')).toBeTruthy();
|
|
186
157
|
});
|
|
187
158
|
});
|
package/src/trial/Trial.tsx
CHANGED
|
@@ -2,14 +2,10 @@ import { type ReactNode, useContext, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import { useStore } from 'zustand/react';
|
|
3
3
|
import { CanvasStack } from '../canvas/CanvasStack';
|
|
4
4
|
import type { CanvasLayerDescriptor } from '../canvas/useLayerScheduler';
|
|
5
|
+
import type { TrialContribution } from '../chrome/types';
|
|
5
6
|
import { DragOverlay, useDragDrop } from '../dragdrop/DragDropRuntime';
|
|
6
7
|
import { Palette } from '../dragdrop/Palette';
|
|
7
|
-
import type {
|
|
8
|
-
Instrument,
|
|
9
|
-
LayerDescriptor,
|
|
10
|
-
PaletteItem,
|
|
11
|
-
RenderContext,
|
|
12
|
-
} from '../instrument/types';
|
|
8
|
+
import type { Instrument, LayerDescriptor, PaletteItem, RenderContext } from '../instrument/types';
|
|
13
9
|
import { useJob } from '../job/useJob';
|
|
14
10
|
import { useLabContext } from '../lab/LabContext';
|
|
15
11
|
import { LayerList } from '../layers/LayerList';
|
|
@@ -25,11 +21,16 @@ import { TrialChrome } from './TrialChrome';
|
|
|
25
21
|
/** Props for `<Trial>`. */
|
|
26
22
|
export interface TrialProps {
|
|
27
23
|
id: string;
|
|
24
|
+
/** Contributions the lab adds to this trial's chrome, merged after the
|
|
25
|
+
* instrument's own. */
|
|
26
|
+
chrome?: readonly TrialContribution[];
|
|
27
|
+
/** Built-in contribution ids to drop. Throws on an id that is not there. */
|
|
28
|
+
suppress?: readonly string[];
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/** Renders one trial from the lab store: looks up its record and
|
|
31
32
|
* instrument, and mounts the instrument inside the trial chrome. */
|
|
32
|
-
export function Trial({ id }: TrialProps) {
|
|
33
|
+
export function Trial({ id, chrome, suppress }: TrialProps) {
|
|
33
34
|
const lab = useLabContext();
|
|
34
35
|
const storeCtx = useContext(LabStoreContext);
|
|
35
36
|
if (!storeCtx) throw new Error('[labkit] <Trial> requires <LabStoreProvider>');
|
|
@@ -49,6 +50,8 @@ export function Trial({ id }: TrialProps) {
|
|
|
49
50
|
instrument={instrument}
|
|
50
51
|
store={storeCtx.store}
|
|
51
52
|
isLast={lab.trials.length <= 1}
|
|
53
|
+
chrome={chrome}
|
|
54
|
+
suppress={suppress}
|
|
52
55
|
/>
|
|
53
56
|
);
|
|
54
57
|
}
|
|
@@ -58,14 +61,19 @@ interface TrialRuntimeProps {
|
|
|
58
61
|
instrument: Instrument;
|
|
59
62
|
store: LabStore;
|
|
60
63
|
isLast: boolean;
|
|
64
|
+
chrome?: readonly TrialContribution[];
|
|
65
|
+
suppress?: readonly string[];
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
function TrialRuntime({ record, instrument, store, isLast }: TrialRuntimeProps) {
|
|
68
|
+
function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }: TrialRuntimeProps) {
|
|
64
69
|
const canvasContainerRef = useRef<HTMLDivElement | null>(null);
|
|
65
70
|
const updateTrialState = useStore(store, (s) => s.updateTrialState);
|
|
66
71
|
const updateTrialConfig = useStore(store, (s) => s.updateTrialConfig);
|
|
67
72
|
const updateTrialView = useStore(store, (s) => s.updateTrialView);
|
|
68
73
|
const updateTrialUndoStack = useStore(store, (s) => s.updateTrialUndoStack);
|
|
74
|
+
const labToolId = useStore(store, (s) => s.activeToolId);
|
|
75
|
+
const setLabTool = useStore(store, (s) => s.setLabTool);
|
|
76
|
+
const setTrialTool = useStore(store, (s) => s.setTrialTool);
|
|
69
77
|
|
|
70
78
|
const busRef = useRef<EventBus | null>(null);
|
|
71
79
|
if (busRef.current === null) busRef.current = createEventBus();
|
|
@@ -88,6 +96,17 @@ function TrialRuntime({ record, instrument, store, isLast }: TrialRuntimeProps)
|
|
|
88
96
|
|
|
89
97
|
const setView = (v: unknown): void => updateTrialView(record.id, v);
|
|
90
98
|
|
|
99
|
+
// A trial gets its own slot when its instrument declares tools; otherwise it
|
|
100
|
+
// reads the lab's. Which slot a change writes follows from the same thing.
|
|
101
|
+
const declaresTools = instrument.tools != null;
|
|
102
|
+
const resolvedToolId = declaresTools
|
|
103
|
+
? (record.activeToolId ?? instrument.tools?.initial ?? instrument.tools?.tools[0]?.id ?? null)
|
|
104
|
+
: labToolId;
|
|
105
|
+
const setActiveTool = (id: string): void => {
|
|
106
|
+
if (declaresTools) setTrialTool(record.id, id);
|
|
107
|
+
else setLabTool(id);
|
|
108
|
+
};
|
|
109
|
+
|
|
91
110
|
// `CanvasStack` and the zoom controls are 2D; a trial holding another view shape
|
|
92
111
|
// gets the default here and simply never renders them.
|
|
93
112
|
const view2d = as2DView(record.view);
|
|
@@ -127,6 +146,7 @@ function TrialRuntime({ record, instrument, store, isLast }: TrialRuntimeProps)
|
|
|
127
146
|
if (!view2d) return;
|
|
128
147
|
updateTrialView(record.id, { ...view2d, zoom: z });
|
|
129
148
|
},
|
|
149
|
+
activeToolId: resolvedToolId,
|
|
130
150
|
},
|
|
131
151
|
emit: (event) => {
|
|
132
152
|
snapshotIfNeeded(event);
|
|
@@ -175,7 +195,7 @@ function TrialRuntime({ record, instrument, store, isLast }: TrialRuntimeProps)
|
|
|
175
195
|
|
|
176
196
|
const layerDescriptors: LayerDescriptor[] = useMemo(() => {
|
|
177
197
|
if (!instrument.layers) return [];
|
|
178
|
-
return instrument.layers.ids.map((
|
|
198
|
+
return instrument.layers.ids.map((l) => (typeof l === 'string' ? { id: l, label: l } : l));
|
|
179
199
|
}, [instrument.layers]);
|
|
180
200
|
|
|
181
201
|
const dragDropResult = useDragDrop({
|
|
@@ -228,15 +248,13 @@ function TrialRuntime({ record, instrument, store, isLast }: TrialRuntimeProps)
|
|
|
228
248
|
let body: ReactNode;
|
|
229
249
|
if (instrument.canvas) {
|
|
230
250
|
body = (
|
|
231
|
-
<div
|
|
232
|
-
ref={canvasContainerRef}
|
|
233
|
-
className="lk-trial__canvas-host"
|
|
234
|
-
style={{ width: '100%', height: '100%', position: 'relative' }}
|
|
235
|
-
>
|
|
251
|
+
<div ref={canvasContainerRef} className="lk-trial__canvas-host">
|
|
236
252
|
<CanvasStack
|
|
237
253
|
layers={layersWithFeedback}
|
|
238
254
|
view={view2d ?? DEFAULT_VIEW}
|
|
239
255
|
onViewChange={setView}
|
|
256
|
+
minZoom={instrument.canvas.minZoom}
|
|
257
|
+
maxZoom={instrument.canvas.maxZoom}
|
|
240
258
|
>
|
|
241
259
|
{instrument.render(renderCtx)}
|
|
242
260
|
</CanvasStack>
|
|
@@ -247,26 +265,50 @@ function TrialRuntime({ record, instrument, store, isLast }: TrialRuntimeProps)
|
|
|
247
265
|
body = instrument.render(renderCtx);
|
|
248
266
|
}
|
|
249
267
|
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
268
|
+
const startDrag = dragDropResult.startDrag;
|
|
269
|
+
const paletteNode = useMemo(
|
|
270
|
+
() =>
|
|
271
|
+
paletteItems.length > 0 ? <Palette items={paletteItems} onDragStart={startDrag} /> : null,
|
|
272
|
+
[paletteItems, startDrag],
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const layerListNode = useMemo(
|
|
276
|
+
() =>
|
|
277
|
+
instrument.layers && layerDescriptors.length > 0 ? (
|
|
278
|
+
<LayerList
|
|
279
|
+
layers={layerDescriptors}
|
|
280
|
+
visibility={layerVisibility}
|
|
281
|
+
onReorder={(next) => {
|
|
282
|
+
setLayerOrder(next.map((l) => l.id));
|
|
283
|
+
bus.emit('layers.reorder');
|
|
284
|
+
}}
|
|
285
|
+
onToggle={(lid, visible) => {
|
|
286
|
+
setLayerVisibility((prev) => ({ ...prev, [lid]: visible }));
|
|
287
|
+
bus.emit('layers.toggle');
|
|
288
|
+
}}
|
|
289
|
+
/>
|
|
290
|
+
) : null,
|
|
291
|
+
[instrument.layers, layerDescriptors, layerVisibility, bus],
|
|
292
|
+
);
|
|
254
293
|
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
294
|
+
const extraChrome = useMemo<TrialContribution[]>(() => {
|
|
295
|
+
const out: TrialContribution[] = [];
|
|
296
|
+
if (paletteNode) {
|
|
297
|
+
out.push({
|
|
298
|
+
id: 'dragdrop-palette',
|
|
299
|
+
region: 'sidebar',
|
|
300
|
+
item: { title: 'Parts', body: paletteNode },
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (layerListNode) {
|
|
304
|
+
out.push({
|
|
305
|
+
id: 'layer-list',
|
|
306
|
+
region: 'sidebar',
|
|
307
|
+
item: { title: 'Layers', body: layerListNode },
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
}, [paletteNode, layerListNode]);
|
|
270
312
|
|
|
271
313
|
return (
|
|
272
314
|
<TrialChrome
|
|
@@ -276,12 +318,11 @@ function TrialRuntime({ record, instrument, store, isLast }: TrialRuntimeProps)
|
|
|
276
318
|
instrument={instrument}
|
|
277
319
|
isLastTrial={isLast}
|
|
278
320
|
undoBindings={undoBindings}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
321
|
+
trialChrome={extraChrome}
|
|
322
|
+
chrome={chrome}
|
|
323
|
+
suppress={suppress}
|
|
324
|
+
activeToolId={resolvedToolId}
|
|
325
|
+
setActiveTool={setActiveTool}
|
|
285
326
|
>
|
|
286
327
|
{body}
|
|
287
328
|
</TrialChrome>
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { type KeyboardEvent, type ReactNode, useContext, useMemo } from 'react';
|
|
2
2
|
import { useStore } from 'zustand/react';
|
|
3
|
+
import { builtinContributions } from '../chrome/builtins';
|
|
4
|
+
import { mergeContributions, suppressContributions } from '../chrome/merge';
|
|
5
|
+
import { PaletteRegion } from '../chrome/regions/PaletteRegion';
|
|
6
|
+
import { SidebarRegion } from '../chrome/regions/SidebarRegion';
|
|
7
|
+
import { StatusRegion } from '../chrome/regions/StatusRegion';
|
|
8
|
+
import { TitleBarRegion } from '../chrome/regions/TitleBarRegion';
|
|
9
|
+
import { ToolbarRegion } from '../chrome/regions/ToolbarRegion';
|
|
10
|
+
import { ViewportRegion } from '../chrome/regions/ViewportRegion';
|
|
11
|
+
import type { TrialChromeContext, TrialContribution, TrialRegion } from '../chrome/types';
|
|
12
|
+
import { useConfigSchema } from '../config/useConfigSchema';
|
|
3
13
|
import type { Instrument } from '../instrument/types';
|
|
4
14
|
import type { JobHandle } from '../job/types';
|
|
5
15
|
import { useLabContext } from '../lab/LabContext';
|
|
16
|
+
import { JobProgress } from '../primitives/JobProgress';
|
|
6
17
|
import { LabStoreContext } from '../state/context';
|
|
7
18
|
import type { TrialRecord } from '../state/types';
|
|
8
19
|
import { as2DView } from '../state/view';
|
|
9
|
-
import {
|
|
10
|
-
import { DefaultStatusBar } from './DefaultStatusBar';
|
|
11
|
-
import { DefaultToolbar } from './DefaultToolbar';
|
|
12
|
-
import type {
|
|
13
|
-
SidebarSlot,
|
|
14
|
-
StatusBarSlot,
|
|
15
|
-
ToolbarSlot,
|
|
16
|
-
TrialSidebarContext,
|
|
17
|
-
TrialStatusBarContext,
|
|
18
|
-
TrialToolbarContext,
|
|
19
|
-
} from './slotTypes';
|
|
20
|
+
import { TrialTitleBar } from './TrialTitleBar';
|
|
20
21
|
|
|
21
22
|
export interface UndoBindings {
|
|
22
23
|
canUndo: boolean;
|
|
@@ -31,30 +32,40 @@ export interface TrialChromeProps {
|
|
|
31
32
|
record: TrialRecord;
|
|
32
33
|
instrument: Instrument;
|
|
33
34
|
isLastTrial: boolean;
|
|
34
|
-
toolbar?: ToolbarSlot;
|
|
35
|
-
sidebar?: SidebarSlot;
|
|
36
|
-
statusBar?: StatusBarSlot;
|
|
37
35
|
undoBindings?: UndoBindings;
|
|
38
36
|
/** Supplied when the instrument declares a `job`. */
|
|
39
37
|
job?: JobHandle;
|
|
40
|
-
|
|
38
|
+
/** Contributions the trial runtime itself adds — the drag palette and the
|
|
39
|
+
* layer list, which depend on runtime state the instrument cannot reach. */
|
|
40
|
+
trialChrome?: readonly TrialContribution[];
|
|
41
|
+
/** Contributions from the lab, merged last. */
|
|
42
|
+
chrome?: readonly TrialContribution[];
|
|
43
|
+
/** Built-in contribution ids to drop. Throws on an id that is not there. */
|
|
44
|
+
suppress?: readonly string[];
|
|
45
|
+
/** The trial's resolved tool slot, and the setter that writes whichever
|
|
46
|
+
* slot it resolved to. `Trial` owns the resolution. */
|
|
47
|
+
activeToolId?: string | null;
|
|
48
|
+
setActiveTool?: (id: string) => void;
|
|
41
49
|
children: ReactNode;
|
|
42
50
|
}
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
const NO_OP = (): void => {};
|
|
53
|
+
|
|
54
|
+
/** The frame around a running instrument. Builds one chrome context, assembles
|
|
55
|
+
* the contributions the instrument, the runtime and the lab declare, and hands
|
|
56
|
+
* each region its slice. */
|
|
47
57
|
export function TrialChrome({
|
|
48
58
|
trialId,
|
|
49
59
|
record,
|
|
50
60
|
instrument,
|
|
51
61
|
isLastTrial,
|
|
52
|
-
toolbar,
|
|
53
|
-
sidebar,
|
|
54
|
-
statusBar,
|
|
55
62
|
undoBindings,
|
|
56
63
|
job,
|
|
57
|
-
|
|
64
|
+
trialChrome,
|
|
65
|
+
chrome,
|
|
66
|
+
suppress,
|
|
67
|
+
activeToolId = null,
|
|
68
|
+
setActiveTool = NO_OP,
|
|
58
69
|
children,
|
|
59
70
|
}: TrialChromeProps) {
|
|
60
71
|
const lab = useLabContext();
|
|
@@ -68,7 +79,9 @@ export function TrialChrome({
|
|
|
68
79
|
// and goes inert when the trial holds something else — an orbit, say.
|
|
69
80
|
const view2d = as2DView(record.view);
|
|
70
81
|
|
|
71
|
-
const
|
|
82
|
+
const configSchema = useConfigSchema(instrument);
|
|
83
|
+
|
|
84
|
+
const ctx = useMemo<TrialChromeContext>(() => {
|
|
72
85
|
const setZoom = (z: number): void => {
|
|
73
86
|
if (!view2d) return;
|
|
74
87
|
updateTrialView(trialId, { ...view2d, zoom: z });
|
|
@@ -76,32 +89,15 @@ export function TrialChrome({
|
|
|
76
89
|
return {
|
|
77
90
|
trialId,
|
|
78
91
|
instrumentName: record.instrumentName,
|
|
79
|
-
|
|
92
|
+
isLastTrial,
|
|
93
|
+
zoom: view2d ? view2d.zoom : null,
|
|
94
|
+
setZoom,
|
|
80
95
|
canUndo: undoBindings?.canUndo ?? false,
|
|
81
96
|
canRedo: undoBindings?.canRedo ?? false,
|
|
82
|
-
undo: undoBindings?.undo ??
|
|
83
|
-
redo: undoBindings?.redo ??
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
zoomIn: () => setZoom((view2d?.zoom ?? 1) * 1.25),
|
|
87
|
-
zoomOut: () => setZoom((view2d?.zoom ?? 1) * 0.8),
|
|
88
|
-
resetZoom: () => setZoom(1),
|
|
89
|
-
hasCanvas: instrument.canvas != null,
|
|
90
|
-
savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
|
|
91
|
-
saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
|
|
92
|
-
loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
|
|
93
|
-
clone: () => lab.cloneTrial(trialId),
|
|
94
|
-
reset: () => lab.resetTrial(trialId),
|
|
95
|
-
close: () => lab.closeTrial(trialId),
|
|
96
|
-
isLastTrial,
|
|
97
|
-
};
|
|
98
|
-
}, [trialId, record, instrument, lab, isLastTrial, updateTrialView, undoBindings, view2d]);
|
|
99
|
-
|
|
100
|
-
const sidebarCtx = useMemo<TrialSidebarContext>(
|
|
101
|
-
() => ({
|
|
102
|
-
trialId,
|
|
103
|
-
instrumentName: record.instrumentName,
|
|
104
|
-
configFields: instrument.configSchema?.() ?? [],
|
|
97
|
+
undo: undoBindings?.undo ?? NO_OP,
|
|
98
|
+
redo: undoBindings?.redo ?? NO_OP,
|
|
99
|
+
configSchema,
|
|
100
|
+
configFields: instrument.config ? [] : (instrument.configSchema?.() ?? []),
|
|
105
101
|
config: record.config,
|
|
106
102
|
setConfig: (key, value) => {
|
|
107
103
|
const prevConfig = record.config as Record<string, unknown>;
|
|
@@ -117,26 +113,58 @@ export function TrialChrome({
|
|
|
117
113
|
updateTrialState(trialId, nextState as never);
|
|
118
114
|
}
|
|
119
115
|
},
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
|
|
117
|
+
saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
|
|
118
|
+
loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
|
|
119
|
+
clone: () => lab.cloneTrial(trialId),
|
|
120
|
+
reset: () => lab.resetTrial(trialId),
|
|
121
|
+
close: () => lab.closeTrial(trialId),
|
|
122
|
+
activeToolId,
|
|
123
|
+
setActiveTool,
|
|
124
|
+
};
|
|
125
|
+
}, [
|
|
126
|
+
trialId,
|
|
127
|
+
record,
|
|
128
|
+
instrument,
|
|
129
|
+
lab,
|
|
130
|
+
isLastTrial,
|
|
131
|
+
updateTrialView,
|
|
132
|
+
updateTrialConfig,
|
|
133
|
+
updateTrialState,
|
|
134
|
+
undoBindings,
|
|
135
|
+
view2d,
|
|
136
|
+
activeToolId,
|
|
137
|
+
setActiveTool,
|
|
138
|
+
configSchema,
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const contributions = useMemo(
|
|
142
|
+
() =>
|
|
143
|
+
suppressContributions(
|
|
144
|
+
mergeContributions(
|
|
145
|
+
builtinContributions(instrument, ctx, lab.controls),
|
|
146
|
+
[...(instrument.chrome ?? [])],
|
|
147
|
+
[...(trialChrome ?? [])],
|
|
148
|
+
[...(chrome ?? [])],
|
|
149
|
+
),
|
|
150
|
+
suppress ?? [],
|
|
151
|
+
),
|
|
152
|
+
[instrument, ctx, trialChrome, chrome, suppress, lab.controls],
|
|
122
153
|
);
|
|
123
154
|
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
instrumentName: record.instrumentName,
|
|
127
|
-
zoom: view2d ? view2d.zoom : null,
|
|
128
|
-
};
|
|
155
|
+
const inRegion = (region: TrialRegion): TrialContribution[] =>
|
|
156
|
+
contributions.filter((c) => c.region === region);
|
|
129
157
|
|
|
130
|
-
const handleKeyDown = (e: KeyboardEvent<
|
|
158
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLElement>): void => {
|
|
131
159
|
const mod = e.metaKey || e.ctrlKey;
|
|
132
160
|
if (!mod) return;
|
|
133
161
|
if (e.key === 'z' || e.key === 'Z') {
|
|
134
162
|
e.preventDefault();
|
|
135
|
-
if (e.shiftKey)
|
|
136
|
-
else
|
|
163
|
+
if (e.shiftKey) ctx.redo();
|
|
164
|
+
else ctx.undo();
|
|
137
165
|
} else if (e.key === 's' || e.key === 'S') {
|
|
138
166
|
e.preventDefault();
|
|
139
|
-
|
|
167
|
+
ctx.saveSnapshot();
|
|
140
168
|
}
|
|
141
169
|
};
|
|
142
170
|
|
|
@@ -144,37 +172,28 @@ export function TrialChrome({
|
|
|
144
172
|
<section
|
|
145
173
|
className="lk-trial"
|
|
146
174
|
aria-label={`Trial ${record.instrumentName}`}
|
|
175
|
+
tabIndex={-1}
|
|
147
176
|
onKeyDown={handleKeyDown}
|
|
148
177
|
>
|
|
178
|
+
<TrialTitleBar title={record.instrumentName}>
|
|
179
|
+
<TitleBarRegion contributions={inRegion('titlebar')} ctx={ctx} />
|
|
180
|
+
</TrialTitleBar>
|
|
149
181
|
<div className="lk-trial__toolbar">
|
|
150
|
-
{toolbar
|
|
182
|
+
<ToolbarRegion contributions={inRegion('toolbar')} ctx={ctx} />
|
|
151
183
|
</div>
|
|
152
184
|
<div className="lk-trial__body">
|
|
185
|
+
<PaletteRegion contributions={inRegion('palette')} ctx={ctx} />
|
|
153
186
|
<div className="lk-trial__sidebar">
|
|
154
|
-
{sidebar
|
|
155
|
-
|
|
187
|
+
<SidebarRegion contributions={inRegion('sidebar')} ctx={ctx} />
|
|
188
|
+
</div>
|
|
189
|
+
<div className={`lk-trial__content${instrument.canvas ? ' lk-trial__content--flush' : ''}`}>
|
|
190
|
+
{children}
|
|
191
|
+
<ViewportRegion contributions={inRegion('viewport')} ctx={ctx} />
|
|
156
192
|
</div>
|
|
157
|
-
<div className="lk-trial__content">{children}</div>
|
|
158
193
|
</div>
|
|
159
194
|
<div className="lk-trial__status">
|
|
160
|
-
{job ?
|
|
161
|
-
|
|
162
|
-
<span className="lk-trial__job-count">
|
|
163
|
-
{job.done}
|
|
164
|
-
{job.total === null ? '' : ` / ${job.total}`}
|
|
165
|
-
</span>
|
|
166
|
-
{job.failures.length > 0 ? (
|
|
167
|
-
<span className="lk-trial__job-failures">{job.failures.length} failed</span>
|
|
168
|
-
) : null}
|
|
169
|
-
{job.error ? <span className="lk-trial__job-error">{job.error}</span> : null}
|
|
170
|
-
{job.status === 'running' ? (
|
|
171
|
-
<button type="button" className="lk-trial__job-cancel" onClick={job.cancel}>
|
|
172
|
-
Cancel
|
|
173
|
-
</button>
|
|
174
|
-
) : null}
|
|
175
|
-
</div>
|
|
176
|
-
) : null}
|
|
177
|
-
{statusBar ? statusBar(statusCtx) : <DefaultStatusBar ctx={statusCtx} />}
|
|
195
|
+
{job ? <JobProgress job={job} /> : null}
|
|
196
|
+
<StatusRegion contributions={inRegion('status')} ctx={ctx} />
|
|
178
197
|
</div>
|
|
179
198
|
</section>
|
|
180
199
|
);
|