dsh-vscode-mode 0.14.13 → 0.15.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/README.md +5 -3
- package/lib/client.js +2849 -1466
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/paths.ts +2 -0
- package/src/client/state/documentStore.ts +148 -0
- package/src/client/state/editorLayout.ts +179 -0
- package/src/client/state/modelCache.ts +16 -7
- package/src/client/styles/editor.css +29 -3
- package/src/client/tabDrag.ts +137 -0
- package/src/client/tabMenu.ts +15 -3
- package/src/client/ui/EditorPane.ts +130 -0
- package/src/client/ui/EditorView.ts +358 -97
- package/src/client/ui/FloatingEditor.ts +87 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** In-page window chrome; closing returns tabs, never closes documents. @author ddj 2026年10月14号 */
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { createPortal } from 'react-dom'
|
|
4
|
+
import { clampFloat, type FloatRect } from '../state/editorLayout.js'
|
|
5
|
+
|
|
6
|
+
export interface FloatingProps {
|
|
7
|
+
group: string
|
|
8
|
+
owner: string
|
|
9
|
+
rect: FloatRect
|
|
10
|
+
focused: boolean
|
|
11
|
+
layer: number
|
|
12
|
+
onBounds(group: string, rect: FloatRect): void
|
|
13
|
+
onFocus(group: string): void
|
|
14
|
+
onReturn(group: string): void
|
|
15
|
+
children?: React.ReactNode
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @public @author ddj 2026年10月14号 @param props Window geometry and group callbacks. @returns Theme-aware portal with move/resize handles. */
|
|
19
|
+
export function FloatingEditor(props: FloatingProps): React.ReactPortal {
|
|
20
|
+
const latest = React.useRef(props)
|
|
21
|
+
latest.current = props
|
|
22
|
+
const cleanup = React.useRef<(() => void) | null>(null)
|
|
23
|
+
/** @private @author ddj 2026年10月14号 Cancel a gesture on unmount without altering tabs. */
|
|
24
|
+
function cancel(): void { cleanup.current?.(); cleanup.current = null }
|
|
25
|
+
React.useEffect(() => cancel, [])
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
/** @private @author ddj 2026年10月14号 Keep title/chrome visible after viewport changes. */
|
|
28
|
+
function resize(): void {
|
|
29
|
+
const current = latest.current
|
|
30
|
+
current.onBounds(current.group, clampFloat(current.rect, window.innerWidth, window.innerHeight))
|
|
31
|
+
}
|
|
32
|
+
window.addEventListener('resize', resize)
|
|
33
|
+
resize()
|
|
34
|
+
return () => window.removeEventListener('resize', resize)
|
|
35
|
+
}, [])
|
|
36
|
+
|
|
37
|
+
/** @private @author ddj 2026年10月14号 @param event Handle press. @param sizing Resize rather than move. */
|
|
38
|
+
function start(event: React.PointerEvent, sizing: boolean): void {
|
|
39
|
+
if (event.button !== 0 || !event.isPrimary) return
|
|
40
|
+
cancel()
|
|
41
|
+
event.preventDefault()
|
|
42
|
+
const initial = latest.current.rect
|
|
43
|
+
const x = event.clientX, y = event.clientY, pointer = event.pointerId
|
|
44
|
+
let frame: number | null = null
|
|
45
|
+
let next = initial
|
|
46
|
+
latest.current.onFocus(props.group)
|
|
47
|
+
/** @private @author ddj 2026年10月14号 Apply at most one geometry change per animation frame. */
|
|
48
|
+
function paint(): void { frame = null; latest.current.onBounds(props.group, next) }
|
|
49
|
+
/** @private @author ddj 2026年10月14号 @param moveEvent Pointer movement for the originating pointer. */
|
|
50
|
+
function move(moveEvent: PointerEvent): void {
|
|
51
|
+
if (moveEvent.pointerId !== pointer) return
|
|
52
|
+
moveEvent.preventDefault()
|
|
53
|
+
const dx = moveEvent.clientX - x, dy = moveEvent.clientY - y
|
|
54
|
+
next = clampFloat(sizing ? { ...initial, width: initial.width + dx, height: initial.height + dy } : { ...initial, left: initial.left + dx, top: initial.top + dy }, window.innerWidth, window.innerHeight)
|
|
55
|
+
if (frame === null) frame = requestAnimationFrame(paint)
|
|
56
|
+
}
|
|
57
|
+
/** @private @author ddj 2026年10月14号 Remove gesture listeners and pending frames. */
|
|
58
|
+
function stop(): void {
|
|
59
|
+
if (frame !== null) { cancelAnimationFrame(frame); paint() }
|
|
60
|
+
window.removeEventListener('pointermove', move, true)
|
|
61
|
+
window.removeEventListener('pointerup', stop, true)
|
|
62
|
+
window.removeEventListener('pointercancel', stop, true)
|
|
63
|
+
window.removeEventListener('blur', stop)
|
|
64
|
+
cleanup.current = null
|
|
65
|
+
}
|
|
66
|
+
cleanup.current = stop
|
|
67
|
+
window.addEventListener('pointermove', move, true)
|
|
68
|
+
window.addEventListener('pointerup', stop, true)
|
|
69
|
+
window.addEventListener('pointercancel', stop, true)
|
|
70
|
+
window.addEventListener('blur', stop)
|
|
71
|
+
}
|
|
72
|
+
/** @private @author ddj 2026年10月14号 @param event Title press. Move window without stealing button presses. */
|
|
73
|
+
function moveStart(event: React.PointerEvent): void { if (!(event.target as Element).closest('button')) start(event, false) }
|
|
74
|
+
/** @private @author ddj 2026年10月14号 @param event Resize grip press. */
|
|
75
|
+
function sizeStart(event: React.PointerEvent): void { start(event, true) }
|
|
76
|
+
/** @private @author ddj 2026年10月14号 Return all tabs to the main group. */
|
|
77
|
+
function returnTabs(): void { latest.current.onReturn(props.group) }
|
|
78
|
+
/** @private @author ddj 2026年10月14号 Bring the group to the foreground. */
|
|
79
|
+
function focus(): void { latest.current.onFocus(props.group) }
|
|
80
|
+
const rect = clampFloat(props.rect, window.innerWidth, window.innerHeight)
|
|
81
|
+
return createPortal(React.createElement('div', { className: 'edrv-floating' + (props.focused ? ' edrv-floating-focused' : ''), 'data-edrv-view': '1', 'data-edrv-float': props.group, 'data-edrv-owner': props.owner, style: { ...rect, zIndex: (props.focused ? 80 : 60) + props.layer }, onPointerDownCapture: focus },
|
|
82
|
+
React.createElement('div', { className: 'edrv-floating-title', onPointerDown: moveStart },
|
|
83
|
+
React.createElement('span', null, '浮动编辑组'), React.createElement('button', { type: 'button', title: '将全部文件移回主编辑区', onClick: returnTabs }, '移回主编辑区'),
|
|
84
|
+
React.createElement('button', { type: 'button', title: '关闭浮窗并将文件移回主编辑区', 'aria-label': '关闭浮窗并移回文件', onClick: returnTabs }, '×')),
|
|
85
|
+
props.children,
|
|
86
|
+
React.createElement('div', { className: 'edrv-floating-grip', title: '拖动调整窗口大小', onPointerDown: sizeStart })), document.body)
|
|
87
|
+
}
|