dsh-better-workspace 0.34.0 → 0.34.1
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/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +173 -11
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-better-workspace",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.1",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);新会话页的工作区选择器同树。Workspace tree for the DSH sidebar workspace list, nested by official disk folders with optional \"/\"-name grouping; the New Session workspace picker shows the same tree.",
|
|
6
6
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-better-workspace",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.1",
|
|
4
4
|
"description": "侧边栏工作区树:按官方磁盘目录嵌套(官方 workspace-tree 语义),可选按名称中的 / 分组(默认关闭,设置里开启);新会话页的工作区选择器同树(同一份视图选项即时同步);添加工作区走官方目录流。Workspace tree for the DSH sidebar: disk-path nesting (official workspace-tree semantics), with optional \"/\"-name grouping (off by default), and the same tree in the New Session workspace picker; add-workspace rides the official directory flow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/client.js
CHANGED
|
@@ -3905,6 +3905,148 @@ window.__ModuleLoader__.load({
|
|
|
3905
3905
|
return String(r.n) + (FALLBACK_UNITS[r.unit] || '')
|
|
3906
3906
|
}
|
|
3907
3907
|
|
|
3908
|
+
// Port of ui-workspace rows/AnimatedRows.tsx — the package entry exports
|
|
3909
|
+
// only apply/inject, so the component cannot be required (same discipline
|
|
3910
|
+
// as the row-style port). FLIP glide + entry/exit fades for keyed rows:
|
|
3911
|
+
// armed by the first pointer/key input, skipped when membership, order,
|
|
3912
|
+
// readiness or the reset key did not change. Deltas from upstream: the
|
|
3913
|
+
// plugin tree rolls its own indent var (--bw-indent, not
|
|
3914
|
+
// --dsh-workspace-indent), the exit overlay is viewport-FIXED because no
|
|
3915
|
+
// positioned ancestor is guaranteed here (clone math subtracts the
|
|
3916
|
+
// overlay rect either way), and the list div takes an attrs passthrough
|
|
3917
|
+
// (data-bw-density / data-bw-count) plus innerRef (autoStyle samples it).
|
|
3918
|
+
const ROW_FADE_MS = 100
|
|
3919
|
+
const ROW_GLIDE_MS = 200
|
|
3920
|
+
const sameRowKeys = (previous, next) => previous.length === next.length
|
|
3921
|
+
&& previous.every((key, index) => key === next[index])
|
|
3922
|
+
const intersectsViewport = (rect, viewport) => rect.bottom > viewport.top && rect.top < viewport.bottom
|
|
3923
|
+
&& rect.right > viewport.left && rect.left < viewport.right
|
|
3924
|
+
class AnimatedRows extends React.Component {
|
|
3925
|
+
constructor(props) {
|
|
3926
|
+
super(props)
|
|
3927
|
+
this.armed = false
|
|
3928
|
+
this.list = { current: null }
|
|
3929
|
+
this.overlay = React.createRef()
|
|
3930
|
+
this.movements = new Map()
|
|
3931
|
+
this.exits = new Map()
|
|
3932
|
+
}
|
|
3933
|
+
getSnapshotBeforeUpdate(previous) {
|
|
3934
|
+
const list = this.list.current
|
|
3935
|
+
if (!this.armed || sameRowKeys(previous.rowKeys, this.props.rowKeys) || previous.resetKey !== this.props.resetKey
|
|
3936
|
+
|| !previous.ready || !this.props.ready || list === null
|
|
3937
|
+
|| typeof list.animate !== 'function'
|
|
3938
|
+
|| window.matchMedia('(prefers-reduced-motion: reduce)').matches) return null
|
|
3939
|
+
const viewport = list.getBoundingClientRect()
|
|
3940
|
+
const positions = this.readPositions()
|
|
3941
|
+
const nextKeys = new Set(this.props.rowKeys)
|
|
3942
|
+
const removed = new Map()
|
|
3943
|
+
for (const [key, row] of positions) {
|
|
3944
|
+
if (nextKeys.has(key) || !intersectsViewport(row.rect, viewport)) continue
|
|
3945
|
+
const clone = row.element.cloneNode(true)
|
|
3946
|
+
clone.removeAttribute('data-row-key')
|
|
3947
|
+
clone.inert = true
|
|
3948
|
+
clone.style.setProperty('--bw-indent', getComputedStyle(row.element).getPropertyValue('--bw-indent'))
|
|
3949
|
+
removed.set(key, Object.assign({}, row, { element: clone }))
|
|
3950
|
+
}
|
|
3951
|
+
return { positions, removed }
|
|
3952
|
+
}
|
|
3953
|
+
componentDidUpdate(previous, _state, snapshot) {
|
|
3954
|
+
if (snapshot === null) {
|
|
3955
|
+
if (!sameRowKeys(previous.rowKeys, this.props.rowKeys) || previous.resetKey !== this.props.resetKey
|
|
3956
|
+
|| previous.ready !== this.props.ready) this.clear()
|
|
3957
|
+
return
|
|
3958
|
+
}
|
|
3959
|
+
this.cancelMovements()
|
|
3960
|
+
const list = this.list.current
|
|
3961
|
+
const overlay = this.overlay.current
|
|
3962
|
+
const viewport = list.getBoundingClientRect()
|
|
3963
|
+
const origin = overlay.getBoundingClientRect()
|
|
3964
|
+
const positions = this.readPositions()
|
|
3965
|
+
for (const [key, row] of positions) {
|
|
3966
|
+
this.removeExit(key)
|
|
3967
|
+
const previousRow = snapshot.positions.get(key)
|
|
3968
|
+
if (!intersectsViewport(row.rect, viewport)
|
|
3969
|
+
&& (previousRow === undefined || !intersectsViewport(previousRow.rect, viewport))) continue
|
|
3970
|
+
if (previousRow === undefined) {
|
|
3971
|
+
this.move(row.element, [{ opacity: 0 }, { opacity: 1 }], ROW_FADE_MS)
|
|
3972
|
+
continue
|
|
3973
|
+
}
|
|
3974
|
+
const dx = previousRow.rect.left - row.rect.left
|
|
3975
|
+
const dy = previousRow.rect.top - row.rect.top
|
|
3976
|
+
if (dx === 0 && dy === 0 && previousRow.opacity === 1) continue
|
|
3977
|
+
this.move(row.element, [
|
|
3978
|
+
{ transform: 'translate(' + String(dx) + 'px, ' + String(dy) + 'px)', opacity: previousRow.opacity },
|
|
3979
|
+
{ transform: 'translate(0, 0)', opacity: 1 },
|
|
3980
|
+
], ROW_GLIDE_MS)
|
|
3981
|
+
}
|
|
3982
|
+
for (const [key, row] of snapshot.removed) {
|
|
3983
|
+
const { element } = row
|
|
3984
|
+
this.removeExit(key)
|
|
3985
|
+
Object.assign(element.style, {
|
|
3986
|
+
position: 'absolute', margin: '0', transform: 'none', boxSizing: 'border-box',
|
|
3987
|
+
left: String(row.rect.left - origin.left) + 'px',
|
|
3988
|
+
top: String(row.rect.top - origin.top) + 'px',
|
|
3989
|
+
width: String(row.rect.width) + 'px', height: String(row.rect.height) + 'px',
|
|
3990
|
+
})
|
|
3991
|
+
overlay.append(element)
|
|
3992
|
+
const animation = element.animate([{ opacity: row.opacity }, { opacity: 0 }], {
|
|
3993
|
+
duration: ROW_FADE_MS, easing: 'ease-out', fill: 'forwards',
|
|
3994
|
+
})
|
|
3995
|
+
this.exits.set(key, { element, animation })
|
|
3996
|
+
animation.onfinish = () => { this.removeExit(key) }
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
componentWillUnmount() { this.clear() }
|
|
4000
|
+
readPositions() {
|
|
4001
|
+
const list = this.list.current
|
|
4002
|
+
const rows = list.querySelectorAll('[data-row-key]')
|
|
4003
|
+
return new Map(Array.from(rows, (element) => [element.dataset.rowKey, {
|
|
4004
|
+
element,
|
|
4005
|
+
rect: element.getBoundingClientRect(),
|
|
4006
|
+
opacity: this.movements.has(element) ? Number(getComputedStyle(element).opacity) : 1,
|
|
4007
|
+
}]))
|
|
4008
|
+
}
|
|
4009
|
+
move(element, keyframes, duration) {
|
|
4010
|
+
const animation = element.animate(keyframes, { duration, easing: 'ease-out' })
|
|
4011
|
+
this.movements.set(element, animation)
|
|
4012
|
+
animation.onfinish = () => { this.movements.delete(element); animation.cancel() }
|
|
4013
|
+
}
|
|
4014
|
+
cancelMovements() {
|
|
4015
|
+
for (const animation of this.movements.values()) {
|
|
4016
|
+
animation.onfinish = null
|
|
4017
|
+
animation.cancel()
|
|
4018
|
+
}
|
|
4019
|
+
this.movements.clear()
|
|
4020
|
+
}
|
|
4021
|
+
removeExit(key) {
|
|
4022
|
+
const exit = this.exits.get(key)
|
|
4023
|
+
if (exit === undefined) return
|
|
4024
|
+
exit.animation.onfinish = null
|
|
4025
|
+
exit.animation.cancel()
|
|
4026
|
+
exit.element.remove()
|
|
4027
|
+
this.exits.delete(key)
|
|
4028
|
+
}
|
|
4029
|
+
clear() {
|
|
4030
|
+
this.cancelMovements()
|
|
4031
|
+
for (const key of Array.from(this.exits.keys())) this.removeExit(key)
|
|
4032
|
+
}
|
|
4033
|
+
render() {
|
|
4034
|
+
const attrs = this.props.attrs || {}
|
|
4035
|
+
return [
|
|
4036
|
+
E('div', Object.assign({
|
|
4037
|
+
key: 'list',
|
|
4038
|
+
ref: (el) => { this.list.current = el; if (this.props.innerRef) this.props.innerRef.current = el },
|
|
4039
|
+
className: this.props.className,
|
|
4040
|
+
role: 'tree',
|
|
4041
|
+
'aria-label': this.props.label,
|
|
4042
|
+
onPointerDownCapture: () => { this.armed = true },
|
|
4043
|
+
onKeyDownCapture: () => { this.armed = true },
|
|
4044
|
+
}, attrs), this.props.children),
|
|
4045
|
+
E('div', { key: 'exits', ref: this.overlay, className: 'bw-rows-exits', 'aria-hidden': 'true' }),
|
|
4046
|
+
]
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
|
|
3908
4050
|
const pendingKindOf = (pending, id) => {
|
|
3909
4051
|
if (!pending) return undefined
|
|
3910
4052
|
const p = typeof pending.get === 'function' ? pending.get(id) : pending[id]
|
|
@@ -4570,6 +4712,7 @@ window.__ModuleLoader__.load({
|
|
|
4570
4712
|
'.bw-menu-block{display:block;width:100%}',
|
|
4571
4713
|
'.bw-menu-inline{display:inline-block;width:auto;flex:none}',
|
|
4572
4714
|
'.bw-tree{--bw-row-h:34px;--bw-session-h:32px;flex:1;overflow-y:auto;overflow-x:hidden;padding:2px 6px 12px;min-height:0}',
|
|
4715
|
+
'.bw-rows-exits{position:fixed;inset:0;contain:strict;overflow:clip;pointer-events:none}',
|
|
4573
4716
|
'.bw-tree[data-bw-density="compact"]{--bw-row-h:28px;--bw-session-h:26px}',
|
|
4574
4717
|
'.bw-tree[data-bw-density="roomy"]{--bw-row-h:40px;--bw-session-h:38px}',
|
|
4575
4718
|
'.bw-row{display:flex;align-items:center;gap:6px;height:var(--bw-row-h,34px);padding:0 8px;padding-inline-start:calc(8px + var(--bw-indent,0px));border-radius:var(--dsw-radius-md,8px);cursor:pointer;user-select:none;font-size:14px;line-height:20px;color:var(--dsw-alias-label-primary,#e6e6e6);position:relative}',
|
|
@@ -5872,7 +6015,7 @@ window.__ModuleLoader__.load({
|
|
|
5872
6015
|
}
|
|
5873
6016
|
}
|
|
5874
6017
|
|
|
5875
|
-
function FolderRow({ node, depth, expanded, onToggle, onContextMenu, dropInto, dragEvents, custStyle, iconMode, pulse, active, t }) {
|
|
6018
|
+
function FolderRow({ node, depth, expanded, onToggle, onContextMenu, dropInto, dragEvents, custStyle, iconMode, pulse, active, dataRowKey, t }) {
|
|
5876
6019
|
const total = countWorkspaces(node)
|
|
5877
6020
|
const iconEl = iconOf(iconMode, expanded)
|
|
5878
6021
|
// Hidden status dots breathe on the icon; with the icon hidden (mode
|
|
@@ -5882,6 +6025,7 @@ window.__ModuleLoader__.load({
|
|
|
5882
6025
|
: iconEl
|
|
5883
6026
|
return E('div', {
|
|
5884
6027
|
className: cls('bw-row', 'bw-container-row', dropInto && 'bw-drop-into', dropInto && 'bw-drop-into-strong'),
|
|
6028
|
+
'data-row-key': dataRowKey,
|
|
5885
6029
|
style: { '--bw-indent': (depth * 12) + 'px', ...(custStyle || {}), ...(pulse ? { '--bw-pulse-color': PULSE_COLORS[pulse] || PULSE_COLORS.ongoing } : null) },
|
|
5886
6030
|
onClick: onToggle,
|
|
5887
6031
|
onContextMenu: onContextMenu,
|
|
@@ -5900,13 +6044,14 @@ window.__ModuleLoader__.load({
|
|
|
5900
6044
|
)
|
|
5901
6045
|
}
|
|
5902
6046
|
|
|
5903
|
-
function WorkspaceRow({ workspace, depth, count, sessionsOpen, onToggle, onStart, onMenu, onContextMenu, currentInside, menuOpen, dropHalf, dropInto, dragEvents, custStyle, iconMode, pulse, newSessionLabel, newSessionAria, menuAria, hoverBody, t }) {
|
|
6047
|
+
function WorkspaceRow({ workspace, depth, count, sessionsOpen, onToggle, onStart, onMenu, onContextMenu, currentInside, menuOpen, dropHalf, dropInto, dragEvents, custStyle, iconMode, pulse, newSessionLabel, newSessionAria, menuAria, hoverBody, dataRowKey, t }) {
|
|
5904
6048
|
const iconEl = iconOf(iconMode, sessionsOpen)
|
|
5905
6049
|
const iconChild = pulse
|
|
5906
6050
|
? E(PulseGlow, { state: pulse }, iconEl || (typeof ui.StateDot === 'function' ? E(ui.StateDot, { state: pulse, size: 10 }) : null))
|
|
5907
6051
|
: iconEl
|
|
5908
6052
|
let row = E('div', {
|
|
5909
6053
|
className: cls('bw-row', 'bw-container-row', currentInside && 'bw-row-current', menuOpen && 'menu-open', dropHalf === 'before' && 'bw-drop-before', dropHalf === 'after' && 'bw-drop-after', dropInto && 'bw-drop-into', dropInto && 'bw-drop-into-strong'),
|
|
6054
|
+
'data-row-key': dataRowKey,
|
|
5910
6055
|
style: { '--bw-indent': (depth * 12) + 'px', ...(custStyle || {}), ...(pulse ? { '--bw-pulse-color': PULSE_COLORS[pulse] || PULSE_COLORS.ongoing } : null) },
|
|
5911
6056
|
onClick: onToggle,
|
|
5912
6057
|
onContextMenu: onContextMenu,
|
|
@@ -5937,7 +6082,7 @@ window.__ModuleLoader__.load({
|
|
|
5937
6082
|
'aria-keyshortcuts': newSessionAria,
|
|
5938
6083
|
title: newSessionLabel || t('session.new'),
|
|
5939
6084
|
onClick: (e) => { e.stopPropagation(); onStart() },
|
|
5940
|
-
}, icon('
|
|
6085
|
+
}, icon('IconNewChatOutline16')),
|
|
5941
6086
|
),
|
|
5942
6087
|
)
|
|
5943
6088
|
// Official workspace hover card (Rows.tsx WorkspaceHoverContent): the
|
|
@@ -6041,7 +6186,7 @@ window.__ModuleLoader__.load({
|
|
|
6041
6186
|
return { titleRef, onPointerEnter, onPointerLeave }
|
|
6042
6187
|
}
|
|
6043
6188
|
|
|
6044
|
-
function SessionRow({ node, depth, current, onOpen, onContextMenu, now, dropHalf, dragEvents, custStyle, breathing, sessionActions, scheduleSeat, t }) {
|
|
6189
|
+
function SessionRow({ node, depth, current, onOpen, onContextMenu, now, dropHalf, dragEvents, custStyle, breathing, sessionActions, scheduleSeat, dataRowKey, t }) {
|
|
6045
6190
|
const state = sessionStateOf(node)
|
|
6046
6191
|
const status = state === null ? null : {
|
|
6047
6192
|
state,
|
|
@@ -6159,6 +6304,7 @@ window.__ModuleLoader__.load({
|
|
|
6159
6304
|
node.archived && 'bw-row-archived',
|
|
6160
6305
|
menuOpen && 'menu-open',
|
|
6161
6306
|
dropHalf === 'before' && 'bw-drop-before', dropHalf === 'after' && 'bw-drop-after'),
|
|
6307
|
+
'data-row-key': dataRowKey,
|
|
6162
6308
|
style: { '--bw-indent': (depth * 12) + 'px', ...(custStyle || {}) },
|
|
6163
6309
|
onClick: () => {
|
|
6164
6310
|
if (node.archived) {
|
|
@@ -6228,9 +6374,10 @@ window.__ModuleLoader__.load({
|
|
|
6228
6374
|
* titles). Deliberately NOT styled like a workspace folder — no folder
|
|
6229
6375
|
* icon, tertiary color — so a session level never reads as a workspace.
|
|
6230
6376
|
*/
|
|
6231
|
-
function SessionGroupRow({ name, depth, expanded, count, onToggle, onContextMenu, dropInto, dragEvents, custStyle, pulse, t }) {
|
|
6377
|
+
function SessionGroupRow({ name, depth, expanded, count, onToggle, onContextMenu, dropInto, dragEvents, custStyle, pulse, dataRowKey, t }) {
|
|
6232
6378
|
return E('div', {
|
|
6233
6379
|
className: cls('bw-row', 'bw-sgroup-row', dropInto && 'bw-drop-into', dropInto && 'bw-drop-into-strong'),
|
|
6380
|
+
'data-row-key': dataRowKey,
|
|
6234
6381
|
style: { '--bw-indent': (depth * 12) + 'px', ...(custStyle || {}) },
|
|
6235
6382
|
onClick: onToggle,
|
|
6236
6383
|
onContextMenu: onContextMenu,
|
|
@@ -9274,6 +9421,17 @@ window.__ModuleLoader__.load({
|
|
|
9274
9421
|
)]
|
|
9275
9422
|
: [E('div', { key: 'empty', className: 'bw-empty' }, searching ? t('empty.search') : (phase === 'pending' ? '…' : t('empty')))]
|
|
9276
9423
|
}
|
|
9424
|
+
// Keyed-row handoff for AnimatedRows: every body row carries a stable
|
|
9425
|
+
// data-row-key (the element's React key when present, array index
|
|
9426
|
+
// otherwise) and the key list feeds the component's sameRows guard.
|
|
9427
|
+
bodyRows = bodyRows.filter((el) => el !== null && el !== false && el !== undefined)
|
|
9428
|
+
const bodyRowKeys = []
|
|
9429
|
+
bodyRows = bodyRows.map((el, index) => {
|
|
9430
|
+
if (!el || typeof el !== 'object') return el
|
|
9431
|
+
const rowKey = el.key != null ? String(el.key) : 'row-' + index
|
|
9432
|
+
bodyRowKeys.push(rowKey)
|
|
9433
|
+
return React.cloneElement(el, { 'data-row-key': rowKey, dataRowKey: rowKey })
|
|
9434
|
+
})
|
|
9277
9435
|
|
|
9278
9436
|
/* ------------------------- context menu ------------------------ */
|
|
9279
9437
|
|
|
@@ -9886,13 +10044,17 @@ window.__ModuleLoader__.load({
|
|
|
9886
10044
|
disabled: flowBusy,
|
|
9887
10045
|
}, icon('IconProjectAddOutline16')) : null,
|
|
9888
10046
|
),
|
|
9889
|
-
E(
|
|
9890
|
-
ref: treeRef,
|
|
10047
|
+
E(AnimatedRows, {
|
|
9891
10048
|
className: 'bw-tree',
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
10049
|
+
label: t('title'),
|
|
10050
|
+
rowKeys: bodyRowKeys,
|
|
10051
|
+
// Drag reorders rows itself; animating under it fights the gesture.
|
|
10052
|
+
ready: drag === null,
|
|
10053
|
+
// View-replacing switches (search, filter, density, count) settle
|
|
10054
|
+
// instead of gliding — official resetKey semantics.
|
|
10055
|
+
resetKey: JSON.stringify([density, showCount, searching, archivedOnly, normalizedQuery]),
|
|
10056
|
+
innerRef: treeRef,
|
|
10057
|
+
attrs: { 'data-bw-density': density, ...(showCount ? { 'data-bw-count': '' } : {}) },
|
|
9896
10058
|
}, bodyRows),
|
|
9897
10059
|
dialogElement,
|
|
9898
10060
|
(ctx !== null && typeof ui.Menu === 'function') ? E(ui.Menu, {
|