dsh-desktop-windows 0.2.4 → 0.2.6

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/screenshot.html +89 -74
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-desktop-windows",
3
3
  "productName": "DeepSeek Harness Desktop",
4
- "version": "0.2.4",
4
+ "version": "0.2.6",
5
5
  "description": "DeepSeek Harness 桌面版(Windows)· Electron desktop shell for DeepSeek Harness (dsh web) — 双击即用,无需终端 / double-click to run the harness in a standalone window",
6
6
  "main": "src/main.js",
7
7
  "license": "MIT",
@@ -8,11 +8,17 @@
8
8
  html, body { width: 100%; height: 100%; overflow: hidden; background: #000; cursor: crosshair; }
9
9
  #stage { position: fixed; inset: 0; }
10
10
  #stage img { width: 100%; height: 100%; object-fit: fill; display: block; }
11
- .mask { position: fixed; background: rgba(0, 0, 0, 0.45); pointer-events: none; }
11
+ /* 遮罩:全屏半透明黑 + clip-path 挖洞(GPU 加速,每帧只改字符串) */
12
+ #mask {
13
+ position: fixed; inset: 0; z-index: 2; pointer-events: none;
14
+ background: rgba(0, 0, 0, 0.45);
15
+ clip-path: polygon(0px 0px, 0px 0px, 0px 0px);
16
+ }
17
+ /* 选区框:border div,transform 定位 + 尺寸更新 */
12
18
  #sel {
13
- position: fixed; border: 1.5px solid #2563eb;
14
- background: rgba(37, 99, 235, 0.10);
15
- display: none; pointer-events: none; z-index: 3;
19
+ position: fixed; z-index: 3; pointer-events: none;
20
+ border: 1.5px solid #2563eb; background: rgba(37, 99, 235, 0.08);
21
+ transform-origin: 0 0;
16
22
  }
17
23
  #sel .sz {
18
24
  position: absolute; left: 0; top: -22px;
@@ -23,6 +29,7 @@
23
29
  position: fixed; width: 9px; height: 9px; background: #2563eb;
24
30
  border: 1px solid #fff; border-radius: 2px; display: none;
25
31
  pointer-events: auto; z-index: 4;
32
+ transform: translate(-50%, -50%);
26
33
  }
27
34
  #toolbar {
28
35
  position: fixed; display: none; align-items: center; gap: 8px;
@@ -32,8 +39,8 @@
32
39
  }
33
40
  #toolbar .tip { color: #94a3b8; font: 12px/1 sans-serif; margin-right: 4px; }
34
41
  #toolbar button {
35
- border: none; border-radius: 6px; padding: 6px 14px; cursor: pointer;
36
- font: 600 12px/1 sans-serif;
42
+ border: none; border-radius: 6px; padding: 7px 16px; cursor: pointer;
43
+ font: 600 13px/1 sans-serif;
37
44
  }
38
45
  #okBtn { background: #2563eb; color: #fff; }
39
46
  #okBtn:hover { background: #1d4ed8; }
@@ -49,10 +56,7 @@
49
56
  </head>
50
57
  <body>
51
58
  <div id="stage"><img id="bg" alt=""></div>
52
- <div id="maskTop" class="mask"></div>
53
- <div id="maskBottom" class="mask"></div>
54
- <div id="maskLeft" class="mask"></div>
55
- <div id="maskRight" class="mask"></div>
59
+ <div id="mask"></div>
56
60
  <div id="sel"><div class="sz"></div></div>
57
61
  <div id="hN" class="handle" style="cursor:n-resize"></div>
58
62
  <div id="hS" class="handle" style="cursor:s-resize"></div>
@@ -62,26 +66,21 @@
62
66
  <div id="hNW" class="handle" style="cursor:nw-resize"></div>
63
67
  <div id="hSE" class="handle" style="cursor:se-resize"></div>
64
68
  <div id="hSW" class="handle" style="cursor:sw-resize"></div>
65
- <div id="toolbar">
66
- <span class="tip" id="tip">拖动边框微调选区</span>
69
+ <div id="toolbar" style="cursor:default">
70
+ <span class="tip" id="tip">可拖拽边框微调 · 点确定或双击完成</span>
67
71
  <button id="okBtn">✔ 确定</button>
68
72
  <button id="cancelBtn">✕ 取消 (Esc)</button>
69
73
  </div>
70
- <div id="hint">拖动鼠标框选要截取的区域 · 画完后可拖拽边框微调 · Esc 取消</div>
74
+ <div id="hint">拖动鼠标框选要截取的区域 · 画完后可拖拽边框微调 · 双击完成 · Esc 取消</div>
71
75
 
72
76
  <script>
73
77
  var bg = document.getElementById('bg')
78
+ var mask = document.getElementById('mask')
74
79
  var sel = document.getElementById('sel')
80
+ var sz = sel.querySelector('.sz')
75
81
  var toolbar = document.getElementById('toolbar')
76
82
  var hint = document.getElementById('hint')
77
83
  var tip = document.getElementById('tip')
78
- var sz = sel.querySelector('.sz')
79
- var masks = {
80
- top: document.getElementById('maskTop'),
81
- bottom: document.getElementById('maskBottom'),
82
- left: document.getElementById('maskLeft'),
83
- right: document.getElementById('maskRight'),
84
- }
85
84
  var handles = {
86
85
  n: document.getElementById('hN'), s: document.getElementById('hS'),
87
86
  e: document.getElementById('hE'), w: document.getElementById('hW'),
@@ -93,10 +92,12 @@
93
92
  var rect = null
94
93
  var mode = 'draw' // draw | adjust
95
94
  var drag = null
95
+ var rafPending = false
96
96
 
97
97
  window.__screenshot.onData(function (dataUrl) {
98
98
  bg.onload = function () {
99
99
  window.__screenshot.loaded()
100
+ update()
100
101
  setTimeout(function () { hint.style.opacity = '0.9' }, 100)
101
102
  }
102
103
  bg.src = dataUrl
@@ -105,38 +106,58 @@
105
106
  function clampRect(r) {
106
107
  var x = Math.max(0, Math.min(W - MIN, r.x))
107
108
  var y = Math.max(0, Math.min(H - MIN, r.y))
108
- var w = Math.min(r.w, W - x)
109
- var h = Math.min(r.h, H - y)
110
- w = Math.max(MIN, w); h = Math.max(MIN, h)
109
+ var w = Math.max(MIN, Math.min(r.w, W - x))
110
+ var h = Math.max(MIN, Math.min(r.h, H - y))
111
111
  return { x: x, y: y, w: w, h: h }
112
112
  }
113
113
 
114
- function layout() {
115
- if (!rect) return
114
+ // 每帧只更新 2 个元素:mask 的 clip-path(GPU)+ sel 的位置尺寸
115
+ function update() {
116
+ if (!rect) {
117
+ mask.style.clipPath = 'polygon(0px 0px, 0px 0px, 0px 0px, 0px 0px, 0px 0px)'
118
+ sel.style.display = 'none'
119
+ hideHandles()
120
+ return
121
+ }
116
122
  var x = rect.x, y = rect.y, w = rect.w, h = rect.h
123
+ // clip-path:外框逆时针 + 选区顺时针 → 挖洞
124
+ mask.style.clipPath =
125
+ 'polygon(0px 0px, ' + W + 'px 0px, ' + W + 'px ' + H + 'px, 0px ' + H + 'px, 0px 0px, ' +
126
+ x + 'px ' + y + 'px, ' + (x + w) + 'px ' + y + 'px, ' + (x + w) + 'px ' + (y + h) + 'px, ' + x + 'px ' + (y + h) + 'px, ' + x + 'px ' + y + 'px)'
117
127
  sel.style.display = 'block'
118
- sel.style.left = x + 'px'; sel.style.top = y + 'px'
119
- sel.style.width = w + 'px'; sel.style.height = h + 'px'
128
+ sel.style.transform = 'translate(' + x + 'px, ' + y + 'px)'
129
+ sel.style.width = w + 'px'
130
+ sel.style.height = h + 'px'
120
131
  sz.textContent = Math.round(w) + ' × ' + Math.round(h)
121
- // 遮罩(4 块)
122
- masks.top.style.cssText = 'left:0;top:0;width:' + W + 'px;height:' + y + 'px;display:block'
123
- masks.bottom.style.cssText = 'left:0;top:' + (y + h) + 'px;width:' + W + 'px;height:' + (H - y - h) + 'px;display:block'
124
- masks.left.style.cssText = 'left:0;top:' + y + 'px;width:' + x + 'px;height:' + h + 'px;display:block'
125
- masks.right.style.cssText = 'left:' + (x + w) + 'px;top:' + y + 'px;width:' + (W - x - w) + 'px;height:' + h + 'px;display:block'
126
- // 手柄
127
- if (mode === 'adjust') {
128
- var half = 4.5
129
- handles.n.style.cssText = 'left:' + (x + w / 2 - half) + 'px;top:' + (y - half) + 'px;display:block'
130
- handles.s.style.cssText = 'left:' + (x + w / 2 - half) + 'px;top:' + (y + h - half) + 'px;display:block'
131
- handles.e.style.cssText = 'left:' + (x + w - half) + 'px;top:' + (y + h / 2 - half) + 'px;display:block'
132
- handles.w.style.cssText = 'left:' + (x - half) + 'px;top:' + (y + h / 2 - half) + 'px;display:block'
133
- handles.ne.style.cssText = 'left:' + (x + w - half) + 'px;top:' + (y - half) + 'px;display:block'
134
- handles.nw.style.cssText = 'left:' + (x - half) + 'px;top:' + (y - half) + 'px;display:block'
135
- handles.se.style.cssText = 'left:' + (x + w - half) + 'px;top:' + (y + h - half) + 'px;display:block'
136
- handles.sw.style.cssText = 'left:' + (x - half) + 'px;top:' + (y + h - half) + 'px;display:block'
137
- } else {
138
- Object.keys(handles).forEach(function (k) { handles[k].style.display = 'none' })
132
+ updateHandles()
133
+ }
134
+
135
+ function updateHandles() {
136
+ if (mode !== 'adjust' || !rect) { hideHandles(); return }
137
+ var x = rect.x, y = rect.y, w = rect.w, h = rect.h
138
+ var pos = {
139
+ n: [x + w / 2, y], s: [x + w / 2, y + h],
140
+ e: [x + w, y + h / 2], w: [x, y + h / 2],
141
+ ne: [x + w, y], nw: [x, y], se: [x + w, y + h], sw: [x, y + h],
139
142
  }
143
+ for (var k in pos) {
144
+ handles[k].style.display = 'block'
145
+ handles[k].style.left = pos[k][0] + 'px'
146
+ handles[k].style.top = pos[k][1] + 'px'
147
+ }
148
+ }
149
+
150
+ function hideHandles() {
151
+ for (var k in handles) handles[k].style.display = 'none'
152
+ }
153
+
154
+ function scheduleUpdate() {
155
+ if (rafPending) return
156
+ rafPending = true
157
+ requestAnimationFrame(function () {
158
+ rafPending = false
159
+ update()
160
+ })
140
161
  }
141
162
 
142
163
  function inside(e, r) {
@@ -147,25 +168,21 @@
147
168
  if (e.button !== 0) return
148
169
  if (mode === 'draw') {
149
170
  drag = { type: 'draw', x0: e.clientX, y0: e.clientY }
171
+ return
172
+ }
173
+ var hKey = null
174
+ for (var k in handles) if (e.target === handles[k]) { hKey = k; break }
175
+ if (hKey) {
176
+ drag = { type: 'resize', dir: hKey, x0: e.clientX, y0: e.clientY, orig: { x: rect.x, y: rect.y, w: rect.w, h: rect.h } }
177
+ } else if (inside(e, rect)) {
178
+ drag = { type: 'move', x0: e.clientX, y0: e.clientY, orig: { x: rect.x, y: rect.y } }
150
179
  } else {
151
- // 调整模式:手柄 / 拖动移动 / 外部重画
152
- var hKey = null
153
- for (var k in handles) if (e.target === handles[k]) { hKey = k; break }
154
- if (hKey) {
155
- drag = { type: 'resize', dir: hKey, x0: e.clientX, y0: e.clientY, orig: { x: rect.x, y: rect.y, w: rect.w, h: rect.h } }
156
- } else if (inside(e, rect)) {
157
- drag = { type: 'move', x0: e.clientX, y0: e.clientY, orig: { x: rect.x, y: rect.y } }
158
- } else {
159
- // 点击选区外 → 重新画
160
- mode = 'draw'
161
- rect = null
162
- sel.style.display = 'none'
163
- toolbar.style.display = 'none'
164
- Object.keys(handles).forEach(function (k) { handles[k].style.display = 'none' })
165
- masks.top.style.display = masks.bottom.style.display = masks.left.style.display = masks.right.style.display = 'none'
166
- hint.style.opacity = '0.9'
167
- drag = { type: 'draw', x0: e.clientX, y0: e.clientY }
168
- }
180
+ mode = 'draw'
181
+ rect = null
182
+ toolbar.style.display = 'none'
183
+ hint.style.opacity = '0.9'
184
+ drag = { type: 'draw', x0: e.clientX, y0: e.clientY }
185
+ scheduleUpdate()
169
186
  }
170
187
  })
171
188
 
@@ -175,10 +192,8 @@
175
192
  var dy = e.clientY - drag.y0
176
193
  if (drag.type === 'draw') {
177
194
  rect = clampRect({ x: Math.min(drag.x0, e.clientX), y: Math.min(drag.y0, e.clientY), w: Math.abs(e.clientX - drag.x0), h: Math.abs(e.clientY - drag.y0) })
178
- layout()
179
195
  } else if (drag.type === 'move') {
180
196
  rect = clampRect({ x: drag.orig.x + dx, y: drag.orig.y + dy, w: rect.w, h: rect.h })
181
- layout()
182
197
  } else if (drag.type === 'resize') {
183
198
  var o = drag.orig
184
199
  var r = { x: o.x, y: o.y, w: o.w, h: o.h }
@@ -187,8 +202,8 @@
187
202
  if (drag.dir.indexOf('w') !== -1) { r.x = o.x + dx; r.w = o.w - dx }
188
203
  if (drag.dir.indexOf('n') !== -1) { r.y = o.y + dy; r.h = o.h - dy }
189
204
  rect = clampRect(r)
190
- layout()
191
205
  }
206
+ scheduleUpdate()
192
207
  })
193
208
 
194
209
  document.addEventListener('mouseup', function () {
@@ -198,23 +213,23 @@
198
213
  mode = 'adjust'
199
214
  var t = document.getElementById('toolbar')
200
215
  t.style.display = 'flex'
201
- t.style.left = Math.min(rect.x + rect.w + 10, W - 230) + 'px'
202
- t.style.top = (rect.y + rect.h + 14 > H - 60 ? rect.y - 58 : rect.y + rect.h + 14) + 'px'
203
- tip.textContent = '可拖拽边框微调 · 点确定或双击完成'
216
+ t.style.left = Math.min(rect.x + rect.w + 10, W - 250) + 'px'
217
+ t.style.top = (rect.y + rect.h + 14 > H - 60 ? rect.y - 60 : rect.y + rect.h + 14) + 'px'
204
218
  hint.style.opacity = '0'
205
- layout() // 显示手柄
219
+ scheduleUpdate()
206
220
  }
207
221
  })
208
222
 
209
- // 双击选区 直接提交
210
- document.addEventListener('dblclick', function (e) {
211
- if (rect && inside(e, rect)) window.__screenshot.done(rect)
212
- })
223
+ // 关键:点击工具栏(确定/取消)不触发 document 的画框逻辑
224
+ toolbar.addEventListener('mousedown', function (e) { e.stopPropagation() })
213
225
 
214
226
  document.getElementById('okBtn').onclick = function () {
215
227
  if (rect) window.__screenshot.done(rect)
216
228
  }
217
229
  document.getElementById('cancelBtn').onclick = function () { window.__screenshot.cancel() }
230
+ document.addEventListener('dblclick', function (e) {
231
+ if (rect && inside(e, rect)) window.__screenshot.done(rect)
232
+ })
218
233
  document.addEventListener('keydown', function (e) {
219
234
  if (e.key === 'Escape') window.__screenshot.cancel()
220
235
  if (e.key === 'Enter' && rect) window.__screenshot.done(rect)