@aryacode/dsh-aryacode-polish 0.14.0 → 0.16.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/CHANGELOG.md +23 -0
- package/client/workspace.js +293 -7
- package/docs/better-workspace.md +34 -0
- package/lib/client.js +294 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.16.0] - 2026-10-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **A glyph per workspace.** The workspace menu can put an emoji badge in front of a
|
|
7
|
+
workspace instead of the folder icon, and the choice is remembered with the colour.
|
|
8
|
+
- **A note per workspace.** The note shows as the row tooltip and is edited from the same
|
|
9
|
+
menu; Enter saves, Escape cancels.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## [0.15.2] - 2026-10-28
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Density modes.** A control in the tree header switches between comfortable and
|
|
16
|
+
compact rows, and the choice is remembered (`aryacode-polish:density.v1`). Density
|
|
17
|
+
changes row height only — the text size stays readable.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **Expand all now expands folders too.** The header button only flipped workspace rows, so a
|
|
21
|
+
folded folder kept its workspaces hidden while the button claimed everything was open.
|
|
22
|
+
- **Archived sessions no longer sit in their workspace.** The tree now reads
|
|
23
|
+
`archivedSessionIds` (it never did) and shows those sessions in a collapsed
|
|
24
|
+
**Archived** section at the bottom, each one restorable from its own menu.
|
|
25
|
+
|
|
3
26
|
## [0.14.0] - 2026-10-28
|
|
4
27
|
|
|
5
28
|
### Added
|
package/client/workspace.js
CHANGED
|
@@ -202,7 +202,7 @@ const WS_ROW = {
|
|
|
202
202
|
gap: '6px',
|
|
203
203
|
boxSizing: 'border-box',
|
|
204
204
|
width: '100%',
|
|
205
|
-
height: '28px',
|
|
205
|
+
height: 'var(--polish-ws-row, 28px)',
|
|
206
206
|
padding: '0 6px',
|
|
207
207
|
border: 'none',
|
|
208
208
|
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
@@ -385,6 +385,116 @@ export function moveBefore(list, movingId, beforeId) {
|
|
|
385
385
|
const LOOK_KEY = 'aryacode-polish:workspace-appearance.v1'
|
|
386
386
|
const ORDER_KEY = 'aryacode-polish:workspace-order.v1'
|
|
387
387
|
const FOLD_KEY = 'aryacode-polish:folded.v1'
|
|
388
|
+
const DENSITY_KEY = 'aryacode-polish:density.v1'
|
|
389
|
+
|
|
390
|
+
export const DENSITIES = ['comfortable', 'compact']
|
|
391
|
+
|
|
392
|
+
/** Row height per density: the tree fits more rows without shrinking the text. */
|
|
393
|
+
export const DENSITY_METRICS = {
|
|
394
|
+
comfortable: { row: '28px' },
|
|
395
|
+
compact: { row: '22px' },
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function densityOf(value) {
|
|
399
|
+
return value === 'compact' ? 'compact' : 'comfortable'
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function readDensity(storage) {
|
|
403
|
+
try {
|
|
404
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
405
|
+
if (!store) return 'comfortable'
|
|
406
|
+
return densityOf(store.getItem(DENSITY_KEY))
|
|
407
|
+
} catch {
|
|
408
|
+
return 'comfortable'
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function writeDensity(value, storage) {
|
|
413
|
+
try {
|
|
414
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
415
|
+
if (!store) return
|
|
416
|
+
store.setItem(DENSITY_KEY, densityOf(value))
|
|
417
|
+
} catch {
|
|
418
|
+
/* remembering this is a nicety, not a requirement */
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Split the flat session list into the rows a workspace shows and the ones it has
|
|
424
|
+
* archived. The shipped browser hides archived sessions; here they keep a section of
|
|
425
|
+
* their own at the bottom instead of disappearing without a trace.
|
|
426
|
+
*/
|
|
427
|
+
export function splitArchived(sessions, archivedIds) {
|
|
428
|
+
const archive = archivedIds instanceof Set ? archivedIds : new Set(archivedIds || [])
|
|
429
|
+
const active = []
|
|
430
|
+
const archived = []
|
|
431
|
+
for (const session of Array.isArray(sessions) ? sessions : []) {
|
|
432
|
+
if (archive.has(session.id)) archived.push(session)
|
|
433
|
+
else active.push(session)
|
|
434
|
+
}
|
|
435
|
+
return { active, archived }
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const WS_GLYPH_GRID = {
|
|
439
|
+
display: 'grid',
|
|
440
|
+
gridTemplateColumns: 'repeat(6, 26px)',
|
|
441
|
+
gap: '2px',
|
|
442
|
+
padding: '2px',
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const WS_GLYPH_BUTTON = {
|
|
446
|
+
width: '26px',
|
|
447
|
+
height: '26px',
|
|
448
|
+
display: 'inline-flex',
|
|
449
|
+
alignItems: 'center',
|
|
450
|
+
justifyContent: 'center',
|
|
451
|
+
padding: 0,
|
|
452
|
+
border: 'none',
|
|
453
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
454
|
+
background: 'transparent',
|
|
455
|
+
fontSize: '15px',
|
|
456
|
+
lineHeight: 1,
|
|
457
|
+
cursor: 'pointer',
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const WS_NOTE_INPUT = {
|
|
461
|
+
width: '220px',
|
|
462
|
+
boxSizing: 'border-box',
|
|
463
|
+
padding: '5px 8px',
|
|
464
|
+
border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
|
|
465
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
466
|
+
background: 'transparent',
|
|
467
|
+
color: 'inherit',
|
|
468
|
+
font: 'inherit',
|
|
469
|
+
outline: 'none',
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const WS_MENU_HINT = {
|
|
473
|
+
padding: '4px 8px 2px',
|
|
474
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 2px)',
|
|
475
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const WS_GLYPH = {
|
|
479
|
+
flex: 'none',
|
|
480
|
+
width: '16px',
|
|
481
|
+
textAlign: 'center',
|
|
482
|
+
fontSize: '13px',
|
|
483
|
+
lineHeight: 1,
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const WS_SECTION = {
|
|
487
|
+
display: 'flex',
|
|
488
|
+
alignItems: 'center',
|
|
489
|
+
gap: '5px',
|
|
490
|
+
padding: '8px 6px 2px',
|
|
491
|
+
textTransform: 'uppercase',
|
|
492
|
+
letterSpacing: '0.04em',
|
|
493
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
494
|
+
fontSize: 'calc(' + 'var(--dsh-content-font-size-secondary, 13px)' + ' - 3px)',
|
|
495
|
+
cursor: 'pointer',
|
|
496
|
+
userSelect: 'none',
|
|
497
|
+
}
|
|
388
498
|
|
|
389
499
|
/** Which workspaces and folders are folded, kept so a restart looks like yesterday. */
|
|
390
500
|
export function readFolded(storage) {
|
|
@@ -583,19 +693,29 @@ export const TONE_COLOR = {
|
|
|
583
693
|
violet: '#7a5cc4',
|
|
584
694
|
}
|
|
585
695
|
|
|
696
|
+
/** Glyphs a workspace can wear instead of the folder icon. */
|
|
697
|
+
export const GLYPHS = ['📁', '🚀', '🧪', '🐍', '⚙️', '📦', '🧭', '✨', '🧠', '🗂️']
|
|
698
|
+
|
|
586
699
|
export function appearanceOf(map, workspaceId) {
|
|
587
700
|
const entry = (map && map[workspaceId]) || {}
|
|
588
701
|
return {
|
|
589
702
|
tone: TONE_COLOR[entry.tone] ? entry.tone : undefined,
|
|
590
703
|
strong: entry.strong === true,
|
|
704
|
+
glyph: typeof entry.glyph === 'string' && entry.glyph.length > 0 && entry.glyph.length <= 4 ? entry.glyph : undefined,
|
|
705
|
+
note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
|
|
591
706
|
}
|
|
592
707
|
}
|
|
593
708
|
|
|
594
709
|
export function withAppearance(map, workspaceId, patch) {
|
|
595
710
|
const next = Object.assign({}, map || {})
|
|
596
711
|
const merged = Object.assign({}, appearanceOf(next, workspaceId), patch || {})
|
|
597
|
-
|
|
598
|
-
|
|
712
|
+
const entry = {}
|
|
713
|
+
if (merged.tone) entry.tone = merged.tone
|
|
714
|
+
if (merged.strong === true) entry.strong = true
|
|
715
|
+
if (merged.glyph) entry.glyph = merged.glyph
|
|
716
|
+
if (merged.note) entry.note = merged.note
|
|
717
|
+
if (Object.keys(entry).length === 0) delete next[workspaceId]
|
|
718
|
+
else next[workspaceId] = entry
|
|
599
719
|
return next
|
|
600
720
|
}
|
|
601
721
|
|
|
@@ -712,9 +832,12 @@ export function BetterWorkspace(props) {
|
|
|
712
832
|
const [drag, setDrag] = React.useState(null)
|
|
713
833
|
const [dropping, setDropping] = React.useState(null)
|
|
714
834
|
const [look, setLook] = React.useState(() => readAppearance())
|
|
835
|
+
const [noteDraft, setNoteDraft] = React.useState('')
|
|
715
836
|
const folded = React.useMemo(() => readFolded(), [])
|
|
716
837
|
const [folders, setFolders] = React.useState(folded.folders)
|
|
717
838
|
const [switcher, setSwitcher] = React.useState(null)
|
|
839
|
+
const [density, setDensity] = React.useState(() => readDensity())
|
|
840
|
+
const [archiveOpen, setArchiveOpen] = React.useState(false)
|
|
718
841
|
React.useEffect(() => { writeFolded({ workspaces: expanded, folders }) }, [expanded, folders])
|
|
719
842
|
const [renaming, setRenaming] = React.useState(null)
|
|
720
843
|
const commitRename = async () => {
|
|
@@ -750,7 +873,11 @@ export function BetterWorkspace(props) {
|
|
|
750
873
|
() => new Set((workspaceState && workspaceState.pinnedSessionIds) || []),
|
|
751
874
|
[workspaceState],
|
|
752
875
|
)
|
|
753
|
-
const
|
|
876
|
+
const metrics = DENSITY_METRICS[densityOf(density)]
|
|
877
|
+
const archiveSet = new Set((workspaceState && workspaceState.archivedSessionIds) || [])
|
|
878
|
+
const split = splitArchived(sessionList, archiveSet)
|
|
879
|
+
const allRows = workspaceRows(workspaceList, split.active)
|
|
880
|
+
const archivedSessions = split.archived
|
|
754
881
|
const rows = filterTree(allRows, query, pinned)
|
|
755
882
|
const folderOf = (workspace) => folderKey(workspace)
|
|
756
883
|
const visibleRows = rows.filter((group) => {
|
|
@@ -763,7 +890,12 @@ export function BetterWorkspace(props) {
|
|
|
763
890
|
const entries = React.useMemo(() => rowMenuEntries(slots), [slots, workspaceList.length, sessionList.length])
|
|
764
891
|
const totalCount = allRows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
765
892
|
const shownCount = rows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
766
|
-
const
|
|
893
|
+
const folderNames = Array.from(new Set(rows.map(row => folderKey(row.workspace)).filter(Boolean)))
|
|
894
|
+
// One click should always do the useful thing: if anything is folded, the button
|
|
895
|
+
// expands everything (folders included); only when nothing is folded does it fold.
|
|
896
|
+
const anyFolded = rows.some(row => expanded[row.workspace.workspaceId] === false)
|
|
897
|
+
|| folderNames.some(name => folders[name] === true)
|
|
898
|
+
const allFolded = rows.length > 0 && !anyFolded
|
|
767
899
|
|
|
768
900
|
React.useEffect(() => {
|
|
769
901
|
if (cursor >= walk.length) setCursor(walk.length - 1)
|
|
@@ -833,10 +965,17 @@ export function BetterWorkspace(props) {
|
|
|
833
965
|
})
|
|
834
966
|
}
|
|
835
967
|
const foldAll = () => {
|
|
836
|
-
if (
|
|
968
|
+
if (anyFolded) {
|
|
969
|
+
setExpanded({})
|
|
970
|
+
setFolders({})
|
|
971
|
+
return
|
|
972
|
+
}
|
|
837
973
|
const next = {}
|
|
838
974
|
rows.forEach((row) => { next[row.workspace.workspaceId] = false })
|
|
975
|
+
const nextFolders = {}
|
|
976
|
+
folderNames.forEach((name) => { nextFolders[name] = true })
|
|
839
977
|
setExpanded(next)
|
|
978
|
+
setFolders(nextFolders)
|
|
840
979
|
}
|
|
841
980
|
|
|
842
981
|
const reorderWorkspace = async (movingId, beforeId) => {
|
|
@@ -955,6 +1094,29 @@ export function BetterWorkspace(props) {
|
|
|
955
1094
|
}
|
|
956
1095
|
},
|
|
957
1096
|
}, 'Restore saved order'),
|
|
1097
|
+
React.createElement('button', {
|
|
1098
|
+
type: 'button',
|
|
1099
|
+
'data-polish-glyph': 'open',
|
|
1100
|
+
style: WS_MENU_ITEM,
|
|
1101
|
+
onClick: () => {
|
|
1102
|
+
const target = menu.workspace
|
|
1103
|
+
const anchor = menu.anchor
|
|
1104
|
+
closeMenu()
|
|
1105
|
+
setMenu({ kind: 'glyph', workspace: target, anchor })
|
|
1106
|
+
},
|
|
1107
|
+
}, 'Glyph…'),
|
|
1108
|
+
React.createElement('button', {
|
|
1109
|
+
type: 'button',
|
|
1110
|
+
'data-polish-note': 'open',
|
|
1111
|
+
style: WS_MENU_ITEM,
|
|
1112
|
+
onClick: () => {
|
|
1113
|
+
const target = menu.workspace
|
|
1114
|
+
const anchor = menu.anchor
|
|
1115
|
+
closeMenu()
|
|
1116
|
+
setNoteDraft(appearanceOf(look, target.workspaceId).note || '')
|
|
1117
|
+
setMenu({ kind: 'note', workspace: target, anchor })
|
|
1118
|
+
},
|
|
1119
|
+
}, 'Note…'),
|
|
958
1120
|
React.createElement('button', {
|
|
959
1121
|
type: 'button',
|
|
960
1122
|
'data-polish-ws-appearance': 'open',
|
|
@@ -980,6 +1142,79 @@ export function BetterWorkspace(props) {
|
|
|
980
1142
|
}, armed && armed === menu.workspace.workspaceId ? 'Remove workspace?' : 'Remove workspace'))
|
|
981
1143
|
: null
|
|
982
1144
|
|
|
1145
|
+
const archivedMenu = menu && menu.kind === 'archived'
|
|
1146
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1147
|
+
React.createElement('button', {
|
|
1148
|
+
type: 'button',
|
|
1149
|
+
'data-polish-ws-unarchive': menu.row.id,
|
|
1150
|
+
style: WS_MENU_ITEM,
|
|
1151
|
+
onClick: async () => {
|
|
1152
|
+
const row = menu.row
|
|
1153
|
+
closeMenu()
|
|
1154
|
+
try {
|
|
1155
|
+
await services.unarchiveSession(row.id)
|
|
1156
|
+
setNotice('Restored that session.')
|
|
1157
|
+
} catch {
|
|
1158
|
+
setNotice('Could not restore that session.')
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
}, 'Unarchive session'))
|
|
1162
|
+
: null
|
|
1163
|
+
|
|
1164
|
+
const glyphMenu = menu && menu.kind === 'glyph'
|
|
1165
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1166
|
+
React.createElement('div', { style: WS_GLYPH_GRID },
|
|
1167
|
+
GLYPHS.map((glyph) => React.createElement('button', {
|
|
1168
|
+
key: 'glyph:' + glyph,
|
|
1169
|
+
type: 'button',
|
|
1170
|
+
'data-polish-glyph-pick': glyph,
|
|
1171
|
+
title: 'Use ' + glyph,
|
|
1172
|
+
style: Object.assign({}, WS_GLYPH_BUTTON, {
|
|
1173
|
+
background: appearanceOf(look, menu.workspace.workspaceId).glyph === glyph
|
|
1174
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06))'
|
|
1175
|
+
: undefined,
|
|
1176
|
+
}),
|
|
1177
|
+
onClick: () => {
|
|
1178
|
+
const target = menu.workspace
|
|
1179
|
+
closeMenu()
|
|
1180
|
+
paintLook(target.workspaceId, { glyph })
|
|
1181
|
+
},
|
|
1182
|
+
}, glyph)),
|
|
1183
|
+
React.createElement('button', {
|
|
1184
|
+
key: 'glyph:none',
|
|
1185
|
+
type: 'button',
|
|
1186
|
+
'data-polish-glyph-pick': 'none',
|
|
1187
|
+
style: WS_GLYPH_BUTTON,
|
|
1188
|
+
onClick: () => {
|
|
1189
|
+
const target = menu.workspace
|
|
1190
|
+
closeMenu()
|
|
1191
|
+
paintLook(target.workspaceId, { glyph: '' })
|
|
1192
|
+
},
|
|
1193
|
+
}, '⌫')))
|
|
1194
|
+
: null
|
|
1195
|
+
|
|
1196
|
+
const noteMenu = menu && menu.kind === 'note'
|
|
1197
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1198
|
+
React.createElement('input', {
|
|
1199
|
+
'data-polish-note-input': 'true',
|
|
1200
|
+
type: 'text',
|
|
1201
|
+
value: noteDraft,
|
|
1202
|
+
placeholder: 'A note for this workspace',
|
|
1203
|
+
autoFocus: true,
|
|
1204
|
+
style: WS_NOTE_INPUT,
|
|
1205
|
+
onChange: (event) => setNoteDraft(event.target.value),
|
|
1206
|
+
onKeyDown: (event) => {
|
|
1207
|
+
if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
|
|
1208
|
+
if (event.key !== 'Enter') return
|
|
1209
|
+
const target = menu.workspace
|
|
1210
|
+
const value = noteDraft.trim()
|
|
1211
|
+
closeMenu()
|
|
1212
|
+
paintLook(target.workspaceId, { note: value })
|
|
1213
|
+
},
|
|
1214
|
+
}),
|
|
1215
|
+
React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
|
|
1216
|
+
: null
|
|
1217
|
+
|
|
983
1218
|
const paintMenu = menu && menu.kind === 'paint'
|
|
984
1219
|
? (() => {
|
|
985
1220
|
const workspace = menu.workspace
|
|
@@ -1093,6 +1328,19 @@ export function BetterWorkspace(props) {
|
|
|
1093
1328
|
style: WS_ADD,
|
|
1094
1329
|
onClick: foldAll,
|
|
1095
1330
|
}, allFolded ? '⌄' : '⌃'),
|
|
1331
|
+
React.createElement('button', {
|
|
1332
|
+
key: 'density',
|
|
1333
|
+
type: 'button',
|
|
1334
|
+
'data-polish-ws-density': densityOf(density),
|
|
1335
|
+
title: densityOf(density) === 'compact' ? 'Use comfortable rows' : 'Use compact rows',
|
|
1336
|
+
'aria-label': densityOf(density) === 'compact' ? 'Use comfortable rows' : 'Use compact rows',
|
|
1337
|
+
style: WS_ADD,
|
|
1338
|
+
onClick: () => {
|
|
1339
|
+
const next = densityOf(density) === 'compact' ? 'comfortable' : 'compact'
|
|
1340
|
+
setDensity(next)
|
|
1341
|
+
writeDensity(next)
|
|
1342
|
+
},
|
|
1343
|
+
}, densityOf(density) === 'compact' ? '≡' : '≣'),
|
|
1096
1344
|
React.createElement('button', {
|
|
1097
1345
|
key: 'add',
|
|
1098
1346
|
type: 'button',
|
|
@@ -1263,6 +1511,7 @@ export function BetterWorkspace(props) {
|
|
|
1263
1511
|
}),
|
|
1264
1512
|
onClick: () => toggleWorkspace(workspace.workspaceId),
|
|
1265
1513
|
onContextMenu: (event) => openWorkspaceMenu(event, workspace),
|
|
1514
|
+
title: looks.note || undefined,
|
|
1266
1515
|
onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
|
|
1267
1516
|
onMouseLeave: () => setHover(null),
|
|
1268
1517
|
onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
|
|
@@ -1275,7 +1524,9 @@ export function BetterWorkspace(props) {
|
|
|
1275
1524
|
onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
|
|
1276
1525
|
},
|
|
1277
1526
|
React.createElement(CaretGlyph, { open }),
|
|
1278
|
-
|
|
1527
|
+
looks.glyph
|
|
1528
|
+
? React.createElement('span', { 'data-polish-ws-glyph': looks.glyph, style: WS_GLYPH }, looks.glyph)
|
|
1529
|
+
: React.createElement(FolderGlyph),
|
|
1279
1530
|
looks.tone
|
|
1280
1531
|
? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
|
|
1281
1532
|
: null,
|
|
@@ -1301,6 +1552,36 @@ export function BetterWorkspace(props) {
|
|
|
1301
1552
|
}
|
|
1302
1553
|
})
|
|
1303
1554
|
|
|
1555
|
+
if (archivedSessions.length > 0) {
|
|
1556
|
+
children.push(React.createElement('div', {
|
|
1557
|
+
key: 'archived-head',
|
|
1558
|
+
'data-polish-ws-archived': 'head',
|
|
1559
|
+
style: WS_SECTION,
|
|
1560
|
+
onClick: () => setArchiveOpen((open) => !open),
|
|
1561
|
+
},
|
|
1562
|
+
React.createElement('span', { 'aria-hidden': 'true' }, archiveOpen ? '⌄' : '›'),
|
|
1563
|
+
React.createElement('span', { style: WS_TITLE }, 'Archived'),
|
|
1564
|
+
React.createElement('span', { style: WS_COUNT }, String(archivedSessions.length))))
|
|
1565
|
+
|
|
1566
|
+
if (archiveOpen) {
|
|
1567
|
+
for (const row of archivedSessions) {
|
|
1568
|
+
children.push(React.createElement('div', {
|
|
1569
|
+
key: 'archived:' + row.id,
|
|
1570
|
+
'data-polish-ws-archived-row': row.id,
|
|
1571
|
+
title: 'Archived session',
|
|
1572
|
+
style: Object.assign({}, WS_ROW, { opacity: 0.7, fontStyle: 'italic' }),
|
|
1573
|
+
onClick: () => services.openSession(row.id),
|
|
1574
|
+
onContextMenu: (event) => {
|
|
1575
|
+
event.preventDefault()
|
|
1576
|
+
event.stopPropagation()
|
|
1577
|
+
setMenu({ kind: 'archived', row, anchor: { top: event.clientY, left: event.clientX } })
|
|
1578
|
+
},
|
|
1579
|
+
},
|
|
1580
|
+
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id)))
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1304
1585
|
return React.createElement('div', {
|
|
1305
1586
|
'data-polish-ws': 'tree',
|
|
1306
1587
|
tabIndex: 0,
|
|
@@ -1317,6 +1598,7 @@ export function BetterWorkspace(props) {
|
|
|
1317
1598
|
overflowX: 'hidden',
|
|
1318
1599
|
padding: '2px 6px 12px',
|
|
1319
1600
|
outline: 'none',
|
|
1601
|
+
'--polish-ws-row': metrics.row,
|
|
1320
1602
|
},
|
|
1321
1603
|
},
|
|
1322
1604
|
children,
|
|
@@ -1325,6 +1607,9 @@ export function BetterWorkspace(props) {
|
|
|
1325
1607
|
: null,
|
|
1326
1608
|
switcherCard,
|
|
1327
1609
|
sessionMenu,
|
|
1610
|
+
glyphMenu,
|
|
1611
|
+
noteMenu,
|
|
1612
|
+
archivedMenu,
|
|
1328
1613
|
paintMenu,
|
|
1329
1614
|
workspaceMenu)
|
|
1330
1615
|
}
|
|
@@ -1451,6 +1736,7 @@ export function installWorkspaceSeat(ctx, configForms, extras = {}) {
|
|
|
1451
1736
|
reorderSession: (workspaceId, movingId, beforeId) => seated.workspaces.insertSessionBefore(workspaceId, movingId, beforeId),
|
|
1452
1737
|
removeWorkspace: (id) => seated.workspaces.delete(id),
|
|
1453
1738
|
renameWorkspace: (id, title) => seated.workspaces.rename(id, title),
|
|
1739
|
+
unarchiveSession: (id) => seated.workspaces.unarchiveSession(id),
|
|
1454
1740
|
openFolder: async (path) => {
|
|
1455
1741
|
const answer = await seated.remote.session.openWorkspacePath({ path })
|
|
1456
1742
|
if (answer && answer.ok === false) throw new Error('cannot open the folder')
|
package/docs/better-workspace.md
CHANGED
|
@@ -152,3 +152,37 @@ panel's stacking context can clip it.
|
|
|
152
152
|
like the way out. Verified across a real restart: folding `WORK` left
|
|
153
153
|
`{"workspaces":{},"folders":{"Work":true}}` in storage and the folder still read `› WORK 2`
|
|
154
154
|
with its workspace hidden.
|
|
155
|
+
|
|
156
|
+
## Density
|
|
157
|
+
|
|
158
|
+
The header carries a density control that flips between comfortable (28 px rows) and
|
|
159
|
+
compact (22 px rows), remembered under `aryacode-polish:density.v1`. Density changes row
|
|
160
|
+
height only: the text keeps its size, so a compact tree is still readable on a laptop.
|
|
161
|
+
The row height travels through the CSS variable `--polish-ws-row`, which the tree element
|
|
162
|
+
sets from `DENSITY_METRICS`; nothing else in the row styling depends on the mode.
|
|
163
|
+
|
|
164
|
+
## Archived sessions
|
|
165
|
+
|
|
166
|
+
Archiving a session removes it from its workspace — that is what the Host means by
|
|
167
|
+
archived, and the shipped browser simply stops rendering it. Here those sessions keep a
|
|
168
|
+
collapsed **Archived** section at the bottom of the tree instead, dimmed and italic, with
|
|
169
|
+
their count on the header. Clicking one opens it; right-clicking offers **Unarchive
|
|
170
|
+
session**, which goes through `workspaces.unarchiveSession`. This also fixed a real gap:
|
|
171
|
+
before this, `workspaceRows` never consulted `archivedSessionIds`, so archived sessions
|
|
172
|
+
were being rendered inline in their workspace as if they were live.
|
|
173
|
+
|
|
174
|
+
## Folding everything
|
|
175
|
+
|
|
176
|
+
The header's fold control expands when anything is folded (folders included) and folds
|
|
177
|
+
only when nothing is. An earlier version only looked at workspace rows, so a folded
|
|
178
|
+
folder left the button saying "Expand all" while its workspaces stayed hidden.
|
|
179
|
+
|
|
180
|
+
## Glyphs and notes
|
|
181
|
+
|
|
182
|
+
A workspace can wear an emoji badge instead of the folder icon (**Glyph…** in its menu, ten
|
|
183
|
+
badges plus a clear button) and carry a short note (**Note…**, Enter saves, Escape cancels).
|
|
184
|
+
Both live in the same appearance record as the colour — one localStorage key, one shape —
|
|
185
|
+
and the note surfaces as the row's tooltip, so it costs no vertical space in the tree.
|
|
186
|
+
|
|
187
|
+
Glyph validation is deliberately narrow (one to four characters): the field is a badge in a
|
|
188
|
+
16 px slot, not a place to paste a sentence.
|
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.16.0"
|
|
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. */
|
|
@@ -403,7 +403,7 @@ window.__ModuleLoader__.load({
|
|
|
403
403
|
gap: '6px',
|
|
404
404
|
boxSizing: 'border-box',
|
|
405
405
|
width: '100%',
|
|
406
|
-
height: '28px',
|
|
406
|
+
height: 'var(--polish-ws-row, 28px)',
|
|
407
407
|
padding: '0 6px',
|
|
408
408
|
border: 'none',
|
|
409
409
|
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
@@ -586,6 +586,116 @@ window.__ModuleLoader__.load({
|
|
|
586
586
|
const LOOK_KEY = 'aryacode-polish:workspace-appearance.v1'
|
|
587
587
|
const ORDER_KEY = 'aryacode-polish:workspace-order.v1'
|
|
588
588
|
const FOLD_KEY = 'aryacode-polish:folded.v1'
|
|
589
|
+
const DENSITY_KEY = 'aryacode-polish:density.v1'
|
|
590
|
+
|
|
591
|
+
const DENSITIES = ['comfortable', 'compact']
|
|
592
|
+
|
|
593
|
+
/** Row height per density: the tree fits more rows without shrinking the text. */
|
|
594
|
+
const DENSITY_METRICS = {
|
|
595
|
+
comfortable: { row: '28px' },
|
|
596
|
+
compact: { row: '22px' },
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function densityOf(value) {
|
|
600
|
+
return value === 'compact' ? 'compact' : 'comfortable'
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function readDensity(storage) {
|
|
604
|
+
try {
|
|
605
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
606
|
+
if (!store) return 'comfortable'
|
|
607
|
+
return densityOf(store.getItem(DENSITY_KEY))
|
|
608
|
+
} catch {
|
|
609
|
+
return 'comfortable'
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function writeDensity(value, storage) {
|
|
614
|
+
try {
|
|
615
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
616
|
+
if (!store) return
|
|
617
|
+
store.setItem(DENSITY_KEY, densityOf(value))
|
|
618
|
+
} catch {
|
|
619
|
+
/* remembering this is a nicety, not a requirement */
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Split the flat session list into the rows a workspace shows and the ones it has
|
|
625
|
+
* archived. The shipped browser hides archived sessions; here they keep a section of
|
|
626
|
+
* their own at the bottom instead of disappearing without a trace.
|
|
627
|
+
*/
|
|
628
|
+
function splitArchived(sessions, archivedIds) {
|
|
629
|
+
const archive = archivedIds instanceof Set ? archivedIds : new Set(archivedIds || [])
|
|
630
|
+
const active = []
|
|
631
|
+
const archived = []
|
|
632
|
+
for (const session of Array.isArray(sessions) ? sessions : []) {
|
|
633
|
+
if (archive.has(session.id)) archived.push(session)
|
|
634
|
+
else active.push(session)
|
|
635
|
+
}
|
|
636
|
+
return { active, archived }
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const WS_GLYPH_GRID = {
|
|
640
|
+
display: 'grid',
|
|
641
|
+
gridTemplateColumns: 'repeat(6, 26px)',
|
|
642
|
+
gap: '2px',
|
|
643
|
+
padding: '2px',
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const WS_GLYPH_BUTTON = {
|
|
647
|
+
width: '26px',
|
|
648
|
+
height: '26px',
|
|
649
|
+
display: 'inline-flex',
|
|
650
|
+
alignItems: 'center',
|
|
651
|
+
justifyContent: 'center',
|
|
652
|
+
padding: 0,
|
|
653
|
+
border: 'none',
|
|
654
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
655
|
+
background: 'transparent',
|
|
656
|
+
fontSize: '15px',
|
|
657
|
+
lineHeight: 1,
|
|
658
|
+
cursor: 'pointer',
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const WS_NOTE_INPUT = {
|
|
662
|
+
width: '220px',
|
|
663
|
+
boxSizing: 'border-box',
|
|
664
|
+
padding: '5px 8px',
|
|
665
|
+
border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
|
|
666
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
667
|
+
background: 'transparent',
|
|
668
|
+
color: 'inherit',
|
|
669
|
+
font: 'inherit',
|
|
670
|
+
outline: 'none',
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const WS_MENU_HINT = {
|
|
674
|
+
padding: '4px 8px 2px',
|
|
675
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 2px)',
|
|
676
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const WS_GLYPH = {
|
|
680
|
+
flex: 'none',
|
|
681
|
+
width: '16px',
|
|
682
|
+
textAlign: 'center',
|
|
683
|
+
fontSize: '13px',
|
|
684
|
+
lineHeight: 1,
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const WS_SECTION = {
|
|
688
|
+
display: 'flex',
|
|
689
|
+
alignItems: 'center',
|
|
690
|
+
gap: '5px',
|
|
691
|
+
padding: '8px 6px 2px',
|
|
692
|
+
textTransform: 'uppercase',
|
|
693
|
+
letterSpacing: '0.04em',
|
|
694
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
695
|
+
fontSize: 'calc(' + 'var(--dsh-content-font-size-secondary, 13px)' + ' - 3px)',
|
|
696
|
+
cursor: 'pointer',
|
|
697
|
+
userSelect: 'none',
|
|
698
|
+
}
|
|
589
699
|
|
|
590
700
|
/** Which workspaces and folders are folded, kept so a restart looks like yesterday. */
|
|
591
701
|
function readFolded(storage) {
|
|
@@ -784,19 +894,29 @@ window.__ModuleLoader__.load({
|
|
|
784
894
|
violet: '#7a5cc4',
|
|
785
895
|
}
|
|
786
896
|
|
|
897
|
+
/** Glyphs a workspace can wear instead of the folder icon. */
|
|
898
|
+
const GLYPHS = ['📁', '🚀', '🧪', '🐍', '⚙️', '📦', '🧭', '✨', '🧠', '🗂️']
|
|
899
|
+
|
|
787
900
|
function appearanceOf(map, workspaceId) {
|
|
788
901
|
const entry = (map && map[workspaceId]) || {}
|
|
789
902
|
return {
|
|
790
903
|
tone: TONE_COLOR[entry.tone] ? entry.tone : undefined,
|
|
791
904
|
strong: entry.strong === true,
|
|
905
|
+
glyph: typeof entry.glyph === 'string' && entry.glyph.length > 0 && entry.glyph.length <= 4 ? entry.glyph : undefined,
|
|
906
|
+
note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
|
|
792
907
|
}
|
|
793
908
|
}
|
|
794
909
|
|
|
795
910
|
function withAppearance(map, workspaceId, patch) {
|
|
796
911
|
const next = Object.assign({}, map || {})
|
|
797
912
|
const merged = Object.assign({}, appearanceOf(next, workspaceId), patch || {})
|
|
798
|
-
|
|
799
|
-
|
|
913
|
+
const entry = {}
|
|
914
|
+
if (merged.tone) entry.tone = merged.tone
|
|
915
|
+
if (merged.strong === true) entry.strong = true
|
|
916
|
+
if (merged.glyph) entry.glyph = merged.glyph
|
|
917
|
+
if (merged.note) entry.note = merged.note
|
|
918
|
+
if (Object.keys(entry).length === 0) delete next[workspaceId]
|
|
919
|
+
else next[workspaceId] = entry
|
|
800
920
|
return next
|
|
801
921
|
}
|
|
802
922
|
|
|
@@ -913,9 +1033,12 @@ window.__ModuleLoader__.load({
|
|
|
913
1033
|
const [drag, setDrag] = React.useState(null)
|
|
914
1034
|
const [dropping, setDropping] = React.useState(null)
|
|
915
1035
|
const [look, setLook] = React.useState(() => readAppearance())
|
|
1036
|
+
const [noteDraft, setNoteDraft] = React.useState('')
|
|
916
1037
|
const folded = React.useMemo(() => readFolded(), [])
|
|
917
1038
|
const [folders, setFolders] = React.useState(folded.folders)
|
|
918
1039
|
const [switcher, setSwitcher] = React.useState(null)
|
|
1040
|
+
const [density, setDensity] = React.useState(() => readDensity())
|
|
1041
|
+
const [archiveOpen, setArchiveOpen] = React.useState(false)
|
|
919
1042
|
React.useEffect(() => { writeFolded({ workspaces: expanded, folders }) }, [expanded, folders])
|
|
920
1043
|
const [renaming, setRenaming] = React.useState(null)
|
|
921
1044
|
const commitRename = async () => {
|
|
@@ -951,7 +1074,11 @@ window.__ModuleLoader__.load({
|
|
|
951
1074
|
() => new Set((workspaceState && workspaceState.pinnedSessionIds) || []),
|
|
952
1075
|
[workspaceState],
|
|
953
1076
|
)
|
|
954
|
-
const
|
|
1077
|
+
const metrics = DENSITY_METRICS[densityOf(density)]
|
|
1078
|
+
const archiveSet = new Set((workspaceState && workspaceState.archivedSessionIds) || [])
|
|
1079
|
+
const split = splitArchived(sessionList, archiveSet)
|
|
1080
|
+
const allRows = workspaceRows(workspaceList, split.active)
|
|
1081
|
+
const archivedSessions = split.archived
|
|
955
1082
|
const rows = filterTree(allRows, query, pinned)
|
|
956
1083
|
const folderOf = (workspace) => folderKey(workspace)
|
|
957
1084
|
const visibleRows = rows.filter((group) => {
|
|
@@ -964,7 +1091,12 @@ window.__ModuleLoader__.load({
|
|
|
964
1091
|
const entries = React.useMemo(() => rowMenuEntries(slots), [slots, workspaceList.length, sessionList.length])
|
|
965
1092
|
const totalCount = allRows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
966
1093
|
const shownCount = rows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
967
|
-
const
|
|
1094
|
+
const folderNames = Array.from(new Set(rows.map(row => folderKey(row.workspace)).filter(Boolean)))
|
|
1095
|
+
// One click should always do the useful thing: if anything is folded, the button
|
|
1096
|
+
// expands everything (folders included); only when nothing is folded does it fold.
|
|
1097
|
+
const anyFolded = rows.some(row => expanded[row.workspace.workspaceId] === false)
|
|
1098
|
+
|| folderNames.some(name => folders[name] === true)
|
|
1099
|
+
const allFolded = rows.length > 0 && !anyFolded
|
|
968
1100
|
|
|
969
1101
|
React.useEffect(() => {
|
|
970
1102
|
if (cursor >= walk.length) setCursor(walk.length - 1)
|
|
@@ -1034,10 +1166,17 @@ window.__ModuleLoader__.load({
|
|
|
1034
1166
|
})
|
|
1035
1167
|
}
|
|
1036
1168
|
const foldAll = () => {
|
|
1037
|
-
if (
|
|
1169
|
+
if (anyFolded) {
|
|
1170
|
+
setExpanded({})
|
|
1171
|
+
setFolders({})
|
|
1172
|
+
return
|
|
1173
|
+
}
|
|
1038
1174
|
const next = {}
|
|
1039
1175
|
rows.forEach((row) => { next[row.workspace.workspaceId] = false })
|
|
1176
|
+
const nextFolders = {}
|
|
1177
|
+
folderNames.forEach((name) => { nextFolders[name] = true })
|
|
1040
1178
|
setExpanded(next)
|
|
1179
|
+
setFolders(nextFolders)
|
|
1041
1180
|
}
|
|
1042
1181
|
|
|
1043
1182
|
const reorderWorkspace = async (movingId, beforeId) => {
|
|
@@ -1156,6 +1295,29 @@ window.__ModuleLoader__.load({
|
|
|
1156
1295
|
}
|
|
1157
1296
|
},
|
|
1158
1297
|
}, 'Restore saved order'),
|
|
1298
|
+
React.createElement('button', {
|
|
1299
|
+
type: 'button',
|
|
1300
|
+
'data-polish-glyph': 'open',
|
|
1301
|
+
style: WS_MENU_ITEM,
|
|
1302
|
+
onClick: () => {
|
|
1303
|
+
const target = menu.workspace
|
|
1304
|
+
const anchor = menu.anchor
|
|
1305
|
+
closeMenu()
|
|
1306
|
+
setMenu({ kind: 'glyph', workspace: target, anchor })
|
|
1307
|
+
},
|
|
1308
|
+
}, 'Glyph…'),
|
|
1309
|
+
React.createElement('button', {
|
|
1310
|
+
type: 'button',
|
|
1311
|
+
'data-polish-note': 'open',
|
|
1312
|
+
style: WS_MENU_ITEM,
|
|
1313
|
+
onClick: () => {
|
|
1314
|
+
const target = menu.workspace
|
|
1315
|
+
const anchor = menu.anchor
|
|
1316
|
+
closeMenu()
|
|
1317
|
+
setNoteDraft(appearanceOf(look, target.workspaceId).note || '')
|
|
1318
|
+
setMenu({ kind: 'note', workspace: target, anchor })
|
|
1319
|
+
},
|
|
1320
|
+
}, 'Note…'),
|
|
1159
1321
|
React.createElement('button', {
|
|
1160
1322
|
type: 'button',
|
|
1161
1323
|
'data-polish-ws-appearance': 'open',
|
|
@@ -1181,6 +1343,79 @@ window.__ModuleLoader__.load({
|
|
|
1181
1343
|
}, armed && armed === menu.workspace.workspaceId ? 'Remove workspace?' : 'Remove workspace'))
|
|
1182
1344
|
: null
|
|
1183
1345
|
|
|
1346
|
+
const archivedMenu = menu && menu.kind === 'archived'
|
|
1347
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1348
|
+
React.createElement('button', {
|
|
1349
|
+
type: 'button',
|
|
1350
|
+
'data-polish-ws-unarchive': menu.row.id,
|
|
1351
|
+
style: WS_MENU_ITEM,
|
|
1352
|
+
onClick: async () => {
|
|
1353
|
+
const row = menu.row
|
|
1354
|
+
closeMenu()
|
|
1355
|
+
try {
|
|
1356
|
+
await services.unarchiveSession(row.id)
|
|
1357
|
+
setNotice('Restored that session.')
|
|
1358
|
+
} catch {
|
|
1359
|
+
setNotice('Could not restore that session.')
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
}, 'Unarchive session'))
|
|
1363
|
+
: null
|
|
1364
|
+
|
|
1365
|
+
const glyphMenu = menu && menu.kind === 'glyph'
|
|
1366
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1367
|
+
React.createElement('div', { style: WS_GLYPH_GRID },
|
|
1368
|
+
GLYPHS.map((glyph) => React.createElement('button', {
|
|
1369
|
+
key: 'glyph:' + glyph,
|
|
1370
|
+
type: 'button',
|
|
1371
|
+
'data-polish-glyph-pick': glyph,
|
|
1372
|
+
title: 'Use ' + glyph,
|
|
1373
|
+
style: Object.assign({}, WS_GLYPH_BUTTON, {
|
|
1374
|
+
background: appearanceOf(look, menu.workspace.workspaceId).glyph === glyph
|
|
1375
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06))'
|
|
1376
|
+
: undefined,
|
|
1377
|
+
}),
|
|
1378
|
+
onClick: () => {
|
|
1379
|
+
const target = menu.workspace
|
|
1380
|
+
closeMenu()
|
|
1381
|
+
paintLook(target.workspaceId, { glyph })
|
|
1382
|
+
},
|
|
1383
|
+
}, glyph)),
|
|
1384
|
+
React.createElement('button', {
|
|
1385
|
+
key: 'glyph:none',
|
|
1386
|
+
type: 'button',
|
|
1387
|
+
'data-polish-glyph-pick': 'none',
|
|
1388
|
+
style: WS_GLYPH_BUTTON,
|
|
1389
|
+
onClick: () => {
|
|
1390
|
+
const target = menu.workspace
|
|
1391
|
+
closeMenu()
|
|
1392
|
+
paintLook(target.workspaceId, { glyph: '' })
|
|
1393
|
+
},
|
|
1394
|
+
}, '⌫')))
|
|
1395
|
+
: null
|
|
1396
|
+
|
|
1397
|
+
const noteMenu = menu && menu.kind === 'note'
|
|
1398
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1399
|
+
React.createElement('input', {
|
|
1400
|
+
'data-polish-note-input': 'true',
|
|
1401
|
+
type: 'text',
|
|
1402
|
+
value: noteDraft,
|
|
1403
|
+
placeholder: 'A note for this workspace',
|
|
1404
|
+
autoFocus: true,
|
|
1405
|
+
style: WS_NOTE_INPUT,
|
|
1406
|
+
onChange: (event) => setNoteDraft(event.target.value),
|
|
1407
|
+
onKeyDown: (event) => {
|
|
1408
|
+
if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
|
|
1409
|
+
if (event.key !== 'Enter') return
|
|
1410
|
+
const target = menu.workspace
|
|
1411
|
+
const value = noteDraft.trim()
|
|
1412
|
+
closeMenu()
|
|
1413
|
+
paintLook(target.workspaceId, { note: value })
|
|
1414
|
+
},
|
|
1415
|
+
}),
|
|
1416
|
+
React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
|
|
1417
|
+
: null
|
|
1418
|
+
|
|
1184
1419
|
const paintMenu = menu && menu.kind === 'paint'
|
|
1185
1420
|
? (() => {
|
|
1186
1421
|
const workspace = menu.workspace
|
|
@@ -1294,6 +1529,19 @@ window.__ModuleLoader__.load({
|
|
|
1294
1529
|
style: WS_ADD,
|
|
1295
1530
|
onClick: foldAll,
|
|
1296
1531
|
}, allFolded ? '⌄' : '⌃'),
|
|
1532
|
+
React.createElement('button', {
|
|
1533
|
+
key: 'density',
|
|
1534
|
+
type: 'button',
|
|
1535
|
+
'data-polish-ws-density': densityOf(density),
|
|
1536
|
+
title: densityOf(density) === 'compact' ? 'Use comfortable rows' : 'Use compact rows',
|
|
1537
|
+
'aria-label': densityOf(density) === 'compact' ? 'Use comfortable rows' : 'Use compact rows',
|
|
1538
|
+
style: WS_ADD,
|
|
1539
|
+
onClick: () => {
|
|
1540
|
+
const next = densityOf(density) === 'compact' ? 'comfortable' : 'compact'
|
|
1541
|
+
setDensity(next)
|
|
1542
|
+
writeDensity(next)
|
|
1543
|
+
},
|
|
1544
|
+
}, densityOf(density) === 'compact' ? '≡' : '≣'),
|
|
1297
1545
|
React.createElement('button', {
|
|
1298
1546
|
key: 'add',
|
|
1299
1547
|
type: 'button',
|
|
@@ -1464,6 +1712,7 @@ window.__ModuleLoader__.load({
|
|
|
1464
1712
|
}),
|
|
1465
1713
|
onClick: () => toggleWorkspace(workspace.workspaceId),
|
|
1466
1714
|
onContextMenu: (event) => openWorkspaceMenu(event, workspace),
|
|
1715
|
+
title: looks.note || undefined,
|
|
1467
1716
|
onMouseEnter: () => setHover('workspace:' + workspace.workspaceId),
|
|
1468
1717
|
onMouseLeave: () => setHover(null),
|
|
1469
1718
|
onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
|
|
@@ -1476,7 +1725,9 @@ window.__ModuleLoader__.load({
|
|
|
1476
1725
|
onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
|
|
1477
1726
|
},
|
|
1478
1727
|
React.createElement(CaretGlyph, { open }),
|
|
1479
|
-
|
|
1728
|
+
looks.glyph
|
|
1729
|
+
? React.createElement('span', { 'data-polish-ws-glyph': looks.glyph, style: WS_GLYPH }, looks.glyph)
|
|
1730
|
+
: React.createElement(FolderGlyph),
|
|
1480
1731
|
looks.tone
|
|
1481
1732
|
? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
|
|
1482
1733
|
: null,
|
|
@@ -1502,6 +1753,36 @@ window.__ModuleLoader__.load({
|
|
|
1502
1753
|
}
|
|
1503
1754
|
})
|
|
1504
1755
|
|
|
1756
|
+
if (archivedSessions.length > 0) {
|
|
1757
|
+
children.push(React.createElement('div', {
|
|
1758
|
+
key: 'archived-head',
|
|
1759
|
+
'data-polish-ws-archived': 'head',
|
|
1760
|
+
style: WS_SECTION,
|
|
1761
|
+
onClick: () => setArchiveOpen((open) => !open),
|
|
1762
|
+
},
|
|
1763
|
+
React.createElement('span', { 'aria-hidden': 'true' }, archiveOpen ? '⌄' : '›'),
|
|
1764
|
+
React.createElement('span', { style: WS_TITLE }, 'Archived'),
|
|
1765
|
+
React.createElement('span', { style: WS_COUNT }, String(archivedSessions.length))))
|
|
1766
|
+
|
|
1767
|
+
if (archiveOpen) {
|
|
1768
|
+
for (const row of archivedSessions) {
|
|
1769
|
+
children.push(React.createElement('div', {
|
|
1770
|
+
key: 'archived:' + row.id,
|
|
1771
|
+
'data-polish-ws-archived-row': row.id,
|
|
1772
|
+
title: 'Archived session',
|
|
1773
|
+
style: Object.assign({}, WS_ROW, { opacity: 0.7, fontStyle: 'italic' }),
|
|
1774
|
+
onClick: () => services.openSession(row.id),
|
|
1775
|
+
onContextMenu: (event) => {
|
|
1776
|
+
event.preventDefault()
|
|
1777
|
+
event.stopPropagation()
|
|
1778
|
+
setMenu({ kind: 'archived', row, anchor: { top: event.clientY, left: event.clientX } })
|
|
1779
|
+
},
|
|
1780
|
+
},
|
|
1781
|
+
React.createElement('span', { style: WS_TITLE }, row.displayTitle || row.id)))
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1505
1786
|
return React.createElement('div', {
|
|
1506
1787
|
'data-polish-ws': 'tree',
|
|
1507
1788
|
tabIndex: 0,
|
|
@@ -1518,6 +1799,7 @@ window.__ModuleLoader__.load({
|
|
|
1518
1799
|
overflowX: 'hidden',
|
|
1519
1800
|
padding: '2px 6px 12px',
|
|
1520
1801
|
outline: 'none',
|
|
1802
|
+
'--polish-ws-row': metrics.row,
|
|
1521
1803
|
},
|
|
1522
1804
|
},
|
|
1523
1805
|
children,
|
|
@@ -1526,6 +1808,9 @@ window.__ModuleLoader__.load({
|
|
|
1526
1808
|
: null,
|
|
1527
1809
|
switcherCard,
|
|
1528
1810
|
sessionMenu,
|
|
1811
|
+
glyphMenu,
|
|
1812
|
+
noteMenu,
|
|
1813
|
+
archivedMenu,
|
|
1529
1814
|
paintMenu,
|
|
1530
1815
|
workspaceMenu)
|
|
1531
1816
|
}
|
|
@@ -1652,6 +1937,7 @@ window.__ModuleLoader__.load({
|
|
|
1652
1937
|
reorderSession: (workspaceId, movingId, beforeId) => seated.workspaces.insertSessionBefore(workspaceId, movingId, beforeId),
|
|
1653
1938
|
removeWorkspace: (id) => seated.workspaces.delete(id),
|
|
1654
1939
|
renameWorkspace: (id, title) => seated.workspaces.rename(id, title),
|
|
1940
|
+
unarchiveSession: (id) => seated.workspaces.unarchiveSession(id),
|
|
1655
1941
|
openFolder: async (path) => {
|
|
1656
1942
|
const answer = await seated.remote.session.openWorkspacePath({ path })
|
|
1657
1943
|
if (answer && answer.ok === false) throw new Error('cannot open the folder')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-aryacode-polish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
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",
|