dsh-desktop-windows 0.2.4 → 0.2.5

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 +79 -85
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.5",
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,21 +8,12 @@
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; }
12
- #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;
16
- }
17
- #sel .sz {
18
- position: absolute; left: 0; top: -22px;
19
- background: #2563eb; color: #fff;
20
- font: 11px/18px sans-serif; padding: 0 6px; border-radius: 4px; white-space: nowrap;
21
- }
11
+ #cv { position: fixed; inset: 0; pointer-events: none; z-index: 2; }
22
12
  .handle {
23
13
  position: fixed; width: 9px; height: 9px; background: #2563eb;
24
14
  border: 1px solid #fff; border-radius: 2px; display: none;
25
15
  pointer-events: auto; z-index: 4;
16
+ transform: translate(-50%, -50%);
26
17
  }
27
18
  #toolbar {
28
19
  position: fixed; display: none; align-items: center; gap: 8px;
@@ -32,8 +23,8 @@
32
23
  }
33
24
  #toolbar .tip { color: #94a3b8; font: 12px/1 sans-serif; margin-right: 4px; }
34
25
  #toolbar button {
35
- border: none; border-radius: 6px; padding: 6px 14px; cursor: pointer;
36
- font: 600 12px/1 sans-serif;
26
+ border: none; border-radius: 6px; padding: 7px 16px; cursor: pointer;
27
+ font: 600 13px/1 sans-serif;
37
28
  }
38
29
  #okBtn { background: #2563eb; color: #fff; }
39
30
  #okBtn:hover { background: #1d4ed8; }
@@ -49,11 +40,7 @@
49
40
  </head>
50
41
  <body>
51
42
  <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>
56
- <div id="sel"><div class="sz"></div></div>
43
+ <canvas id="cv"></canvas>
57
44
  <div id="hN" class="handle" style="cursor:n-resize"></div>
58
45
  <div id="hS" class="handle" style="cursor:s-resize"></div>
59
46
  <div id="hE" class="handle" style="cursor:e-resize"></div>
@@ -62,26 +49,20 @@
62
49
  <div id="hNW" class="handle" style="cursor:nw-resize"></div>
63
50
  <div id="hSE" class="handle" style="cursor:se-resize"></div>
64
51
  <div id="hSW" class="handle" style="cursor:sw-resize"></div>
65
- <div id="toolbar">
66
- <span class="tip" id="tip">拖动边框微调选区</span>
52
+ <div id="toolbar" style="cursor:default">
53
+ <span class="tip" id="tip">可拖拽边框微调 · 点确定或双击完成</span>
67
54
  <button id="okBtn">✔ 确定</button>
68
55
  <button id="cancelBtn">✕ 取消 (Esc)</button>
69
56
  </div>
70
- <div id="hint">拖动鼠标框选要截取的区域 · 画完后可拖拽边框微调 · Esc 取消</div>
57
+ <div id="hint">拖动鼠标框选要截取的区域 · 画完后可拖拽边框微调 · 双击完成 · Esc 取消</div>
71
58
 
72
59
  <script>
73
60
  var bg = document.getElementById('bg')
74
- var sel = document.getElementById('sel')
75
61
  var toolbar = document.getElementById('toolbar')
76
62
  var hint = document.getElementById('hint')
77
63
  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
- }
64
+ var cv = document.getElementById('cv')
65
+ var cx = cv.getContext('2d')
85
66
  var handles = {
86
67
  n: document.getElementById('hN'), s: document.getElementById('hS'),
87
68
  e: document.getElementById('hE'), w: document.getElementById('hW'),
@@ -89,14 +70,17 @@
89
70
  se: document.getElementById('hSE'), sw: document.getElementById('hSW'),
90
71
  }
91
72
  var W = window.innerWidth, H = window.innerHeight
73
+ cv.width = W; cv.height = H
92
74
  var MIN = 8
93
75
  var rect = null
94
76
  var mode = 'draw' // draw | adjust
95
77
  var drag = null
78
+ var rafPending = false
96
79
 
97
80
  window.__screenshot.onData(function (dataUrl) {
98
81
  bg.onload = function () {
99
82
  window.__screenshot.loaded()
83
+ draw()
100
84
  setTimeout(function () { hint.style.opacity = '0.9' }, 100)
101
85
  }
102
86
  bg.src = dataUrl
@@ -105,38 +89,54 @@
105
89
  function clampRect(r) {
106
90
  var x = Math.max(0, Math.min(W - MIN, r.x))
107
91
  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)
92
+ var w = Math.max(MIN, Math.min(r.w, W - x))
93
+ var h = Math.max(MIN, Math.min(r.h, H - y))
111
94
  return { x: x, y: y, w: w, h: h }
112
95
  }
113
96
 
114
- function layout() {
115
- if (!rect) return
97
+ // Canvas 一次性绘制遮罩 + 选区 + 尺寸(高性能)
98
+ function draw() {
99
+ cx.clearRect(0, 0, W, H)
100
+ cx.fillStyle = 'rgba(0,0,0,0.45)'
101
+ if (!rect) { cx.fillRect(0, 0, W, H); return }
102
+ cx.fillRect(0, 0, W, rect.y)
103
+ cx.fillRect(0, rect.y + rect.h, W, H - rect.y - rect.h)
104
+ cx.fillRect(0, rect.y, rect.x, rect.h)
105
+ cx.fillRect(rect.x + rect.w, rect.y, W - rect.x - rect.w, rect.h)
106
+ cx.strokeStyle = '#2563eb'
107
+ cx.lineWidth = 1.5
108
+ cx.strokeRect(rect.x + 0.75, rect.y + 0.75, rect.w, rect.h)
109
+ cx.fillStyle = '#2563eb'
110
+ cx.font = '12px sans-serif'
111
+ cx.fillText(Math.round(rect.w) + ' × ' + Math.round(rect.h), rect.x + 4, Math.max(14, rect.y - 6))
112
+ }
113
+
114
+ function updateHandles() {
115
+ if (mode !== 'adjust' || !rect) {
116
+ for (var k in handles) handles[k].style.display = 'none'
117
+ return
118
+ }
116
119
  var x = rect.x, y = rect.y, w = rect.w, h = rect.h
117
- 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'
120
- 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' })
120
+ var pos = {
121
+ n: [x + w / 2, y], s: [x + w / 2, y + h],
122
+ e: [x + w, y + h / 2], w: [x, y + h / 2],
123
+ ne: [x + w, y], nw: [x, y], se: [x + w, y + h], sw: [x, y + h],
139
124
  }
125
+ for (var k in pos) {
126
+ handles[k].style.display = 'block'
127
+ handles[k].style.left = pos[k][0] + 'px'
128
+ handles[k].style.top = pos[k][1] + 'px'
129
+ }
130
+ }
131
+
132
+ function scheduleDraw() {
133
+ if (rafPending) return
134
+ rafPending = true
135
+ requestAnimationFrame(function () {
136
+ rafPending = false
137
+ draw()
138
+ updateHandles()
139
+ })
140
140
  }
141
141
 
142
142
  function inside(e, r) {
@@ -147,25 +147,21 @@
147
147
  if (e.button !== 0) return
148
148
  if (mode === 'draw') {
149
149
  drag = { type: 'draw', x0: e.clientX, y0: e.clientY }
150
+ return
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 } }
150
158
  } 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
- }
159
+ mode = 'draw'
160
+ rect = null
161
+ toolbar.style.display = 'none'
162
+ hint.style.opacity = '0.9'
163
+ drag = { type: 'draw', x0: e.clientX, y0: e.clientY }
164
+ scheduleDraw()
169
165
  }
170
166
  })
171
167
 
@@ -175,10 +171,8 @@
175
171
  var dy = e.clientY - drag.y0
176
172
  if (drag.type === 'draw') {
177
173
  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
174
  } else if (drag.type === 'move') {
180
175
  rect = clampRect({ x: drag.orig.x + dx, y: drag.orig.y + dy, w: rect.w, h: rect.h })
181
- layout()
182
176
  } else if (drag.type === 'resize') {
183
177
  var o = drag.orig
184
178
  var r = { x: o.x, y: o.y, w: o.w, h: o.h }
@@ -187,8 +181,8 @@
187
181
  if (drag.dir.indexOf('w') !== -1) { r.x = o.x + dx; r.w = o.w - dx }
188
182
  if (drag.dir.indexOf('n') !== -1) { r.y = o.y + dy; r.h = o.h - dy }
189
183
  rect = clampRect(r)
190
- layout()
191
184
  }
185
+ scheduleDraw()
192
186
  })
193
187
 
194
188
  document.addEventListener('mouseup', function () {
@@ -198,23 +192,23 @@
198
192
  mode = 'adjust'
199
193
  var t = document.getElementById('toolbar')
200
194
  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 = '可拖拽边框微调 · 点确定或双击完成'
195
+ t.style.left = Math.min(rect.x + rect.w + 10, W - 250) + 'px'
196
+ t.style.top = (rect.y + rect.h + 14 > H - 60 ? rect.y - 60 : rect.y + rect.h + 14) + 'px'
204
197
  hint.style.opacity = '0'
205
- layout() // 显示手柄
198
+ scheduleDraw() // 显示手柄
206
199
  }
207
200
  })
208
201
 
209
- // 双击选区 直接提交
210
- document.addEventListener('dblclick', function (e) {
211
- if (rect && inside(e, rect)) window.__screenshot.done(rect)
212
- })
202
+ // 关键修复:点击工具栏(确定/取消)不触发 document 的画框逻辑
203
+ toolbar.addEventListener('mousedown', function (e) { e.stopPropagation() })
213
204
 
214
205
  document.getElementById('okBtn').onclick = function () {
215
206
  if (rect) window.__screenshot.done(rect)
216
207
  }
217
208
  document.getElementById('cancelBtn').onclick = function () { window.__screenshot.cancel() }
209
+ document.addEventListener('dblclick', function (e) {
210
+ if (rect && inside(e, rect)) window.__screenshot.done(rect)
211
+ })
218
212
  document.addEventListener('keydown', function (e) {
219
213
  if (e.key === 'Escape') window.__screenshot.cancel()
220
214
  if (e.key === 'Enter' && rect) window.__screenshot.done(rect)