@aryacode/dsh-aryacode-polish 0.9.6 → 0.11.2
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/CHANGELOG.md +41 -0
- package/README.md +1 -1
- package/client/workspace.js +393 -75
- package/docs/better-workspace.md +44 -0
- package/lib/client.js +394 -76
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.2] - 2026-10-28
|
|
4
|
+
|
|
5
|
+
### Verified
|
|
6
|
+
|
|
7
|
+
- Drag reorder now has live proof, not just unit tests: with two workspaces in the
|
|
8
|
+
Dev AryaCode desktop, dropping the second workspace on the first reordered them
|
|
9
|
+
(`workspaces.insertBefore`), and the new order survived a full app restart.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- A session is never promised a move the Host cannot make. The remote workspace
|
|
14
|
+
namespace exposes `create`, `rename`, `delete`, `insertBefore`,
|
|
15
|
+
`insertSessionBefore`, `archiveSession`, `unarchiveSession`, `pinSession` and
|
|
16
|
+
`unpinSession` — and no `attachSession` or `detachSession` — while a workspace's own
|
|
17
|
+
`insertSessionBefore` only reorders the sessions already inside it (it rejects a
|
|
18
|
+
session it does not hold). So dropping a session on another workspace is refused
|
|
19
|
+
with a plain explanation instead of firing a call that fails, and the session menu
|
|
20
|
+
no longer offers "Move to <workspace>".
|
|
21
|
+
|
|
22
|
+
## [0.10.0] - 2026-10-28
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- A filter box in the workspaces header: it narrows workspaces and sessions, opens
|
|
27
|
+
every group it keeps so a match is never hidden, and reports "n of m sessions".
|
|
28
|
+
`/` focuses it from anywhere in the app and Escape clears it.
|
|
29
|
+
- Pinned sessions are hoisted to the top of their workspace with a marker, and a
|
|
30
|
+
running session shows a dot, so the row reads before you click it.
|
|
31
|
+
- Drag and drop: a workspace row dropped on another moves in front of it
|
|
32
|
+
(`workspaces.insertBefore`), a session row dropped on a sibling reorders it
|
|
33
|
+
(`workspaces.insertSessionBefore`); a session refuses to leave its workspace.
|
|
34
|
+
- Keyboard walking: arrow keys move a highlighted row, Enter opens a session or
|
|
35
|
+
folds a workspace, Escape closes the menu, clears the filter, then drops the cursor.
|
|
36
|
+
- Row actions appear on hover or on the keyboard cursor instead of always, and a
|
|
37
|
+
header control folds or expands every workspace at once.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- The tree no longer renders every "..." button permanently, which was what made a
|
|
42
|
+
three-row sidebar look like a nine-row one.
|
|
43
|
+
|
|
3
44
|
## [0.9.6] - 2026-10-28
|
|
4
45
|
|
|
5
46
|
### Fixed
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ installed into and patches nothing. It is one package, one Plugins row, and one
|
|
|
7
7
|
switch per feature, so a refinement that turns out to be wrong for a machine can
|
|
8
8
|
be turned off without uninstalling anything.
|
|
9
9
|
|
|
10
|
-
Status: **
|
|
10
|
+
Status: **eight features shipped.** The version is the package's, and the card
|
|
11
11
|
header already shows it.
|
|
12
12
|
|
|
13
13
|
## What it is
|
package/client/workspace.js
CHANGED
|
@@ -328,6 +328,111 @@ export function workspaceLabel(workspace) {
|
|
|
328
328
|
return words.charAt(0).toUpperCase() + words.slice(1)
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
+
export function filterTree(rows, query, pinned) {
|
|
332
|
+
const needle = String(query || '').trim().toLowerCase()
|
|
333
|
+
const ordered = rows.map(({ workspace, sessions }) => {
|
|
334
|
+
const hoisted = []
|
|
335
|
+
const rest = []
|
|
336
|
+
;(sessions || []).forEach(row => {
|
|
337
|
+
if (pinned && pinned.has(row.id)) hoisted.push(row)
|
|
338
|
+
else rest.push(row)
|
|
339
|
+
})
|
|
340
|
+
return { workspace, sessions: hoisted.concat(rest) }
|
|
341
|
+
})
|
|
342
|
+
if (!needle) return ordered
|
|
343
|
+
const matched = []
|
|
344
|
+
ordered.forEach(({ workspace, sessions }) => {
|
|
345
|
+
const label = workspaceLabel(workspace).toLowerCase()
|
|
346
|
+
if (label.includes(needle)) {
|
|
347
|
+
matched.push({ workspace, sessions })
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
const hits = sessions.filter(row => String(row.displayTitle || row.id).toLowerCase().includes(needle))
|
|
351
|
+
if (hits.length > 0) matched.push({ workspace, sessions: hits })
|
|
352
|
+
})
|
|
353
|
+
return matched
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The rows a keyboard walk visits, in the order they are drawn. A filter opens every
|
|
358
|
+
* group it kept, because a match you cannot see is not a match.
|
|
359
|
+
*/
|
|
360
|
+
export function flattenRows(rows, expanded, query) {
|
|
361
|
+
const filtering = String(query || '').trim().length > 0
|
|
362
|
+
const flat = []
|
|
363
|
+
rows.forEach(({ workspace, sessions }) => {
|
|
364
|
+
flat.push({ kind: 'workspace', id: workspace.workspaceId, workspaceId: workspace.workspaceId })
|
|
365
|
+
const open = filtering || expanded[workspace.workspaceId] !== false
|
|
366
|
+
if (!open) return
|
|
367
|
+
;(sessions || []).forEach(row => {
|
|
368
|
+
flat.push({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId })
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
return flat
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function moveBefore(list, movingId, beforeId) {
|
|
375
|
+
const from = list.findIndex(item => item.id === movingId)
|
|
376
|
+
const to = list.findIndex(item => item.id === beforeId)
|
|
377
|
+
if (from < 0 || to < 0 || from === to) return list
|
|
378
|
+
const next = list.slice()
|
|
379
|
+
const [moving] = next.splice(from, 1)
|
|
380
|
+
const target = next.findIndex(item => item.id === beforeId)
|
|
381
|
+
next.splice(target < 0 ? next.length : target, 0, moving)
|
|
382
|
+
return next
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const WS_FILTER = {
|
|
386
|
+
margin: '2px 2px 6px',
|
|
387
|
+
padding: '4px 8px',
|
|
388
|
+
border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
|
|
389
|
+
borderRadius: 'var(--dsw-radius-sm)',
|
|
390
|
+
background: 'transparent',
|
|
391
|
+
color: 'var(--dsw-alias-label-primary, inherit)',
|
|
392
|
+
font: 'inherit',
|
|
393
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 2px)',
|
|
394
|
+
outline: 'none',
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const WS_ROW_ACTION = {
|
|
398
|
+
flex: 'none',
|
|
399
|
+
width: '20px',
|
|
400
|
+
height: '20px',
|
|
401
|
+
display: 'inline-flex',
|
|
402
|
+
alignItems: 'center',
|
|
403
|
+
justifyContent: 'center',
|
|
404
|
+
padding: 0,
|
|
405
|
+
border: 'none',
|
|
406
|
+
borderRadius: 'var(--dsw-radius-sm)',
|
|
407
|
+
background: 'transparent',
|
|
408
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
409
|
+
cursor: 'pointer',
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const WS_DOT = {
|
|
413
|
+
flex: 'none',
|
|
414
|
+
width: '6px',
|
|
415
|
+
height: '6px',
|
|
416
|
+
borderRadius: '50%',
|
|
417
|
+
marginRight: '2px',
|
|
418
|
+
background: 'var(--dsw-alias-label-success, #12b76a)',
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const WS_PIN = {
|
|
422
|
+
flex: 'none',
|
|
423
|
+
fontSize: '9px',
|
|
424
|
+
lineHeight: 1,
|
|
425
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const WS_PIN_COUNT = WS_PIN
|
|
429
|
+
|
|
430
|
+
const WS_COUNT_NOTE = {
|
|
431
|
+
margin: '2px 4px 0',
|
|
432
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 3px)',
|
|
433
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
434
|
+
}
|
|
435
|
+
|
|
331
436
|
export function BetterWorkspace(props) {
|
|
332
437
|
const services = props.services || {}
|
|
333
438
|
const { ctx, slots } = services
|
|
@@ -337,6 +442,12 @@ export function BetterWorkspace(props) {
|
|
|
337
442
|
const [armed, setArmed] = React.useState(null)
|
|
338
443
|
const [adding, setAdding] = React.useState(false)
|
|
339
444
|
const [notice, setNotice] = React.useState('')
|
|
445
|
+
const [query, setQuery] = React.useState('')
|
|
446
|
+
const [cursor, setCursor] = React.useState(-1)
|
|
447
|
+
const [hover, setHover] = React.useState(null)
|
|
448
|
+
const [drag, setDrag] = React.useState(null)
|
|
449
|
+
const [dropping, setDropping] = React.useState(null)
|
|
450
|
+
const filterRef = React.useRef(null)
|
|
340
451
|
|
|
341
452
|
const workspaceState = useLive(services.workspacesLive)
|
|
342
453
|
const sessionState = useLive(services.sessionsLive)
|
|
@@ -344,10 +455,36 @@ export function BetterWorkspace(props) {
|
|
|
344
455
|
const sessionList = sessionState && sessionState.ids && sessionState.byId
|
|
345
456
|
? sessionState.ids.map(id => sessionState.byId[id]).filter(Boolean)
|
|
346
457
|
: (sessionState && (sessionState.items || sessionState.sessions)) || []
|
|
347
|
-
const
|
|
458
|
+
const pinned = React.useMemo(
|
|
459
|
+
() => new Set((workspaceState && workspaceState.pinnedSessionIds) || []),
|
|
460
|
+
[workspaceState],
|
|
461
|
+
)
|
|
462
|
+
const allRows = workspaceRows(workspaceList, sessionList)
|
|
463
|
+
const rows = filterTree(allRows, query, pinned)
|
|
464
|
+
const walk = flattenRows(rows, expanded, query)
|
|
348
465
|
const runtime = React.useMemo(() => menuRuntime(ctx, slots, standard), [ctx, slots, standard.wide])
|
|
349
466
|
const RowBoundary = React.useMemo(() => rowBoundary(), [])
|
|
350
467
|
const entries = React.useMemo(() => rowMenuEntries(slots), [slots, workspaceList.length, sessionList.length])
|
|
468
|
+
const totalCount = allRows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
469
|
+
const shownCount = rows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
470
|
+
const allFolded = rows.length > 0 && rows.every(row => expanded[row.workspace.workspaceId] === false)
|
|
471
|
+
|
|
472
|
+
React.useEffect(() => {
|
|
473
|
+
if (cursor >= walk.length) setCursor(walk.length - 1)
|
|
474
|
+
}, [walk.length, cursor])
|
|
475
|
+
|
|
476
|
+
React.useEffect(() => {
|
|
477
|
+
const onShortcut = (event) => {
|
|
478
|
+
const node = event.target
|
|
479
|
+
const typing = node && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA' || node.isContentEditable)
|
|
480
|
+
if (event.key === '/' && !typing) {
|
|
481
|
+
event.preventDefault()
|
|
482
|
+
if (filterRef.current && filterRef.current.focus) filterRef.current.focus()
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
document.addEventListener('keydown', onShortcut)
|
|
486
|
+
return () => document.removeEventListener('keydown', onShortcut)
|
|
487
|
+
}, [])
|
|
351
488
|
|
|
352
489
|
const openMenu = (event, row, workspace) => {
|
|
353
490
|
event.preventDefault()
|
|
@@ -360,9 +497,79 @@ export function BetterWorkspace(props) {
|
|
|
360
497
|
setArmed(null)
|
|
361
498
|
setMenu({ kind: 'workspace', workspace, anchor: { top: event.clientY, left: event.clientX } })
|
|
362
499
|
}
|
|
363
|
-
|
|
364
500
|
const closeMenu = React.useCallback(() => setMenu(null), [])
|
|
365
501
|
|
|
502
|
+
const toggleWorkspace = (workspaceId) => {
|
|
503
|
+
setExpanded((prev) => {
|
|
504
|
+
const next = Object.assign({}, prev)
|
|
505
|
+
next[workspaceId] = prev[workspaceId] === false
|
|
506
|
+
return next
|
|
507
|
+
})
|
|
508
|
+
}
|
|
509
|
+
const foldAll = () => {
|
|
510
|
+
if (allFolded) { setExpanded({}); return }
|
|
511
|
+
const next = {}
|
|
512
|
+
rows.forEach((row) => { next[row.workspace.workspaceId] = false })
|
|
513
|
+
setExpanded(next)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const reorderWorkspace = async (movingId, beforeId) => {
|
|
517
|
+
if (!services.reorderWorkspace || !movingId || movingId === beforeId) return
|
|
518
|
+
try { await services.reorderWorkspace(movingId, beforeId) } catch { setNotice('Could not reorder workspaces.') }
|
|
519
|
+
}
|
|
520
|
+
const reorderSession = async (workspaceId, movingId, beforeId) => {
|
|
521
|
+
if (!services.reorderSession || !movingId || movingId === beforeId) return
|
|
522
|
+
try { await services.reorderSession(workspaceId, movingId, beforeId) } catch { setNotice('Could not reorder sessions.') }
|
|
523
|
+
}
|
|
524
|
+
const dropOnWorkspace = (targetId) => {
|
|
525
|
+
const moving = drag
|
|
526
|
+
setDrag(null)
|
|
527
|
+
setDropping(null)
|
|
528
|
+
if (!moving || moving.kind !== 'workspace') return
|
|
529
|
+
void reorderWorkspace(moving.id, targetId)
|
|
530
|
+
}
|
|
531
|
+
const dropOnSession = (workspaceId, targetId) => {
|
|
532
|
+
const moving = drag
|
|
533
|
+
setDrag(null)
|
|
534
|
+
setDropping(null)
|
|
535
|
+
if (!moving || moving.kind !== 'session' || moving.id === targetId) return
|
|
536
|
+
if (moving.workspaceId !== workspaceId) {
|
|
537
|
+
// A session cannot change workspace from a plugin: the Host's remote
|
|
538
|
+
// namespace exposes create/rename/delete/insertBefore/insertSessionBefore
|
|
539
|
+
// and no attachSession or detachSession, and a workspace's own
|
|
540
|
+
// insertSessionBefore only reorders the sessions already in it.
|
|
541
|
+
setNotice('A session stays in its workspace; drag it within that workspace to reorder.')
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
void reorderSession(workspaceId, moving.id, targetId)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const activate = (item) => {
|
|
548
|
+
if (!item) return
|
|
549
|
+
if (item.kind === 'workspace') toggleWorkspace(item.workspaceId)
|
|
550
|
+
else services.openSession(item.id)
|
|
551
|
+
}
|
|
552
|
+
const onKeyDown = (event) => {
|
|
553
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
554
|
+
if (walk.length === 0) return
|
|
555
|
+
event.preventDefault()
|
|
556
|
+
const step = event.key === 'ArrowDown' ? 1 : -1
|
|
557
|
+
setCursor(Math.min(walk.length - 1, Math.max(0, cursor + step)))
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
if (event.key === 'Enter') {
|
|
561
|
+
if (cursor < 0) return
|
|
562
|
+
event.preventDefault()
|
|
563
|
+
activate(walk[cursor])
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
if (event.key === 'Escape') {
|
|
567
|
+
if (query) { setQuery(''); return }
|
|
568
|
+
if (menu) { closeMenu(); return }
|
|
569
|
+
setCursor(-1)
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
366
573
|
const workspaceMenu = menu && menu.kind === 'workspace'
|
|
367
574
|
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
368
575
|
React.createElement('button', {
|
|
@@ -370,9 +577,7 @@ export function BetterWorkspace(props) {
|
|
|
370
577
|
style: WS_MENU_ITEM,
|
|
371
578
|
onClick: async () => {
|
|
372
579
|
closeMenu()
|
|
373
|
-
try {
|
|
374
|
-
await services.openFolder(menu.workspace.path)
|
|
375
|
-
} catch {}
|
|
580
|
+
try { await services.openFolder(menu.workspace.path) } catch {}
|
|
376
581
|
},
|
|
377
582
|
}, 'Open in File Explorer'),
|
|
378
583
|
React.createElement('button', {
|
|
@@ -380,9 +585,7 @@ export function BetterWorkspace(props) {
|
|
|
380
585
|
style: WS_MENU_ITEM,
|
|
381
586
|
onClick: async () => {
|
|
382
587
|
closeMenu()
|
|
383
|
-
try {
|
|
384
|
-
await navigator.clipboard.writeText(menu.workspace.path)
|
|
385
|
-
} catch {}
|
|
588
|
+
try { await navigator.clipboard.writeText(menu.workspace.path) } catch {}
|
|
386
589
|
},
|
|
387
590
|
}, 'Copy path'),
|
|
388
591
|
React.createElement('button', {
|
|
@@ -392,16 +595,13 @@ export function BetterWorkspace(props) {
|
|
|
392
595
|
const workspace = menu.workspace
|
|
393
596
|
if (!armed || armed !== workspace.workspaceId) {
|
|
394
597
|
setArmed(workspace.workspaceId)
|
|
395
|
-
setMenu(Object.assign({}, menu
|
|
598
|
+
setMenu(Object.assign({}, menu))
|
|
396
599
|
return
|
|
397
600
|
}
|
|
398
601
|
closeMenu()
|
|
399
|
-
try {
|
|
400
|
-
await services.removeWorkspace(workspace.workspaceId)
|
|
401
|
-
} catch {}
|
|
602
|
+
try { await services.removeWorkspace(workspace.workspaceId) } catch {}
|
|
402
603
|
},
|
|
403
|
-
}, armed && armed === menu.workspace.workspaceId ? 'Remove workspace?' : 'Remove workspace')
|
|
404
|
-
)
|
|
604
|
+
}, armed && armed === menu.workspace.workspaceId ? 'Remove workspace?' : 'Remove workspace'))
|
|
405
605
|
: null
|
|
406
606
|
|
|
407
607
|
const sessionMenu = menu && menu.kind === 'session'
|
|
@@ -413,11 +613,182 @@ export function BetterWorkspace(props) {
|
|
|
413
613
|
workspacesSnapshot: workspaceState,
|
|
414
614
|
session: { sessionId: menu.row.id, displayTitle: menu.row.displayTitle, session: menu.row },
|
|
415
615
|
}))),
|
|
416
|
-
|
|
616
|
+
null)
|
|
417
617
|
: null
|
|
418
618
|
|
|
619
|
+
const children = []
|
|
620
|
+
|
|
621
|
+
children.push(React.createElement('div', { key: 'header', 'data-polish-ws': 'header', style: WS_HEADER },
|
|
622
|
+
React.createElement('span', { style: WS_HEADER_TITLE }, 'Workspaces'),
|
|
623
|
+
React.createElement('button', {
|
|
624
|
+
key: 'fold',
|
|
625
|
+
type: 'button',
|
|
626
|
+
'data-polish-ws-fold': 'true',
|
|
627
|
+
title: allFolded ? 'Expand all' : 'Collapse all',
|
|
628
|
+
'aria-label': allFolded ? 'Expand all workspaces' : 'Collapse all workspaces',
|
|
629
|
+
style: WS_ADD,
|
|
630
|
+
onClick: foldAll,
|
|
631
|
+
}, allFolded ? '⌄' : '⌃'),
|
|
632
|
+
React.createElement('button', {
|
|
633
|
+
key: 'add',
|
|
634
|
+
type: 'button',
|
|
635
|
+
'data-polish-ws-add': 'true',
|
|
636
|
+
title: 'Add a workspace',
|
|
637
|
+
'aria-label': 'Add a workspace',
|
|
638
|
+
style: WS_ADD,
|
|
639
|
+
onClick: () => { setAdding(true); setNotice('') },
|
|
640
|
+
}, '+')))
|
|
641
|
+
|
|
642
|
+
children.push(React.createElement('input', {
|
|
643
|
+
key: 'filter',
|
|
644
|
+
ref: filterRef,
|
|
645
|
+
'data-polish-ws-filter': 'true',
|
|
646
|
+
type: 'search',
|
|
647
|
+
value: query,
|
|
648
|
+
placeholder: 'Filter workspaces and sessions',
|
|
649
|
+
'aria-label': 'Filter workspaces and sessions',
|
|
650
|
+
style: WS_FILTER,
|
|
651
|
+
onChange: (event) => { setQuery(event.target.value); setCursor(-1) },
|
|
652
|
+
onKeyDown: (event) => {
|
|
653
|
+
if (event.key === 'Escape') { event.stopPropagation(); setQuery('') }
|
|
654
|
+
},
|
|
655
|
+
}))
|
|
656
|
+
|
|
657
|
+
if (notice) {
|
|
658
|
+
children.push(React.createElement('div', { key: 'notice', 'data-polish-ws': 'notice', style: WS_NOTICE }, notice))
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (adding && services.sourceFlow) {
|
|
662
|
+
children.push(React.createElement(services.sourceFlow, {
|
|
663
|
+
key: 'flow',
|
|
664
|
+
configForms: services.configForms(),
|
|
665
|
+
open: true,
|
|
666
|
+
onPicked: async (path) => {
|
|
667
|
+
if (!path) return
|
|
668
|
+
try {
|
|
669
|
+
await services.createWorkspace(path)
|
|
670
|
+
setAdding(false)
|
|
671
|
+
} catch (error) {
|
|
672
|
+
setNotice((error && error.message) || 'Could not add that folder.')
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
onCancel: () => setAdding(false),
|
|
676
|
+
onError: (message) => setNotice(message || 'Could not add that folder.'),
|
|
677
|
+
}))
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (rows.length === 0) {
|
|
681
|
+
children.push(React.createElement('div', {
|
|
682
|
+
key: 'empty',
|
|
683
|
+
style: { padding: '8px 6px', color: 'var(--dsw-alias-label-quaternary, #8a8a8a)' },
|
|
684
|
+
}, query ? 'Nothing matches "' + query + '".' : 'No workspace yet.'))
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
let index = -1
|
|
688
|
+
rows.forEach((group) => {
|
|
689
|
+
const workspace = group.workspace
|
|
690
|
+
const sessions = group.sessions
|
|
691
|
+
index += 1
|
|
692
|
+
const open = expanded[workspace.workspaceId] !== false
|
|
693
|
+
const focused = cursor === index
|
|
694
|
+
const hovered = hover === 'workspace:' + workspace.workspaceId
|
|
695
|
+
const dropHere = dropping === 'workspace:' + workspace.workspaceId
|
|
696
|
+
|
|
697
|
+
children.push(React.createElement('div', {
|
|
698
|
+
key: 'workspace:' + workspace.workspaceId,
|
|
699
|
+
style: { display: 'flex', flexDirection: 'column' },
|
|
700
|
+
},
|
|
701
|
+
React.createElement('div', {
|
|
702
|
+
'data-polish-ws-row': workspace.workspaceId,
|
|
703
|
+
draggable: true,
|
|
704
|
+
title: workspace.path || workspaceLabel(workspace),
|
|
705
|
+
style: Object.assign({}, WS_ROW, {
|
|
706
|
+
fontWeight: 500,
|
|
707
|
+
background: dropHere
|
|
708
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
709
|
+
: (focused ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04))' : undefined),
|
|
710
|
+
boxShadow: focused ? 'inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))' : undefined,
|
|
711
|
+
}),
|
|
712
|
+
onClick: () => toggleWorkspace(workspace.workspaceId),
|
|
713
|
+
onContextMenu: (event) => openWorkspaceMenu(event, workspace),
|
|
714
|
+
onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
|
|
715
|
+
onMouseLeave: () => setHover(null),
|
|
716
|
+
onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
|
|
717
|
+
onDragOver: (event) => {
|
|
718
|
+
if (!drag || drag.kind !== 'workspace' || drag.id === workspace.workspaceId) return
|
|
719
|
+
event.preventDefault()
|
|
720
|
+
setDropping('workspace:' + workspace.workspaceId)
|
|
721
|
+
},
|
|
722
|
+
onDragLeave: () => setDropping(null),
|
|
723
|
+
onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
|
|
724
|
+
},
|
|
725
|
+
React.createElement(CaretGlyph, { open }),
|
|
726
|
+
React.createElement(FolderGlyph),
|
|
727
|
+
React.createElement('span', { style: WS_TITLE }, workspaceLabel(workspace)),
|
|
728
|
+
React.createElement('span', { style: WS_COUNT }, String(sessions.length)),
|
|
729
|
+
hovered || focused
|
|
730
|
+
? React.createElement('button', {
|
|
731
|
+
type: 'button',
|
|
732
|
+
'data-polish-ws-menu': 'workspace',
|
|
733
|
+
'aria-label': 'Workspace menu',
|
|
734
|
+
style: WS_ROW_ACTION,
|
|
735
|
+
onClick: (event) => openWorkspaceMenu(event, workspace),
|
|
736
|
+
}, React.createElement(DotsGlyph))
|
|
737
|
+
: null)))
|
|
738
|
+
|
|
739
|
+
if (open) {
|
|
740
|
+
sessions.forEach((row) => {
|
|
741
|
+
index += 1
|
|
742
|
+
const rowFocused = cursor === index
|
|
743
|
+
const rowHovered = hover === 'session:' + row.id
|
|
744
|
+
const lifted = dropping === 'session:' + row.id
|
|
745
|
+
children.push(React.createElement('div', {
|
|
746
|
+
key: 'session:' + row.id,
|
|
747
|
+
'data-polish-ws-session': row.id,
|
|
748
|
+
draggable: true,
|
|
749
|
+
style: Object.assign({}, WS_ROW, {
|
|
750
|
+
paddingLeft: '24px',
|
|
751
|
+
opacity: drag && drag.kind === 'session' && drag.id === row.id ? 0.5 : undefined,
|
|
752
|
+
background: lifted
|
|
753
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
754
|
+
: (rowFocused ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04))' : undefined),
|
|
755
|
+
boxShadow: rowFocused ? 'inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))' : undefined,
|
|
756
|
+
}),
|
|
757
|
+
onClick: () => services.openSession(row.id),
|
|
758
|
+
onContextMenu: (event) => openMenu(event, row, workspace),
|
|
759
|
+
onMouseEnter: () => setHover('session:' + row.id),
|
|
760
|
+
onMouseLeave: () => setHover(null),
|
|
761
|
+
onDragStart: () => setDrag({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId }),
|
|
762
|
+
onDragOver: (event) => {
|
|
763
|
+
if (!drag || drag.kind !== 'session' || drag.id === row.id) return
|
|
764
|
+
event.preventDefault()
|
|
765
|
+
setDropping('session:' + row.id)
|
|
766
|
+
},
|
|
767
|
+
onDragLeave: () => setDropping(null),
|
|
768
|
+
onDrop: (event) => { event.preventDefault(); dropOnSession(workspace.workspaceId, row.id) },
|
|
769
|
+
},
|
|
770
|
+
row.running ? React.createElement('span', { style: WS_DOT, title: 'Running' }) : null,
|
|
771
|
+
pinned.has(row.id) ? React.createElement('span', { style: WS_PIN, title: 'Pinned' }, '\u2605') : null,
|
|
772
|
+
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id),
|
|
773
|
+
rowHovered || rowFocused
|
|
774
|
+
? React.createElement('button', {
|
|
775
|
+
type: 'button',
|
|
776
|
+
'data-polish-ws-menu': 'session',
|
|
777
|
+
'aria-label': 'Session menu',
|
|
778
|
+
style: WS_ROW_ACTION,
|
|
779
|
+
onClick: (event) => openMenu(event, row, workspace),
|
|
780
|
+
}, React.createElement(DotsGlyph))
|
|
781
|
+
: null))
|
|
782
|
+
})
|
|
783
|
+
}
|
|
784
|
+
})
|
|
785
|
+
|
|
419
786
|
return React.createElement('div', {
|
|
420
787
|
'data-polish-ws': 'tree',
|
|
788
|
+
tabIndex: 0,
|
|
789
|
+
role: 'tree',
|
|
790
|
+
'aria-label': 'Workspaces',
|
|
791
|
+
onKeyDown: onKeyDown,
|
|
421
792
|
style: {
|
|
422
793
|
display: 'flex',
|
|
423
794
|
flexDirection: 'column',
|
|
@@ -427,68 +798,13 @@ export function BetterWorkspace(props) {
|
|
|
427
798
|
overflowY: 'auto',
|
|
428
799
|
overflowX: 'hidden',
|
|
429
800
|
padding: '2px 6px 12px',
|
|
801
|
+
outline: 'none',
|
|
430
802
|
},
|
|
431
803
|
},
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
'data-polish-ws-add': 'true',
|
|
437
|
-
title: 'Add a workspace',
|
|
438
|
-
'aria-label': 'Add a workspace',
|
|
439
|
-
style: WS_ADD,
|
|
440
|
-
onClick: () => { setAdding(true); setNotice('') },
|
|
441
|
-
}, '+')),
|
|
442
|
-
notice ? React.createElement('div', { 'data-polish-ws': 'notice', style: WS_NOTICE }, notice) : null,
|
|
443
|
-
adding && services.sourceFlow
|
|
444
|
-
? React.createElement(services.sourceFlow, {
|
|
445
|
-
configForms: services.configForms(),
|
|
446
|
-
open: true,
|
|
447
|
-
onPicked: async (path) => {
|
|
448
|
-
if (!path) return
|
|
449
|
-
try {
|
|
450
|
-
await services.createWorkspace(path)
|
|
451
|
-
setAdding(false)
|
|
452
|
-
} catch (error) {
|
|
453
|
-
setNotice((error && error.message) || 'Could not add that folder.')
|
|
454
|
-
}
|
|
455
|
-
},
|
|
456
|
-
onCancel: () => setAdding(false),
|
|
457
|
-
onError: (message) => setNotice(message || 'Could not add that folder.'),
|
|
458
|
-
})
|
|
459
|
-
: null,
|
|
460
|
-
rows.length === 0
|
|
461
|
-
? React.createElement('div', { style: { padding: '8px 6px', color: 'var(--dsw-alias-label-quaternary, #8a8a8a)' } }, 'No workspace yet.')
|
|
462
|
-
: rows.map(({ workspace, sessions }) => {
|
|
463
|
-
const open = expanded[workspace.workspaceId] !== false
|
|
464
|
-
return React.createElement('div', { key: workspace.workspaceId, style: { display: 'flex', flexDirection: 'column' } },
|
|
465
|
-
React.createElement('div', {
|
|
466
|
-
'data-polish-ws-row': workspace.workspaceId,
|
|
467
|
-
style: Object.assign({}, WS_ROW, { fontWeight: 500 }),
|
|
468
|
-
onClick: () => setExpanded(prev => Object.assign({}, prev, { [workspace.workspaceId]: prev[workspace.workspaceId] === false })),
|
|
469
|
-
onContextMenu: event => openWorkspaceMenu(event, workspace),
|
|
470
|
-
},
|
|
471
|
-
React.createElement(CaretGlyph, { open }),
|
|
472
|
-
React.createElement(FolderGlyph),
|
|
473
|
-
React.createElement('span', { style: WS_TITLE, title: workspace.path || workspace.title }, workspaceLabel(workspace)),
|
|
474
|
-
React.createElement('span', { style: WS_COUNT }, String(sessions.length))),
|
|
475
|
-
open
|
|
476
|
-
? sessions.map(row => React.createElement('div', {
|
|
477
|
-
key: row.id,
|
|
478
|
-
'data-polish-ws-session': row.id,
|
|
479
|
-
style: Object.assign({}, WS_ROW, { paddingLeft: '24px' }),
|
|
480
|
-
onClick: () => services.openSession(row.id),
|
|
481
|
-
onContextMenu: event => openMenu(event, row, workspace),
|
|
482
|
-
},
|
|
483
|
-
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id),
|
|
484
|
-
React.createElement('button', {
|
|
485
|
-
type: 'button',
|
|
486
|
-
'aria-label': 'Session menu',
|
|
487
|
-
style: Object.assign({}, WS_MENU_ITEM, { width: '20px', height: '20px', padding: 0, justifyContent: 'center', flex: 'none' }),
|
|
488
|
-
onClick: event => openMenu(event, row, workspace),
|
|
489
|
-
}, React.createElement(DotsGlyph))))
|
|
490
|
-
: null)
|
|
491
|
-
}),
|
|
804
|
+
children,
|
|
805
|
+
shownCount < totalCount
|
|
806
|
+
? React.createElement('div', { key: 'count', style: WS_COUNT_NOTE }, String(shownCount) + ' of ' + String(totalCount) + ' sessions')
|
|
807
|
+
: null,
|
|
492
808
|
sessionMenu,
|
|
493
809
|
workspaceMenu)
|
|
494
810
|
}
|
|
@@ -539,6 +855,8 @@ export function installWorkspaceSeat(ctx, configForms, extras = {}) {
|
|
|
539
855
|
sessionsLive: seated.sessions.list,
|
|
540
856
|
openSession: (id) => seated.uiWorkspace.openSession(id),
|
|
541
857
|
createWorkspace: (path) => seated.workspaces.create({ path }),
|
|
858
|
+
reorderWorkspace: (movingId, beforeId) => seated.workspaces.insertBefore(movingId, beforeId),
|
|
859
|
+
reorderSession: (workspaceId, movingId, beforeId) => seated.workspaces.insertSessionBefore(workspaceId, movingId, beforeId),
|
|
542
860
|
removeWorkspace: (id) => seated.workspaces.delete(id),
|
|
543
861
|
openFolder: async (path) => {
|
|
544
862
|
const answer = await seated.remote.session.openWorkspacePath({ path })
|
package/docs/better-workspace.md
CHANGED
|
@@ -49,3 +49,47 @@ config form and adds/removes the registration each time the switch flips.
|
|
|
49
49
|
Drag and drop, reorder and pinned ordering, the "/" name grouping the reference
|
|
50
50
|
plugin offers, appearance options, and the same tree in the New Session picker. Those
|
|
51
51
|
are M2/M3; the seat is already the right place for them.
|
|
52
|
+
|
|
53
|
+
## What M2 added
|
|
54
|
+
|
|
55
|
+
- **Filter** — one box over workspaces and session titles. Every group it keeps is
|
|
56
|
+
opened, because a match you cannot see is not a match; the footer reports
|
|
57
|
+
"n of m sessions" whenever the filter hides any. `/` focuses it from anywhere,
|
|
58
|
+
Escape clears it.
|
|
59
|
+
- **Pinned first, running dot** — a pinned session is hoisted to the top of its
|
|
60
|
+
workspace with a star, and a running session shows a dot, so the row can be read
|
|
61
|
+
without clicking it.
|
|
62
|
+
- **Drag and drop** — a workspace dropped on another moves in front of it
|
|
63
|
+
(`workspaces.insertBefore`); a session dropped on a sibling reorders it
|
|
64
|
+
(`workspaces.insertSessionBefore`). A session refuses to leave its workspace: the
|
|
65
|
+
Host stores sessions inside workspaces, and moving one would look like a delete.
|
|
66
|
+
- **Keyboard walk** — arrows move a highlighted row, Enter opens a session or folds a
|
|
67
|
+
workspace, Escape closes the menu, then clears the filter, then drops the cursor.
|
|
68
|
+
- **Quiet rows** — the "..." action appears on hover or on the keyboard cursor instead
|
|
69
|
+
of on every row at once, and a header control folds or expands every workspace.
|
|
70
|
+
|
|
71
|
+
Still owed: "/"-in-title grouping, per-row appearance, workspace presets, and the same
|
|
72
|
+
tree in the Hero picker.
|
|
73
|
+
|
|
74
|
+
## Why a session cannot change workspace
|
|
75
|
+
|
|
76
|
+
Drag reorder works inside a workspace and between workspaces, but a *session* cannot
|
|
77
|
+
be dragged into a different workspace, and that is the Host's shape rather than an
|
|
78
|
+
omission here:
|
|
79
|
+
|
|
80
|
+
- `packages/api/workspace-controller/src/index.ts` exposes these remotes:
|
|
81
|
+
`create`, `initializeDefault`, `rename`, `delete`, `insertBefore`,
|
|
82
|
+
`insertSessionBefore`, `archiveSession`, `unarchiveSession`, `pinSession`,
|
|
83
|
+
`unpinSession` — and nothing that attaches or detaches a session.
|
|
84
|
+
- `packages/api/workspace-controller/src/commands.ts:132` resolves the target workspace
|
|
85
|
+
and calls its own `insertSessionBefore(sessionId, beforeSessionId)`
|
|
86
|
+
(`packages/workspace/workspace/src/entity.ts:152`), which reorders that workspace's
|
|
87
|
+
`sessionIds`; `packages/workspace/workspace/tests/workspace.spec.ts:715` pins that a
|
|
88
|
+
session the workspace does not hold is rejected.
|
|
89
|
+
- The entity does have `attachSession`/`detachSession`
|
|
90
|
+
(`packages/workspace/workspace/src/types.ts:134` region), but no remote surfaces them.
|
|
91
|
+
|
|
92
|
+
So the tree refuses with "A session stays in its workspace; drag it within that
|
|
93
|
+
workspace to reorder." rather than attempting a call that throws. If the Host ever
|
|
94
|
+
exposes attach/detach, the gesture becomes a one-line change here — the drag payload
|
|
95
|
+
already carries `{ kind: 'session', id, workspaceId }`.
|
package/lib/client.js
CHANGED
|
@@ -53,7 +53,7 @@ window.__ModuleLoader__.load({
|
|
|
53
53
|
]
|
|
54
54
|
const ROW_ID = "polish"
|
|
55
55
|
const ROW_KEY = "@aryacode/dsh-aryacode-polish" + '#' + ROW_ID
|
|
56
|
-
const CLIENT_VERSION = "0.
|
|
56
|
+
const CLIENT_VERSION = "0.11.2"
|
|
57
57
|
|
|
58
58
|
/* html2canvas 1.4.1 — MIT, vendored from vendor/html2canvas.min.js.
|
|
59
59
|
* A UMD build, so it assigns itself to globalThis; face.js reads it lazily. */
|
|
@@ -529,6 +529,111 @@ window.__ModuleLoader__.load({
|
|
|
529
529
|
return words.charAt(0).toUpperCase() + words.slice(1)
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
function filterTree(rows, query, pinned) {
|
|
533
|
+
const needle = String(query || '').trim().toLowerCase()
|
|
534
|
+
const ordered = rows.map(({ workspace, sessions }) => {
|
|
535
|
+
const hoisted = []
|
|
536
|
+
const rest = []
|
|
537
|
+
;(sessions || []).forEach(row => {
|
|
538
|
+
if (pinned && pinned.has(row.id)) hoisted.push(row)
|
|
539
|
+
else rest.push(row)
|
|
540
|
+
})
|
|
541
|
+
return { workspace, sessions: hoisted.concat(rest) }
|
|
542
|
+
})
|
|
543
|
+
if (!needle) return ordered
|
|
544
|
+
const matched = []
|
|
545
|
+
ordered.forEach(({ workspace, sessions }) => {
|
|
546
|
+
const label = workspaceLabel(workspace).toLowerCase()
|
|
547
|
+
if (label.includes(needle)) {
|
|
548
|
+
matched.push({ workspace, sessions })
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
const hits = sessions.filter(row => String(row.displayTitle || row.id).toLowerCase().includes(needle))
|
|
552
|
+
if (hits.length > 0) matched.push({ workspace, sessions: hits })
|
|
553
|
+
})
|
|
554
|
+
return matched
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* The rows a keyboard walk visits, in the order they are drawn. A filter opens every
|
|
559
|
+
* group it kept, because a match you cannot see is not a match.
|
|
560
|
+
*/
|
|
561
|
+
function flattenRows(rows, expanded, query) {
|
|
562
|
+
const filtering = String(query || '').trim().length > 0
|
|
563
|
+
const flat = []
|
|
564
|
+
rows.forEach(({ workspace, sessions }) => {
|
|
565
|
+
flat.push({ kind: 'workspace', id: workspace.workspaceId, workspaceId: workspace.workspaceId })
|
|
566
|
+
const open = filtering || expanded[workspace.workspaceId] !== false
|
|
567
|
+
if (!open) return
|
|
568
|
+
;(sessions || []).forEach(row => {
|
|
569
|
+
flat.push({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId })
|
|
570
|
+
})
|
|
571
|
+
})
|
|
572
|
+
return flat
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function moveBefore(list, movingId, beforeId) {
|
|
576
|
+
const from = list.findIndex(item => item.id === movingId)
|
|
577
|
+
const to = list.findIndex(item => item.id === beforeId)
|
|
578
|
+
if (from < 0 || to < 0 || from === to) return list
|
|
579
|
+
const next = list.slice()
|
|
580
|
+
const [moving] = next.splice(from, 1)
|
|
581
|
+
const target = next.findIndex(item => item.id === beforeId)
|
|
582
|
+
next.splice(target < 0 ? next.length : target, 0, moving)
|
|
583
|
+
return next
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const WS_FILTER = {
|
|
587
|
+
margin: '2px 2px 6px',
|
|
588
|
+
padding: '4px 8px',
|
|
589
|
+
border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
|
|
590
|
+
borderRadius: 'var(--dsw-radius-sm)',
|
|
591
|
+
background: 'transparent',
|
|
592
|
+
color: 'var(--dsw-alias-label-primary, inherit)',
|
|
593
|
+
font: 'inherit',
|
|
594
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 2px)',
|
|
595
|
+
outline: 'none',
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const WS_ROW_ACTION = {
|
|
599
|
+
flex: 'none',
|
|
600
|
+
width: '20px',
|
|
601
|
+
height: '20px',
|
|
602
|
+
display: 'inline-flex',
|
|
603
|
+
alignItems: 'center',
|
|
604
|
+
justifyContent: 'center',
|
|
605
|
+
padding: 0,
|
|
606
|
+
border: 'none',
|
|
607
|
+
borderRadius: 'var(--dsw-radius-sm)',
|
|
608
|
+
background: 'transparent',
|
|
609
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
610
|
+
cursor: 'pointer',
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const WS_DOT = {
|
|
614
|
+
flex: 'none',
|
|
615
|
+
width: '6px',
|
|
616
|
+
height: '6px',
|
|
617
|
+
borderRadius: '50%',
|
|
618
|
+
marginRight: '2px',
|
|
619
|
+
background: 'var(--dsw-alias-label-success, #12b76a)',
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const WS_PIN = {
|
|
623
|
+
flex: 'none',
|
|
624
|
+
fontSize: '9px',
|
|
625
|
+
lineHeight: 1,
|
|
626
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const WS_PIN_COUNT = WS_PIN
|
|
630
|
+
|
|
631
|
+
const WS_COUNT_NOTE = {
|
|
632
|
+
margin: '2px 4px 0',
|
|
633
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 3px)',
|
|
634
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
635
|
+
}
|
|
636
|
+
|
|
532
637
|
function BetterWorkspace(props) {
|
|
533
638
|
const services = props.services || {}
|
|
534
639
|
const { ctx, slots } = services
|
|
@@ -538,6 +643,12 @@ window.__ModuleLoader__.load({
|
|
|
538
643
|
const [armed, setArmed] = React.useState(null)
|
|
539
644
|
const [adding, setAdding] = React.useState(false)
|
|
540
645
|
const [notice, setNotice] = React.useState('')
|
|
646
|
+
const [query, setQuery] = React.useState('')
|
|
647
|
+
const [cursor, setCursor] = React.useState(-1)
|
|
648
|
+
const [hover, setHover] = React.useState(null)
|
|
649
|
+
const [drag, setDrag] = React.useState(null)
|
|
650
|
+
const [dropping, setDropping] = React.useState(null)
|
|
651
|
+
const filterRef = React.useRef(null)
|
|
541
652
|
|
|
542
653
|
const workspaceState = useLive(services.workspacesLive)
|
|
543
654
|
const sessionState = useLive(services.sessionsLive)
|
|
@@ -545,10 +656,36 @@ window.__ModuleLoader__.load({
|
|
|
545
656
|
const sessionList = sessionState && sessionState.ids && sessionState.byId
|
|
546
657
|
? sessionState.ids.map(id => sessionState.byId[id]).filter(Boolean)
|
|
547
658
|
: (sessionState && (sessionState.items || sessionState.sessions)) || []
|
|
548
|
-
const
|
|
659
|
+
const pinned = React.useMemo(
|
|
660
|
+
() => new Set((workspaceState && workspaceState.pinnedSessionIds) || []),
|
|
661
|
+
[workspaceState],
|
|
662
|
+
)
|
|
663
|
+
const allRows = workspaceRows(workspaceList, sessionList)
|
|
664
|
+
const rows = filterTree(allRows, query, pinned)
|
|
665
|
+
const walk = flattenRows(rows, expanded, query)
|
|
549
666
|
const runtime = React.useMemo(() => menuRuntime(ctx, slots, standard), [ctx, slots, standard.wide])
|
|
550
667
|
const RowBoundary = React.useMemo(() => rowBoundary(), [])
|
|
551
668
|
const entries = React.useMemo(() => rowMenuEntries(slots), [slots, workspaceList.length, sessionList.length])
|
|
669
|
+
const totalCount = allRows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
670
|
+
const shownCount = rows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
671
|
+
const allFolded = rows.length > 0 && rows.every(row => expanded[row.workspace.workspaceId] === false)
|
|
672
|
+
|
|
673
|
+
React.useEffect(() => {
|
|
674
|
+
if (cursor >= walk.length) setCursor(walk.length - 1)
|
|
675
|
+
}, [walk.length, cursor])
|
|
676
|
+
|
|
677
|
+
React.useEffect(() => {
|
|
678
|
+
const onShortcut = (event) => {
|
|
679
|
+
const node = event.target
|
|
680
|
+
const typing = node && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA' || node.isContentEditable)
|
|
681
|
+
if (event.key === '/' && !typing) {
|
|
682
|
+
event.preventDefault()
|
|
683
|
+
if (filterRef.current && filterRef.current.focus) filterRef.current.focus()
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
document.addEventListener('keydown', onShortcut)
|
|
687
|
+
return () => document.removeEventListener('keydown', onShortcut)
|
|
688
|
+
}, [])
|
|
552
689
|
|
|
553
690
|
const openMenu = (event, row, workspace) => {
|
|
554
691
|
event.preventDefault()
|
|
@@ -561,9 +698,79 @@ window.__ModuleLoader__.load({
|
|
|
561
698
|
setArmed(null)
|
|
562
699
|
setMenu({ kind: 'workspace', workspace, anchor: { top: event.clientY, left: event.clientX } })
|
|
563
700
|
}
|
|
564
|
-
|
|
565
701
|
const closeMenu = React.useCallback(() => setMenu(null), [])
|
|
566
702
|
|
|
703
|
+
const toggleWorkspace = (workspaceId) => {
|
|
704
|
+
setExpanded((prev) => {
|
|
705
|
+
const next = Object.assign({}, prev)
|
|
706
|
+
next[workspaceId] = prev[workspaceId] === false
|
|
707
|
+
return next
|
|
708
|
+
})
|
|
709
|
+
}
|
|
710
|
+
const foldAll = () => {
|
|
711
|
+
if (allFolded) { setExpanded({}); return }
|
|
712
|
+
const next = {}
|
|
713
|
+
rows.forEach((row) => { next[row.workspace.workspaceId] = false })
|
|
714
|
+
setExpanded(next)
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const reorderWorkspace = async (movingId, beforeId) => {
|
|
718
|
+
if (!services.reorderWorkspace || !movingId || movingId === beforeId) return
|
|
719
|
+
try { await services.reorderWorkspace(movingId, beforeId) } catch { setNotice('Could not reorder workspaces.') }
|
|
720
|
+
}
|
|
721
|
+
const reorderSession = async (workspaceId, movingId, beforeId) => {
|
|
722
|
+
if (!services.reorderSession || !movingId || movingId === beforeId) return
|
|
723
|
+
try { await services.reorderSession(workspaceId, movingId, beforeId) } catch { setNotice('Could not reorder sessions.') }
|
|
724
|
+
}
|
|
725
|
+
const dropOnWorkspace = (targetId) => {
|
|
726
|
+
const moving = drag
|
|
727
|
+
setDrag(null)
|
|
728
|
+
setDropping(null)
|
|
729
|
+
if (!moving || moving.kind !== 'workspace') return
|
|
730
|
+
void reorderWorkspace(moving.id, targetId)
|
|
731
|
+
}
|
|
732
|
+
const dropOnSession = (workspaceId, targetId) => {
|
|
733
|
+
const moving = drag
|
|
734
|
+
setDrag(null)
|
|
735
|
+
setDropping(null)
|
|
736
|
+
if (!moving || moving.kind !== 'session' || moving.id === targetId) return
|
|
737
|
+
if (moving.workspaceId !== workspaceId) {
|
|
738
|
+
// A session cannot change workspace from a plugin: the Host's remote
|
|
739
|
+
// namespace exposes create/rename/delete/insertBefore/insertSessionBefore
|
|
740
|
+
// and no attachSession or detachSession, and a workspace's own
|
|
741
|
+
// insertSessionBefore only reorders the sessions already in it.
|
|
742
|
+
setNotice('A session stays in its workspace; drag it within that workspace to reorder.')
|
|
743
|
+
return
|
|
744
|
+
}
|
|
745
|
+
void reorderSession(workspaceId, moving.id, targetId)
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const activate = (item) => {
|
|
749
|
+
if (!item) return
|
|
750
|
+
if (item.kind === 'workspace') toggleWorkspace(item.workspaceId)
|
|
751
|
+
else services.openSession(item.id)
|
|
752
|
+
}
|
|
753
|
+
const onKeyDown = (event) => {
|
|
754
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
755
|
+
if (walk.length === 0) return
|
|
756
|
+
event.preventDefault()
|
|
757
|
+
const step = event.key === 'ArrowDown' ? 1 : -1
|
|
758
|
+
setCursor(Math.min(walk.length - 1, Math.max(0, cursor + step)))
|
|
759
|
+
return
|
|
760
|
+
}
|
|
761
|
+
if (event.key === 'Enter') {
|
|
762
|
+
if (cursor < 0) return
|
|
763
|
+
event.preventDefault()
|
|
764
|
+
activate(walk[cursor])
|
|
765
|
+
return
|
|
766
|
+
}
|
|
767
|
+
if (event.key === 'Escape') {
|
|
768
|
+
if (query) { setQuery(''); return }
|
|
769
|
+
if (menu) { closeMenu(); return }
|
|
770
|
+
setCursor(-1)
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
567
774
|
const workspaceMenu = menu && menu.kind === 'workspace'
|
|
568
775
|
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
569
776
|
React.createElement('button', {
|
|
@@ -571,9 +778,7 @@ window.__ModuleLoader__.load({
|
|
|
571
778
|
style: WS_MENU_ITEM,
|
|
572
779
|
onClick: async () => {
|
|
573
780
|
closeMenu()
|
|
574
|
-
try {
|
|
575
|
-
await services.openFolder(menu.workspace.path)
|
|
576
|
-
} catch {}
|
|
781
|
+
try { await services.openFolder(menu.workspace.path) } catch {}
|
|
577
782
|
},
|
|
578
783
|
}, 'Open in File Explorer'),
|
|
579
784
|
React.createElement('button', {
|
|
@@ -581,9 +786,7 @@ window.__ModuleLoader__.load({
|
|
|
581
786
|
style: WS_MENU_ITEM,
|
|
582
787
|
onClick: async () => {
|
|
583
788
|
closeMenu()
|
|
584
|
-
try {
|
|
585
|
-
await navigator.clipboard.writeText(menu.workspace.path)
|
|
586
|
-
} catch {}
|
|
789
|
+
try { await navigator.clipboard.writeText(menu.workspace.path) } catch {}
|
|
587
790
|
},
|
|
588
791
|
}, 'Copy path'),
|
|
589
792
|
React.createElement('button', {
|
|
@@ -593,16 +796,13 @@ window.__ModuleLoader__.load({
|
|
|
593
796
|
const workspace = menu.workspace
|
|
594
797
|
if (!armed || armed !== workspace.workspaceId) {
|
|
595
798
|
setArmed(workspace.workspaceId)
|
|
596
|
-
setMenu(Object.assign({}, menu
|
|
799
|
+
setMenu(Object.assign({}, menu))
|
|
597
800
|
return
|
|
598
801
|
}
|
|
599
802
|
closeMenu()
|
|
600
|
-
try {
|
|
601
|
-
await services.removeWorkspace(workspace.workspaceId)
|
|
602
|
-
} catch {}
|
|
803
|
+
try { await services.removeWorkspace(workspace.workspaceId) } catch {}
|
|
603
804
|
},
|
|
604
|
-
}, armed && armed === menu.workspace.workspaceId ? 'Remove workspace?' : 'Remove workspace')
|
|
605
|
-
)
|
|
805
|
+
}, armed && armed === menu.workspace.workspaceId ? 'Remove workspace?' : 'Remove workspace'))
|
|
606
806
|
: null
|
|
607
807
|
|
|
608
808
|
const sessionMenu = menu && menu.kind === 'session'
|
|
@@ -614,11 +814,182 @@ window.__ModuleLoader__.load({
|
|
|
614
814
|
workspacesSnapshot: workspaceState,
|
|
615
815
|
session: { sessionId: menu.row.id, displayTitle: menu.row.displayTitle, session: menu.row },
|
|
616
816
|
}))),
|
|
617
|
-
|
|
817
|
+
null)
|
|
618
818
|
: null
|
|
619
819
|
|
|
820
|
+
const children = []
|
|
821
|
+
|
|
822
|
+
children.push(React.createElement('div', { key: 'header', 'data-polish-ws': 'header', style: WS_HEADER },
|
|
823
|
+
React.createElement('span', { style: WS_HEADER_TITLE }, 'Workspaces'),
|
|
824
|
+
React.createElement('button', {
|
|
825
|
+
key: 'fold',
|
|
826
|
+
type: 'button',
|
|
827
|
+
'data-polish-ws-fold': 'true',
|
|
828
|
+
title: allFolded ? 'Expand all' : 'Collapse all',
|
|
829
|
+
'aria-label': allFolded ? 'Expand all workspaces' : 'Collapse all workspaces',
|
|
830
|
+
style: WS_ADD,
|
|
831
|
+
onClick: foldAll,
|
|
832
|
+
}, allFolded ? '⌄' : '⌃'),
|
|
833
|
+
React.createElement('button', {
|
|
834
|
+
key: 'add',
|
|
835
|
+
type: 'button',
|
|
836
|
+
'data-polish-ws-add': 'true',
|
|
837
|
+
title: 'Add a workspace',
|
|
838
|
+
'aria-label': 'Add a workspace',
|
|
839
|
+
style: WS_ADD,
|
|
840
|
+
onClick: () => { setAdding(true); setNotice('') },
|
|
841
|
+
}, '+')))
|
|
842
|
+
|
|
843
|
+
children.push(React.createElement('input', {
|
|
844
|
+
key: 'filter',
|
|
845
|
+
ref: filterRef,
|
|
846
|
+
'data-polish-ws-filter': 'true',
|
|
847
|
+
type: 'search',
|
|
848
|
+
value: query,
|
|
849
|
+
placeholder: 'Filter workspaces and sessions',
|
|
850
|
+
'aria-label': 'Filter workspaces and sessions',
|
|
851
|
+
style: WS_FILTER,
|
|
852
|
+
onChange: (event) => { setQuery(event.target.value); setCursor(-1) },
|
|
853
|
+
onKeyDown: (event) => {
|
|
854
|
+
if (event.key === 'Escape') { event.stopPropagation(); setQuery('') }
|
|
855
|
+
},
|
|
856
|
+
}))
|
|
857
|
+
|
|
858
|
+
if (notice) {
|
|
859
|
+
children.push(React.createElement('div', { key: 'notice', 'data-polish-ws': 'notice', style: WS_NOTICE }, notice))
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (adding && services.sourceFlow) {
|
|
863
|
+
children.push(React.createElement(services.sourceFlow, {
|
|
864
|
+
key: 'flow',
|
|
865
|
+
configForms: services.configForms(),
|
|
866
|
+
open: true,
|
|
867
|
+
onPicked: async (path) => {
|
|
868
|
+
if (!path) return
|
|
869
|
+
try {
|
|
870
|
+
await services.createWorkspace(path)
|
|
871
|
+
setAdding(false)
|
|
872
|
+
} catch (error) {
|
|
873
|
+
setNotice((error && error.message) || 'Could not add that folder.')
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
onCancel: () => setAdding(false),
|
|
877
|
+
onError: (message) => setNotice(message || 'Could not add that folder.'),
|
|
878
|
+
}))
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (rows.length === 0) {
|
|
882
|
+
children.push(React.createElement('div', {
|
|
883
|
+
key: 'empty',
|
|
884
|
+
style: { padding: '8px 6px', color: 'var(--dsw-alias-label-quaternary, #8a8a8a)' },
|
|
885
|
+
}, query ? 'Nothing matches "' + query + '".' : 'No workspace yet.'))
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
let index = -1
|
|
889
|
+
rows.forEach((group) => {
|
|
890
|
+
const workspace = group.workspace
|
|
891
|
+
const sessions = group.sessions
|
|
892
|
+
index += 1
|
|
893
|
+
const open = expanded[workspace.workspaceId] !== false
|
|
894
|
+
const focused = cursor === index
|
|
895
|
+
const hovered = hover === 'workspace:' + workspace.workspaceId
|
|
896
|
+
const dropHere = dropping === 'workspace:' + workspace.workspaceId
|
|
897
|
+
|
|
898
|
+
children.push(React.createElement('div', {
|
|
899
|
+
key: 'workspace:' + workspace.workspaceId,
|
|
900
|
+
style: { display: 'flex', flexDirection: 'column' },
|
|
901
|
+
},
|
|
902
|
+
React.createElement('div', {
|
|
903
|
+
'data-polish-ws-row': workspace.workspaceId,
|
|
904
|
+
draggable: true,
|
|
905
|
+
title: workspace.path || workspaceLabel(workspace),
|
|
906
|
+
style: Object.assign({}, WS_ROW, {
|
|
907
|
+
fontWeight: 500,
|
|
908
|
+
background: dropHere
|
|
909
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
910
|
+
: (focused ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04))' : undefined),
|
|
911
|
+
boxShadow: focused ? 'inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))' : undefined,
|
|
912
|
+
}),
|
|
913
|
+
onClick: () => toggleWorkspace(workspace.workspaceId),
|
|
914
|
+
onContextMenu: (event) => openWorkspaceMenu(event, workspace),
|
|
915
|
+
onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
|
|
916
|
+
onMouseLeave: () => setHover(null),
|
|
917
|
+
onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
|
|
918
|
+
onDragOver: (event) => {
|
|
919
|
+
if (!drag || drag.kind !== 'workspace' || drag.id === workspace.workspaceId) return
|
|
920
|
+
event.preventDefault()
|
|
921
|
+
setDropping('workspace:' + workspace.workspaceId)
|
|
922
|
+
},
|
|
923
|
+
onDragLeave: () => setDropping(null),
|
|
924
|
+
onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
|
|
925
|
+
},
|
|
926
|
+
React.createElement(CaretGlyph, { open }),
|
|
927
|
+
React.createElement(FolderGlyph),
|
|
928
|
+
React.createElement('span', { style: WS_TITLE }, workspaceLabel(workspace)),
|
|
929
|
+
React.createElement('span', { style: WS_COUNT }, String(sessions.length)),
|
|
930
|
+
hovered || focused
|
|
931
|
+
? React.createElement('button', {
|
|
932
|
+
type: 'button',
|
|
933
|
+
'data-polish-ws-menu': 'workspace',
|
|
934
|
+
'aria-label': 'Workspace menu',
|
|
935
|
+
style: WS_ROW_ACTION,
|
|
936
|
+
onClick: (event) => openWorkspaceMenu(event, workspace),
|
|
937
|
+
}, React.createElement(DotsGlyph))
|
|
938
|
+
: null)))
|
|
939
|
+
|
|
940
|
+
if (open) {
|
|
941
|
+
sessions.forEach((row) => {
|
|
942
|
+
index += 1
|
|
943
|
+
const rowFocused = cursor === index
|
|
944
|
+
const rowHovered = hover === 'session:' + row.id
|
|
945
|
+
const lifted = dropping === 'session:' + row.id
|
|
946
|
+
children.push(React.createElement('div', {
|
|
947
|
+
key: 'session:' + row.id,
|
|
948
|
+
'data-polish-ws-session': row.id,
|
|
949
|
+
draggable: true,
|
|
950
|
+
style: Object.assign({}, WS_ROW, {
|
|
951
|
+
paddingLeft: '24px',
|
|
952
|
+
opacity: drag && drag.kind === 'session' && drag.id === row.id ? 0.5 : undefined,
|
|
953
|
+
background: lifted
|
|
954
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
955
|
+
: (rowFocused ? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04))' : undefined),
|
|
956
|
+
boxShadow: rowFocused ? 'inset 0 0 0 1px var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))' : undefined,
|
|
957
|
+
}),
|
|
958
|
+
onClick: () => services.openSession(row.id),
|
|
959
|
+
onContextMenu: (event) => openMenu(event, row, workspace),
|
|
960
|
+
onMouseEnter: () => setHover('session:' + row.id),
|
|
961
|
+
onMouseLeave: () => setHover(null),
|
|
962
|
+
onDragStart: () => setDrag({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId }),
|
|
963
|
+
onDragOver: (event) => {
|
|
964
|
+
if (!drag || drag.kind !== 'session' || drag.id === row.id) return
|
|
965
|
+
event.preventDefault()
|
|
966
|
+
setDropping('session:' + row.id)
|
|
967
|
+
},
|
|
968
|
+
onDragLeave: () => setDropping(null),
|
|
969
|
+
onDrop: (event) => { event.preventDefault(); dropOnSession(workspace.workspaceId, row.id) },
|
|
970
|
+
},
|
|
971
|
+
row.running ? React.createElement('span', { style: WS_DOT, title: 'Running' }) : null,
|
|
972
|
+
pinned.has(row.id) ? React.createElement('span', { style: WS_PIN, title: 'Pinned' }, '\u2605') : null,
|
|
973
|
+
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id),
|
|
974
|
+
rowHovered || rowFocused
|
|
975
|
+
? React.createElement('button', {
|
|
976
|
+
type: 'button',
|
|
977
|
+
'data-polish-ws-menu': 'session',
|
|
978
|
+
'aria-label': 'Session menu',
|
|
979
|
+
style: WS_ROW_ACTION,
|
|
980
|
+
onClick: (event) => openMenu(event, row, workspace),
|
|
981
|
+
}, React.createElement(DotsGlyph))
|
|
982
|
+
: null))
|
|
983
|
+
})
|
|
984
|
+
}
|
|
985
|
+
})
|
|
986
|
+
|
|
620
987
|
return React.createElement('div', {
|
|
621
988
|
'data-polish-ws': 'tree',
|
|
989
|
+
tabIndex: 0,
|
|
990
|
+
role: 'tree',
|
|
991
|
+
'aria-label': 'Workspaces',
|
|
992
|
+
onKeyDown: onKeyDown,
|
|
622
993
|
style: {
|
|
623
994
|
display: 'flex',
|
|
624
995
|
flexDirection: 'column',
|
|
@@ -628,68 +999,13 @@ window.__ModuleLoader__.load({
|
|
|
628
999
|
overflowY: 'auto',
|
|
629
1000
|
overflowX: 'hidden',
|
|
630
1001
|
padding: '2px 6px 12px',
|
|
1002
|
+
outline: 'none',
|
|
631
1003
|
},
|
|
632
1004
|
},
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
'data-polish-ws-add': 'true',
|
|
638
|
-
title: 'Add a workspace',
|
|
639
|
-
'aria-label': 'Add a workspace',
|
|
640
|
-
style: WS_ADD,
|
|
641
|
-
onClick: () => { setAdding(true); setNotice('') },
|
|
642
|
-
}, '+')),
|
|
643
|
-
notice ? React.createElement('div', { 'data-polish-ws': 'notice', style: WS_NOTICE }, notice) : null,
|
|
644
|
-
adding && services.sourceFlow
|
|
645
|
-
? React.createElement(services.sourceFlow, {
|
|
646
|
-
configForms: services.configForms(),
|
|
647
|
-
open: true,
|
|
648
|
-
onPicked: async (path) => {
|
|
649
|
-
if (!path) return
|
|
650
|
-
try {
|
|
651
|
-
await services.createWorkspace(path)
|
|
652
|
-
setAdding(false)
|
|
653
|
-
} catch (error) {
|
|
654
|
-
setNotice((error && error.message) || 'Could not add that folder.')
|
|
655
|
-
}
|
|
656
|
-
},
|
|
657
|
-
onCancel: () => setAdding(false),
|
|
658
|
-
onError: (message) => setNotice(message || 'Could not add that folder.'),
|
|
659
|
-
})
|
|
660
|
-
: null,
|
|
661
|
-
rows.length === 0
|
|
662
|
-
? React.createElement('div', { style: { padding: '8px 6px', color: 'var(--dsw-alias-label-quaternary, #8a8a8a)' } }, 'No workspace yet.')
|
|
663
|
-
: rows.map(({ workspace, sessions }) => {
|
|
664
|
-
const open = expanded[workspace.workspaceId] !== false
|
|
665
|
-
return React.createElement('div', { key: workspace.workspaceId, style: { display: 'flex', flexDirection: 'column' } },
|
|
666
|
-
React.createElement('div', {
|
|
667
|
-
'data-polish-ws-row': workspace.workspaceId,
|
|
668
|
-
style: Object.assign({}, WS_ROW, { fontWeight: 500 }),
|
|
669
|
-
onClick: () => setExpanded(prev => Object.assign({}, prev, { [workspace.workspaceId]: prev[workspace.workspaceId] === false })),
|
|
670
|
-
onContextMenu: event => openWorkspaceMenu(event, workspace),
|
|
671
|
-
},
|
|
672
|
-
React.createElement(CaretGlyph, { open }),
|
|
673
|
-
React.createElement(FolderGlyph),
|
|
674
|
-
React.createElement('span', { style: WS_TITLE, title: workspace.path || workspace.title }, workspaceLabel(workspace)),
|
|
675
|
-
React.createElement('span', { style: WS_COUNT }, String(sessions.length))),
|
|
676
|
-
open
|
|
677
|
-
? sessions.map(row => React.createElement('div', {
|
|
678
|
-
key: row.id,
|
|
679
|
-
'data-polish-ws-session': row.id,
|
|
680
|
-
style: Object.assign({}, WS_ROW, { paddingLeft: '24px' }),
|
|
681
|
-
onClick: () => services.openSession(row.id),
|
|
682
|
-
onContextMenu: event => openMenu(event, row, workspace),
|
|
683
|
-
},
|
|
684
|
-
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id),
|
|
685
|
-
React.createElement('button', {
|
|
686
|
-
type: 'button',
|
|
687
|
-
'aria-label': 'Session menu',
|
|
688
|
-
style: Object.assign({}, WS_MENU_ITEM, { width: '20px', height: '20px', padding: 0, justifyContent: 'center', flex: 'none' }),
|
|
689
|
-
onClick: event => openMenu(event, row, workspace),
|
|
690
|
-
}, React.createElement(DotsGlyph))))
|
|
691
|
-
: null)
|
|
692
|
-
}),
|
|
1005
|
+
children,
|
|
1006
|
+
shownCount < totalCount
|
|
1007
|
+
? React.createElement('div', { key: 'count', style: WS_COUNT_NOTE }, String(shownCount) + ' of ' + String(totalCount) + ' sessions')
|
|
1008
|
+
: null,
|
|
693
1009
|
sessionMenu,
|
|
694
1010
|
workspaceMenu)
|
|
695
1011
|
}
|
|
@@ -740,6 +1056,8 @@ window.__ModuleLoader__.load({
|
|
|
740
1056
|
sessionsLive: seated.sessions.list,
|
|
741
1057
|
openSession: (id) => seated.uiWorkspace.openSession(id),
|
|
742
1058
|
createWorkspace: (path) => seated.workspaces.create({ path }),
|
|
1059
|
+
reorderWorkspace: (movingId, beforeId) => seated.workspaces.insertBefore(movingId, beforeId),
|
|
1060
|
+
reorderSession: (workspaceId, movingId, beforeId) => seated.workspaces.insertSessionBefore(workspaceId, movingId, beforeId),
|
|
743
1061
|
removeWorkspace: (id) => seated.workspaces.delete(id),
|
|
744
1062
|
openFolder: async (path) => {
|
|
745
1063
|
const answer = await seated.remote.session.openWorkspacePath({ path })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-aryacode-polish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "AryaCode Polish: one install for the small things that make AryaCode feel finished. One Plugins row, one switch per quality-of-life feature.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|