pmx-canvas 0.7.1 → 0.7.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.
- package/.github/extensions/pmx-canvas/extension.mjs +79 -61
- package/CHANGELOG.md +29 -1
- package/Readme.md +48 -9
- package/dist/canvas/global.css +144 -45
- package/dist/canvas/index.js +90 -90
- package/dist/json-render/index.js +55 -55
- package/dist/types/client/canvas/ChoiceMenu.d.ts +13 -0
- package/dist/types/client/canvas/ExternalWriters.d.ts +3 -3
- package/dist/types/client/icons.d.ts +15 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +0 -12
- package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -0
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +1 -1
- package/dist/types/json-render/renderer/index.d.ts +2 -1
- package/dist/types/json-render/server.d.ts +2 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +2 -0
- package/dist/types/shared/delimited-text.d.ts +14 -0
- package/docs/ax-host-adapter-contract.md +13 -2
- package/docs/cli.md +9 -2
- package/docs/http-api.md +12 -8
- package/docs/mcp.md +8 -1
- package/docs/sdk.md +2 -2
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +41 -16
- package/skills/pmx-canvas/evals/evals.json +112 -2
- package/skills/pmx-canvas/references/full-reference.md +9 -4
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +17 -3
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +76 -4
- package/skills/pmx-canvas-testing/SKILL.md +49 -0
- package/src/cli/commands/backup.ts +15 -4
- package/src/cli/commands/boards.ts +16 -3
- package/src/client/canvas/AttentionHistory.tsx +2 -1
- package/src/client/canvas/BoardSwitcher.tsx +3 -3
- package/src/client/canvas/CanvasNode.tsx +211 -197
- package/src/client/canvas/CanvasViewport.tsx +6 -0
- package/src/client/canvas/ChoiceMenu.tsx +122 -0
- package/src/client/canvas/CommandBar.tsx +27 -25
- package/src/client/canvas/ContextPinBar.tsx +3 -1
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -3
- package/src/client/canvas/ExternalWriters.tsx +4 -3
- package/src/client/canvas/FeedbackDialog.tsx +12 -9
- package/src/client/canvas/IntentLayer.tsx +2 -2
- package/src/client/canvas/SelectionBar.tsx +2 -2
- package/src/client/canvas/SessionPanel.tsx +6 -4
- package/src/client/canvas/SessionReceipt.tsx +2 -1
- package/src/client/canvas/SnapshotPanel.tsx +3 -2
- package/src/client/canvas/ToolRail.tsx +48 -100
- package/src/client/canvas/TopBar.tsx +2 -2
- package/src/client/icons.tsx +118 -1
- package/src/client/nodes/BoardNode.tsx +2 -1
- package/src/client/nodes/ExtAppFrame.tsx +3 -1
- package/src/client/nodes/FileNode.tsx +4 -78
- package/src/client/nodes/ImageNode.tsx +2 -1
- package/src/client/nodes/MarkdownNode.tsx +2 -1
- package/src/client/nodes/McpAppNode.tsx +16 -0
- package/src/client/nodes/PromptNode.tsx +3 -1
- package/src/client/nodes/use-ax-surface-bridge.ts +4 -2
- package/src/client/nodes/viewer-runtime-state.ts +78 -0
- package/src/client/state/canvas-store.ts +3 -0
- package/src/client/state/intent-bridge.ts +14 -2
- package/src/client/theme/global.css +144 -45
- package/src/json-render/renderer/index.tsx +58 -38
- package/src/json-render/server.ts +8 -1
- package/src/mcp/server.ts +2 -2
- package/src/server/agent-presence.ts +17 -11
- package/src/server/board-export.ts +97 -32
- package/src/server/index.ts +9 -7
- package/src/server/operations/composites.ts +3 -2
- package/src/server/operations/ops/ax-presence.ts +7 -3
- package/src/server/operations/ops/boards.ts +12 -5
- package/src/server/operations/registry.ts +4 -13
- package/src/server/server.ts +13 -4
- package/src/shared/ax-surface-protocol.ts +3 -0
- package/src/shared/delimited-text.ts +75 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useState } from 'preact/hooks';
|
|
2
|
-
import { IconSteer } from '../icons';
|
|
2
|
+
import { IconSteer, IconClose } from '../icons';
|
|
3
3
|
import { contextPinnedNodeIds, nodes, toggleContextPin } from '../state/canvas-store';
|
|
4
4
|
import { steerableAgents } from '../state/presence-store';
|
|
5
5
|
import { sendSteering } from '../state/session-store';
|
|
6
6
|
import { modChord } from '../utils/platform';
|
|
7
|
+
import { ChoiceMenu } from './ChoiceMenu';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Command bar (rail-chrome-v2 phase 5): the human's steering surface while a
|
|
@@ -72,7 +73,7 @@ export function CommandBar() {
|
|
|
72
73
|
title="Remove from context"
|
|
73
74
|
onClick={() => toggleContextPin(id)}
|
|
74
75
|
>
|
|
75
|
-
|
|
76
|
+
<IconClose size={14} />
|
|
76
77
|
</button>
|
|
77
78
|
</span>
|
|
78
79
|
);
|
|
@@ -83,33 +84,34 @@ export function CommandBar() {
|
|
|
83
84
|
<div class="command-bar-composer">
|
|
84
85
|
<IconSteer size={15} class="command-bar-icon" />
|
|
85
86
|
{showPicker && (
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
title="Connected agents — steering reaches all of them unless you address one"
|
|
87
|
+
<ChoiceMenu
|
|
88
|
+
className="command-bar-target"
|
|
89
|
+
label="Steer which agent"
|
|
90
90
|
value={effectiveTarget ?? ''}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
91
|
+
above
|
|
92
|
+
onChange={(value) => setTarget(value || null)}
|
|
93
|
+
options={[
|
|
94
|
+
{ value: '', label: 'All agents' },
|
|
95
|
+
...agents.map((agent) => {
|
|
96
|
+
// Pump health at the point of choice: unclaimed steers already
|
|
97
|
+
// sitting in this agent's queue (a growing number means its loop
|
|
98
|
+
// is not polling), and proof-of-polling when its consumer key
|
|
99
|
+
// claimed recently — attached alone doesn't mean anyone reads
|
|
100
|
+
// the inbox.
|
|
101
|
+
const queued = agent.pendingSteers && agent.pendingSteers > 0 ? ` · ${agent.pendingSteers} queued` : '';
|
|
102
|
+
const polling = agent.polling ? ' · polling' : '';
|
|
103
|
+
return {
|
|
104
|
+
value: agent.value,
|
|
105
|
+
disabled: !agent.steerable,
|
|
106
|
+
label: agent.attached
|
|
105
107
|
? `${agent.label}${polling}${queued}`
|
|
106
108
|
: agent.steerable
|
|
107
109
|
? `${agent.label} · writer${polling}${queued}`
|
|
108
|
-
: `${agent.label} · no inbox
|
|
109
|
-
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
110
|
+
: `${agent.label} · no inbox`,
|
|
111
|
+
};
|
|
112
|
+
}),
|
|
113
|
+
]}
|
|
114
|
+
/>
|
|
113
115
|
)}
|
|
114
116
|
<input
|
|
115
117
|
class="command-bar-input"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { clearContextPins, contextPinnedNodeIds } from '../state/canvas-store';
|
|
2
2
|
import { attentionHistoryOpen } from '../state/attention-store';
|
|
3
|
+
import { IconClose } from '../icons';
|
|
3
4
|
|
|
4
5
|
export function ContextPinBar() {
|
|
5
6
|
const count = contextPinnedNodeIds.value.size;
|
|
@@ -15,8 +16,9 @@ export function ContextPinBar() {
|
|
|
15
16
|
class="context-pin-bar-btn context-pin-bar-clear"
|
|
16
17
|
onClick={clearContextPins}
|
|
17
18
|
title="Clear all context pins"
|
|
19
|
+
aria-label="Clear all context pins"
|
|
18
20
|
>
|
|
19
|
-
{
|
|
21
|
+
<IconClose size={14} />
|
|
20
22
|
</button>
|
|
21
23
|
</div>
|
|
22
24
|
);
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
pendingExpandedNodeCloseId,
|
|
26
26
|
toggleContextPin,
|
|
27
27
|
} from '../state/canvas-store';
|
|
28
|
-
import { getNodeIcon } from '../icons';
|
|
28
|
+
import { getNodeIcon, IconClose, IconExternalLink } from '../icons';
|
|
29
29
|
import { useFocusTrap } from './use-focus-trap';
|
|
30
30
|
import { TYPE_LABELS } from '../types';
|
|
31
31
|
import type { CanvasNodeState } from '../types';
|
|
@@ -316,7 +316,7 @@ export function ExpandedNodeOverlay() {
|
|
|
316
316
|
onClick={() => void openNodeAsSite(node)}
|
|
317
317
|
title="Open as a full-page site in the system browser"
|
|
318
318
|
>
|
|
319
|
-
Open in tab
|
|
319
|
+
Open in tab <IconExternalLink size={14} />
|
|
320
320
|
</button>
|
|
321
321
|
)}
|
|
322
322
|
{canPresent && (
|
|
@@ -331,7 +331,7 @@ export function ExpandedNodeOverlay() {
|
|
|
331
331
|
)}
|
|
332
332
|
</div>
|
|
333
333
|
<button type="button" class="expanded-close" onClick={handleClose} title="Close (Esc)" aria-label="Close">
|
|
334
|
-
|
|
334
|
+
<IconClose />
|
|
335
335
|
</button>
|
|
336
336
|
</div>
|
|
337
337
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'preact/hooks';
|
|
2
|
+
import { IconClose, IconExternalLink } from '../icons';
|
|
2
3
|
import type { AgentPresence } from '../../shared/agent-presence.js';
|
|
3
4
|
import { vetoGhostIntent } from '../state/intent-bridge';
|
|
4
5
|
import { type ClientIntent, intents, removeIntent } from '../state/intent-store';
|
|
@@ -149,7 +150,7 @@ export function ActivityFeed() {
|
|
|
149
150
|
activityFeedOpen.value = false;
|
|
150
151
|
}}
|
|
151
152
|
>
|
|
152
|
-
|
|
153
|
+
<IconClose />
|
|
153
154
|
</button>
|
|
154
155
|
</div>
|
|
155
156
|
<div class="activity-filters">
|
|
@@ -226,7 +227,7 @@ export function ActivityFeed() {
|
|
|
226
227
|
void startSession();
|
|
227
228
|
}}
|
|
228
229
|
>
|
|
229
|
-
Start session
|
|
230
|
+
Start session <IconExternalLink size={14} />
|
|
230
231
|
</button>
|
|
231
232
|
</div>
|
|
232
233
|
</div>
|
|
@@ -267,7 +268,7 @@ export function WritersSheet() {
|
|
|
267
268
|
<span class="writers-title">Connected writers</span>
|
|
268
269
|
<span class="writers-board">{sessionId.value.slice(0, 12)}</span>
|
|
269
270
|
<button type="button" class="activity-feed-close" aria-label="Close writers" onClick={close}>
|
|
270
|
-
|
|
271
|
+
<IconClose />
|
|
271
272
|
</button>
|
|
272
273
|
</div>
|
|
273
274
|
<div class="writers-body">
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useRef, useState } from 'preact/hooks';
|
|
2
2
|
import { useFocusTrap } from './use-focus-trap';
|
|
3
|
+
import { IconExternalLink } from '../icons';
|
|
4
|
+
import { ChoiceMenu } from './ChoiceMenu';
|
|
3
5
|
|
|
4
6
|
/** Draft only: GitHub owns authentication and the final public submission. */
|
|
5
7
|
export function FeedbackDialog({ onClose }: { onClose: () => void }) {
|
|
@@ -39,14 +41,15 @@ export function FeedbackDialog({ onClose }: { onClose: () => void }) {
|
|
|
39
41
|
public. Only what you enter below is included; no board contents, workspace paths, screenshots, or logs are
|
|
40
42
|
attached.
|
|
41
43
|
</p>
|
|
42
|
-
<
|
|
43
|
-
Feedback type
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
<div class="feedback-field">
|
|
45
|
+
<span>Feedback type</span>
|
|
46
|
+
<ChoiceMenu
|
|
47
|
+
label="Feedback type"
|
|
48
|
+
value={kind}
|
|
49
|
+
onChange={setKind}
|
|
50
|
+
options={['Bug report', 'Feature request', 'General feedback'].map((value) => ({ value, label: value }))}
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
50
53
|
<label>
|
|
51
54
|
Title
|
|
52
55
|
<input
|
|
@@ -77,7 +80,7 @@ export function FeedbackDialog({ onClose }: { onClose: () => void }) {
|
|
|
77
80
|
Cancel
|
|
78
81
|
</button>
|
|
79
82
|
<button type="submit" class="text-prompt-confirm" disabled={!title.trim() || !description.trim()}>
|
|
80
|
-
Continue on GitHub
|
|
83
|
+
Continue on GitHub <IconExternalLink size={14} />
|
|
81
84
|
</button>
|
|
82
85
|
</div>
|
|
83
86
|
</form>
|
|
@@ -2,7 +2,7 @@ import { useEffect } from 'preact/hooks';
|
|
|
2
2
|
import { nodes } from '../state/canvas-store';
|
|
3
3
|
import { hoveredIntentId, intents, removeIntent, type ClientIntent } from '../state/intent-store';
|
|
4
4
|
import { vetoGhostIntent } from '../state/intent-bridge';
|
|
5
|
-
import { getNodeIcon } from '../icons';
|
|
5
|
+
import { getNodeIcon, IconClose } from '../icons';
|
|
6
6
|
import { TYPE_LABELS } from '../types';
|
|
7
7
|
import type { CanvasNodeState } from '../types';
|
|
8
8
|
|
|
@@ -102,7 +102,7 @@ function GhostInfo({ intent }: { intent: ClientIntent }) {
|
|
|
102
102
|
void vetoGhostIntent(intent);
|
|
103
103
|
}}
|
|
104
104
|
>
|
|
105
|
-
|
|
105
|
+
<IconClose size={14} />
|
|
106
106
|
</button>
|
|
107
107
|
)}
|
|
108
108
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback } from 'preact/hooks';
|
|
2
|
-
import { IconArrange } from '../icons';
|
|
2
|
+
import { IconArrange, IconClose } from '../icons';
|
|
3
3
|
import { attentionHistoryOpen } from '../state/attention-store';
|
|
4
4
|
import {
|
|
5
5
|
addContextPins,
|
|
@@ -204,7 +204,7 @@ export function SelectionBar() {
|
|
|
204
204
|
</BarHint>
|
|
205
205
|
<BarHint label="Clear selection" shortcut="Esc" side="up">
|
|
206
206
|
<button type="button" class="selection-bar-icon" onClick={clearSelection} aria-label="Clear selection">
|
|
207
|
-
|
|
207
|
+
<IconClose size={14} />
|
|
208
208
|
</button>
|
|
209
209
|
</BarHint>
|
|
210
210
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from 'preact/hooks';
|
|
2
|
+
import { IconChevronRight, IconUndo } from '../icons';
|
|
2
3
|
import { useNow } from './use-now';
|
|
3
4
|
import { agentIdentityHue, agentPhaseLabel } from '../../shared/agent-presence.js';
|
|
4
5
|
import { formatCountdown, gateRemainingMs } from '../../shared/approval-gates.js';
|
|
@@ -301,7 +302,7 @@ function TimelineRow({ entry }: { entry: TimelineEntry }) {
|
|
|
301
302
|
data-testid="timeline-undo"
|
|
302
303
|
onClick={() => void undoAgentEdit(entry)}
|
|
303
304
|
>
|
|
304
|
-
|
|
305
|
+
<IconUndo size={14} /> undo this edit
|
|
305
306
|
</button>
|
|
306
307
|
)}
|
|
307
308
|
{undone && <div class="session-timeline-undone">undone · steering sent</div>}
|
|
@@ -387,8 +388,9 @@ export function SessionPanel() {
|
|
|
387
388
|
class="session-collapse"
|
|
388
389
|
onClick={() => setManuallyOpened(false)}
|
|
389
390
|
title="Collapse session panel"
|
|
391
|
+
aria-label="Collapse session panel"
|
|
390
392
|
>
|
|
391
|
-
|
|
393
|
+
<IconChevronRight />
|
|
392
394
|
</button>
|
|
393
395
|
</header>
|
|
394
396
|
|
|
@@ -406,7 +408,7 @@ export function SessionPanel() {
|
|
|
406
408
|
Agent edit on top: {undoable.body}
|
|
407
409
|
</span>
|
|
408
410
|
<button type="button" class="session-undo-btn" onClick={() => void undoAgentEdit(undoable)}>
|
|
409
|
-
|
|
411
|
+
<IconUndo size={14} /> Undo
|
|
410
412
|
</button>
|
|
411
413
|
</div>
|
|
412
414
|
);
|
|
@@ -455,7 +457,7 @@ export function SessionPanel() {
|
|
|
455
457
|
{summary}
|
|
456
458
|
</span>
|
|
457
459
|
<span class={`session-section-chevron${showList ? ' is-open' : ''}`} aria-hidden="true">
|
|
458
|
-
|
|
460
|
+
<IconChevronRight size={14} />
|
|
459
461
|
</span>
|
|
460
462
|
</button>
|
|
461
463
|
{showList &&
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from 'preact/hooks';
|
|
2
2
|
import { dismissSessionReceipt, sessionReceipt } from '../state/session-store';
|
|
3
|
+
import { IconClose } from '../icons';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Session receipt (rail-chrome-v2 phase 5, design item 2): a dismissible card
|
|
@@ -74,7 +75,7 @@ export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => voi
|
|
|
74
75
|
onClick={dismissSessionReceipt}
|
|
75
76
|
aria-label="Dismiss receipt"
|
|
76
77
|
>
|
|
77
|
-
|
|
78
|
+
<IconClose />
|
|
78
79
|
</button>
|
|
79
80
|
</div>
|
|
80
81
|
<div class="session-receipt-tiles">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { IconClose } from '../icons';
|
|
2
3
|
import {
|
|
3
4
|
type CanvasSnapshotInfo,
|
|
4
5
|
deleteSnapshot,
|
|
@@ -183,7 +184,7 @@ export function SnapshotPanel({
|
|
|
183
184
|
aria-label="Delete snapshot"
|
|
184
185
|
disabled={restoringId !== null}
|
|
185
186
|
>
|
|
186
|
-
|
|
187
|
+
<IconClose size={14} />
|
|
187
188
|
</button>
|
|
188
189
|
</>
|
|
189
190
|
);
|
|
@@ -195,7 +196,7 @@ export function SnapshotPanel({
|
|
|
195
196
|
<span class="snapshot-panel-sub">snapshots + sessions</span>
|
|
196
197
|
<span class="snapshot-panel-spacer" />
|
|
197
198
|
<button type="button" class="snapshot-panel-close" onClick={onClose} title="Close" aria-label="Close history">
|
|
198
|
-
|
|
199
|
+
<IconClose />
|
|
199
200
|
</button>
|
|
200
201
|
</div>
|
|
201
202
|
|
|
@@ -8,10 +8,7 @@ import {
|
|
|
8
8
|
IconLogo,
|
|
9
9
|
IconMinimap,
|
|
10
10
|
IconMoon,
|
|
11
|
-
IconNodeFile,
|
|
12
11
|
IconNodeGroup,
|
|
13
|
-
IconNodeHtml,
|
|
14
|
-
IconNodeImage,
|
|
15
12
|
IconNodeMarkdown,
|
|
16
13
|
IconNodeWebpage,
|
|
17
14
|
IconPen,
|
|
@@ -21,6 +18,7 @@ import {
|
|
|
21
18
|
IconSun,
|
|
22
19
|
IconTextAnnotation,
|
|
23
20
|
IconTrace,
|
|
21
|
+
IconUpload,
|
|
24
22
|
} from '../icons';
|
|
25
23
|
import {
|
|
26
24
|
autoArrange,
|
|
@@ -176,37 +174,33 @@ export function ToolRail({
|
|
|
176
174
|
const traceNodeCount = Array.from(nodes.value.values()).filter((n) => n.type === 'trace').length;
|
|
177
175
|
const edgeCount = edges.value.size;
|
|
178
176
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
|
179
|
-
const [openMenu, setOpenMenu] = useState<null | 'theme'
|
|
180
|
-
const [menuAnchor, setMenuAnchor] = useState<{
|
|
177
|
+
const [openMenu, setOpenMenu] = useState<null | 'theme'>(null);
|
|
178
|
+
const [menuAnchor, setMenuAnchor] = useState<{ bottom: number; right: number } | null>(null);
|
|
181
179
|
const railRef = useRef<HTMLDivElement>(null);
|
|
182
|
-
const
|
|
180
|
+
const fileInput = useRef<HTMLInputElement>(null);
|
|
183
181
|
|
|
184
|
-
const toggleMenu = (menu: 'theme'
|
|
182
|
+
const toggleMenu = (menu: 'theme') => (e: MouseEvent) => {
|
|
185
183
|
if (openMenu === menu) {
|
|
186
184
|
setOpenMenu(null);
|
|
187
185
|
return;
|
|
188
186
|
}
|
|
189
187
|
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
190
|
-
setMenuAnchor({
|
|
188
|
+
setMenuAnchor({ bottom: rect.bottom, right: rect.right });
|
|
191
189
|
setOpenMenu(menu);
|
|
192
190
|
};
|
|
193
191
|
|
|
194
192
|
// The rail scrolls (overflow-y:auto), so an absolutely-positioned popover
|
|
195
193
|
// would be clipped by it. Fixed positioning from the trigger's rect escapes
|
|
196
|
-
// the scroll container; the
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
right: 'auto',
|
|
207
|
-
}
|
|
208
|
-
: { position: 'fixed' as const, left: `${menuAnchor.right + 8}px`, top: `${menuAnchor.top}px`, right: 'auto' }
|
|
209
|
-
: undefined;
|
|
194
|
+
// the scroll container; the theme menu bottom-aligns so it grows upward.
|
|
195
|
+
const sideMenuStyle = menuAnchor
|
|
196
|
+
? {
|
|
197
|
+
position: 'fixed' as const,
|
|
198
|
+
left: `${menuAnchor.right + 8}px`,
|
|
199
|
+
bottom: `${window.innerHeight - menuAnchor.bottom}px`,
|
|
200
|
+
top: 'auto',
|
|
201
|
+
right: 'auto',
|
|
202
|
+
}
|
|
203
|
+
: undefined;
|
|
210
204
|
|
|
211
205
|
useEffect(() => {
|
|
212
206
|
if (!openMenu) return;
|
|
@@ -330,22 +324,20 @@ export function ToolRail({
|
|
|
330
324
|
>
|
|
331
325
|
<IconNodeMarkdown size={15} />
|
|
332
326
|
</RailButton>
|
|
333
|
-
<RailButton
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
<span aria-hidden="true">↥</span>
|
|
327
|
+
<RailButton
|
|
328
|
+
label="Attach files"
|
|
329
|
+
ariaLabel="Attach files"
|
|
330
|
+
detail="Images (PNG, JPEG, SVG…), Markdown and text/code files. PDF, Word, Excel, PowerPoint and OpenDocument use agent import with review."
|
|
331
|
+
onClick={() => fileInput.current?.click()}
|
|
332
|
+
>
|
|
333
|
+
<IconUpload size={15} />
|
|
341
334
|
</RailButton>
|
|
342
335
|
<input
|
|
343
|
-
ref={
|
|
336
|
+
ref={fileInput}
|
|
344
337
|
type="file"
|
|
345
338
|
hidden
|
|
346
339
|
multiple
|
|
347
|
-
|
|
348
|
-
aria-label="Attach documents"
|
|
340
|
+
aria-label="Attach files"
|
|
349
341
|
onChange={(event) => {
|
|
350
342
|
const area = canvasArea();
|
|
351
343
|
const view = viewport.value;
|
|
@@ -360,17 +352,6 @@ export function ToolRail({
|
|
|
360
352
|
<RailButton label="Webpage" shortcut="W" onClick={() => promptedCreate('webpage')}>
|
|
361
353
|
<IconNodeWebpage size={15} />
|
|
362
354
|
</RailButton>
|
|
363
|
-
<RailButton
|
|
364
|
-
label="HTML surface"
|
|
365
|
-
shortcut="H"
|
|
366
|
-
onClick={() =>
|
|
367
|
-
void createNodeInView({ type: 'html', title: 'HTML surface' }).catch((error) =>
|
|
368
|
-
logRailError('create html', error),
|
|
369
|
-
)
|
|
370
|
-
}
|
|
371
|
-
>
|
|
372
|
-
<IconNodeHtml />
|
|
373
|
-
</RailButton>
|
|
374
355
|
<RailButton
|
|
375
356
|
label="Group"
|
|
376
357
|
shortcut="G"
|
|
@@ -382,61 +363,28 @@ export function ToolRail({
|
|
|
382
363
|
>
|
|
383
364
|
<IconNodeGroup size={15} />
|
|
384
365
|
</RailButton>
|
|
385
|
-
<
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
{
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
onClick={() => {
|
|
408
|
-
onSetAnnotationTool(annotationTool === 'pen' ? null : 'pen');
|
|
409
|
-
setOpenMenu(null);
|
|
410
|
-
}}
|
|
411
|
-
>
|
|
412
|
-
<IconPen />
|
|
413
|
-
<span>{annotationTool === 'pen' ? 'Stop annotating' : 'Draw (A)'}</span>
|
|
414
|
-
</button>
|
|
415
|
-
<button
|
|
416
|
-
type="button"
|
|
417
|
-
class={`toolbar-menu-item${annotationTool === 'text' ? ' active' : ''}`}
|
|
418
|
-
onClick={() => {
|
|
419
|
-
onSetAnnotationTool(annotationTool === 'text' ? null : 'text');
|
|
420
|
-
setOpenMenu(null);
|
|
421
|
-
}}
|
|
422
|
-
>
|
|
423
|
-
<IconTextAnnotation />
|
|
424
|
-
<span>{annotationTool === 'text' ? 'Stop text notes' : 'Text note'}</span>
|
|
425
|
-
</button>
|
|
426
|
-
<button
|
|
427
|
-
type="button"
|
|
428
|
-
class={`toolbar-menu-item${annotationTool === 'eraser' ? ' active' : ''}`}
|
|
429
|
-
onClick={() => {
|
|
430
|
-
onSetAnnotationTool(annotationTool === 'eraser' ? null : 'eraser');
|
|
431
|
-
setOpenMenu(null);
|
|
432
|
-
}}
|
|
433
|
-
>
|
|
434
|
-
<IconEraser />
|
|
435
|
-
<span>{annotationTool === 'eraser' ? 'Stop erasing' : 'Eraser'}</span>
|
|
436
|
-
</button>
|
|
437
|
-
</div>
|
|
438
|
-
)}
|
|
439
|
-
</span>
|
|
366
|
+
<RailButton
|
|
367
|
+
label="Draw"
|
|
368
|
+
shortcut="A"
|
|
369
|
+
active={annotationTool === 'pen'}
|
|
370
|
+
onClick={() => onSetAnnotationTool(annotationTool === 'pen' ? null : 'pen')}
|
|
371
|
+
>
|
|
372
|
+
<IconPen />
|
|
373
|
+
</RailButton>
|
|
374
|
+
<RailButton
|
|
375
|
+
label="Text note"
|
|
376
|
+
active={annotationTool === 'text'}
|
|
377
|
+
onClick={() => onSetAnnotationTool(annotationTool === 'text' ? null : 'text')}
|
|
378
|
+
>
|
|
379
|
+
<IconTextAnnotation />
|
|
380
|
+
</RailButton>
|
|
381
|
+
<RailButton
|
|
382
|
+
label="Eraser"
|
|
383
|
+
active={annotationTool === 'eraser'}
|
|
384
|
+
onClick={() => onSetAnnotationTool(annotationTool === 'eraser' ? null : 'eraser')}
|
|
385
|
+
>
|
|
386
|
+
<IconEraser />
|
|
387
|
+
</RailButton>
|
|
440
388
|
|
|
441
389
|
<div class="rail-spacer" />
|
|
442
390
|
|
|
@@ -484,7 +432,7 @@ export function ToolRail({
|
|
|
484
432
|
{canvasThemeScheme(activeTheme) === 'dark' ? <IconSun /> : <IconMoon />}
|
|
485
433
|
</RailButton>
|
|
486
434
|
{openMenu === 'theme' && (
|
|
487
|
-
<div class="toolbar-menu" style={sideMenuStyle
|
|
435
|
+
<div class="toolbar-menu" style={sideMenuStyle} role="menu" aria-label="Theme">
|
|
488
436
|
{CANVAS_THEMES.map((name) => (
|
|
489
437
|
<button
|
|
490
438
|
key={name}
|
|
@@ -4,7 +4,7 @@ import { BoardSwitcher } from './BoardSwitcher';
|
|
|
4
4
|
import type { ComponentChildren } from 'preact';
|
|
5
5
|
import { presenting } from '../state/presentation';
|
|
6
6
|
import { useEffect, useState } from 'preact/hooks';
|
|
7
|
-
import { IconFitAll, IconZoomIn, IconZoomOut } from '../icons';
|
|
7
|
+
import { IconFitAll, IconZoomIn, IconZoomOut, IconClose } from '../icons';
|
|
8
8
|
import {
|
|
9
9
|
animateViewport,
|
|
10
10
|
connectionStatus,
|
|
@@ -121,7 +121,7 @@ function AgentChip() {
|
|
|
121
121
|
void endSession({ source: session.source, agentId: session.agentId });
|
|
122
122
|
}}
|
|
123
123
|
>
|
|
124
|
-
|
|
124
|
+
<IconClose size={14} />
|
|
125
125
|
</button>
|
|
126
126
|
</span>
|
|
127
127
|
</BarHint>
|