From e4ab7c61412eb050616c4bc2b69ad2571c1e5141 Mon Sep 17 00:00:00 2001 From: AliceJump <149395013+AliceJump@users.noreply.github.com> Date: Fri, 25 Sep 2026 18:59:52 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E5=A4=8D=E5=88=B6=E5=9D=90?= =?UTF-8?q?=E6=A0=87=E5=88=86=E9=9A=94=E5=81=8F=E5=A5=BD=EF=BC=88=E9=80=97?= =?UTF-8?q?=E5=8F=B7=E5=90=8E=E7=A9=BA=E6=A0=BC=EF=BC=89=E2=80=94=E2=80=94?= =?UTF-8?q?=20application=20=E4=BD=9C=E7=94=A8=E5=9F=9F=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E4=B9=A0=E6=83=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 框选复制归一化坐标(annotationPanel / tempScreenshots)的输出格式 从 `x,y,tox,toy` 升级为可选 `x, y, tox, toy`: - 新增 `okScriptToolkit.copyCoordsSpace`(boolean,默认开, scope=application):个人习惯只放全局设置,不能按工作区覆盖; - 两个面板控制器经 config 消息把偏好下发给 webview (attach + refresh 双时机,改设置后切一下视图即生效); - webview formatCoords 按 `copyCoordsSpace` 切换 join 分隔符; - package.nls 六语言描述同步。 --- media/annotationPanel/app.js | 11 +++++++++-- media/tempScreenshots/app.js | 12 +++++++++++- package.json | 6 ++++++ package.nls.es.json | 1 + package.nls.ja.json | 1 + package.nls.json | 1 + package.nls.ko.json | 1 + package.nls.zh-cn.json | 1 + package.nls.zh-tw.json | 1 + src/annotationPanel.ts | 14 ++++++++------ src/tempScreenshotPanel.ts | 14 ++++++++++++++ 11 files changed, 54 insertions(+), 9 deletions(-) diff --git a/media/annotationPanel/app.js b/media/annotationPanel/app.js index e3e3970..bb55803 100644 --- a/media/annotationPanel/app.js +++ b/media/annotationPanel/app.js @@ -72,6 +72,9 @@ nextImage: 'ArrowRight' }; + // 复制坐标时逗号后加空格(个人偏好,application 作用域;由扩展端经 config 消息下发) + let copyCoordsSpace = true; + function parseKeybinding(kb) { const parts = kb.toLowerCase().split('+'); const key = parts.pop(); @@ -643,7 +646,8 @@ } function formatNormalizedBox(box) { - return [box.x, box.y, box.tox, box.toy].map((v) => v.toFixed(COORD_DECIMALS)).join(','); + return [box.x, box.y, box.tox, box.toy].map((v) => v.toFixed(COORD_DECIMALS)) + .join(copyCoordsSpace ? ', ' : ','); } /** 拖拽过程中在颜色栏实时显示即将复制的坐标。 */ @@ -1011,10 +1015,13 @@ window.addEventListener('message', (e) => { const msg = e.data; if (msg.type === 'config') { - // 接收快捷键配置 + // 接收扩展端配置:快捷键 + 坐标分隔偏好 if (msg.keybindings) { Object.assign(keybindings, msg.keybindings); } + if (typeof msg.copyCoordsSpace === 'boolean') { + copyCoordsSpace = msg.copyCoordsSpace; + } updateButtonTexts(); return; } diff --git a/media/tempScreenshots/app.js b/media/tempScreenshots/app.js index 636f0a1..9f833fa 100644 --- a/media/tempScreenshots/app.js +++ b/media/tempScreenshots/app.js @@ -58,6 +58,8 @@ /* ---------- 常量 ---------- */ const CAROUSEL_INTERVAL_MS = 100; // 需求:0.1s 轮播 const COORD_DECIMALS = 4; // 归一化坐标小数位 + // 复制坐标时逗号后加空格(个人偏好,application 作用域;由扩展端经 config 消息下发) + let copyCoordsSpace = true; const MIN_BOX_PX = 3; const MAX_ZOOM = 20; @@ -101,7 +103,8 @@ } function formatCoords(x1, y1, x2, y2) { - return [x1, y1, x2, y2].map((v) => v.toFixed(COORD_DECIMALS)).join(','); + return [x1, y1, x2, y2].map((v) => v.toFixed(COORD_DECIMALS)) + .join(copyCoordsSpace ? ', ' : ','); } function activeFrame() { @@ -719,6 +722,13 @@ } return; } + if (msg.type === 'config') { + // 扩展端配置:复制坐标分隔偏好(个人偏好,application 作用域) + if (typeof msg.copyCoordsSpace === 'boolean') { + copyCoordsSpace = msg.copyCoordsSpace; + } + return; + } if (msg.type === 'notice') { toast(msg.text, msg.level === 'error'); } diff --git a/package.json b/package.json index a3e2b68..a05ad21 100644 --- a/package.json +++ b/package.json @@ -293,6 +293,12 @@ "default": "ok_templates", "description": "%config.okTemplatesDirectory%" }, + "okScriptToolkit.copyCoordsSpace": { + "type": "boolean", + "default": true, + "scope": "application", + "description": "%config.copyCoordsSpace%" + }, "okScriptToolkit.annotationKeybindings": { "type": "object", "default": { diff --git a/package.nls.es.json b/package.nls.es.json index b030229..bda8794 100644 --- a/package.nls.es.json +++ b/package.nls.es.json @@ -37,6 +37,7 @@ "config.okScriptProjectPath": "Raíz del proyecto ok-script usada por el lanzador de tareas.", "config.okScriptPython": "Intérprete de Python usado para ejecutar tareas ok-script.", "config.captureMethod": "Método de captura de la ventana del juego. auto prueba Windows Graphics Capture y recurre a BitBlt si falla. Usa foreground si la pantalla de inicio o de acceso sale en blanco: activa la ventana y lee la pantalla, lo que roba el foco.", + "config.copyCoordsSpace": "Añade un espacio después de cada coma al copiar coordenadas (x, y, tox, toy). Es una preferencia personal: solo ámbito global (application), no se puede configurar por espacio de trabajo.", "config.characterProjectPath": "Raíz del proyecto usada por el gestor de personajes y habilidades.", "config.characterMasterFile": "JSON maestro de personajes.", "config.characterSkillsDirectory": "Directorio JSON de habilidades de personajes.", diff --git a/package.nls.ja.json b/package.nls.ja.json index 25dcb36..a905440 100644 --- a/package.nls.ja.json +++ b/package.nls.ja.json @@ -37,6 +37,7 @@ "config.okScriptProjectPath": "タスク起動で使用する ok-script プロジェクトルート。", "config.okScriptPython": "ok-script タスク実行に使用する Python。", "config.captureMethod": "ゲームウィンドウのキャプチャ方式。auto は Windows Graphics Capture を試し、失敗したら BitBlt に戻します。ランチャーやログイン画面が空白になる場合は foreground を使ってください。ウィンドウを前面に出して画面を読み取るため、フォーカスが奪われます。", + "config.copyCoordsSpace": "座標をコピーするときにカンマの後にスペースを入れます(x, y, tox, toy)。個人設定のため、グローバル(application スコープ)のみで、ワークスペース単位では設定できません。", "config.characterProjectPath": "キャラクター・スキル管理で使用するプロジェクトルート。", "config.characterMasterFile": "キャラクターマスター JSON。", "config.characterSkillsDirectory": "キャラクタースキル JSON ディレクトリ。", diff --git a/package.nls.json b/package.nls.json index 827c745..fcbf0d8 100644 --- a/package.nls.json +++ b/package.nls.json @@ -37,6 +37,7 @@ "config.okScriptProjectPath": "ok-script project root containing src/config.py. Used by the task launcher.", "config.okScriptPython": "Python interpreter used to run ok-script tasks. Empty uses the target project's .venv on Windows.", "config.captureMethod": "Game window capture method. auto tries Windows Graphics Capture and falls back to BitBlt. Use foreground for launcher or login screens that capture blank otherwise: it activates the window and reads the screen, which steals focus.", + "config.copyCoordsSpace": "Add a space after each comma when copying box coordinates (x, y, tox, toy). A personal habit — application scope only, cannot be set per workspace.", "config.characterProjectPath": "Project root used by the character and skill manager. Empty falls back to okScriptProjectPath and the current workspace.", "config.characterMasterFile": "Character master JSON relative to the character project root.", "config.characterSkillsDirectory": "Character skill JSON directory relative to the character project root.", diff --git a/package.nls.ko.json b/package.nls.ko.json index aabff69..55f72c3 100644 --- a/package.nls.ko.json +++ b/package.nls.ko.json @@ -37,6 +37,7 @@ "config.okScriptProjectPath": "작업 실행기에서 사용할 ok-script 프로젝트 루트입니다.", "config.okScriptPython": "ok-script 작업 실행에 사용할 Python 인터프리터입니다.", "config.captureMethod": "게임 창 캡처 방식입니다. auto는 Windows Graphics Capture를 시도하고 실패하면 BitBlt로 대체합니다. 런처/로그인 화면이 비어 있게 찍힌다면 foreground를 사용하세요. 창을 전면으로 활성화한 뒤 화면을 읽으므로 포커스를 가져갑니다.", + "config.copyCoordsSpace": "좌표를 복사할 때 쉼표 뒤에 공백을 추가합니다(x, y, tox, toy). 개인 취향에 해당하며 전역(application 범위) 설정으로만 가능하고 작업 영역별로는 설정할 수 없습니다.", "config.characterProjectPath": "캐릭터·스킬 관리자가 사용할 프로젝트 루트입니다.", "config.characterMasterFile": "캐릭터 마스터 JSON입니다.", "config.characterSkillsDirectory": "캐릭터 스킬 JSON 디렉터리입니다.", diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 6abff75..e9c2e22 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -37,6 +37,7 @@ "config.okScriptProjectPath": "包含 src/config.py 的 ok-script 项目根目录,供任务启动器使用。", "config.okScriptPython": "运行 ok-script 任务的 Python 解释器;留空时 Windows 优先使用目标项目 .venv。", "config.captureMethod": "游戏窗口截图方式。auto 先尝试 Windows Graphics Capture,失败回退 BitBlt。若启动器/登录界面截出来是空白,改用 foreground:它会把窗口激活到前台再读取屏幕(会抢焦点)。", + "config.copyCoordsSpace": "复制框选坐标时在逗号后加空格(x, y, tox, toy)。属个人习惯,仅全局设置(application 作用域),不能按工作区覆盖。", "config.characterProjectPath": "角色技能管理器使用的项目根目录;留空时回退到 okScriptProjectPath 和当前工作区。", "config.characterMasterFile": "角色主表 JSON(相对于角色项目根目录)。", "config.characterSkillsDirectory": "角色技能 JSON 目录(相对于角色项目根目录)。", diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json index 249e4e1..84eb04f 100644 --- a/package.nls.zh-tw.json +++ b/package.nls.zh-tw.json @@ -37,6 +37,7 @@ "config.okScriptProjectPath": "包含 src/config.py 的 ok-script 專案根目錄,供任務啟動器使用。", "config.okScriptPython": "執行 ok-script 任務的 Python 直譯器;留空時 Windows 優先使用目標專案 .venv。", "config.captureMethod": "遊戲視窗截圖方式。auto 先嘗試 Windows Graphics Capture,失敗時退回 BitBlt。若啟動器/登入介面截出來是空白,改用 foreground:它會把視窗啟用到前景再讀取畫面(會搶走焦點)。", + "config.copyCoordsSpace": "複製框選座標時在逗號後加空格(x, y, tox, toy)。屬個人習慣,僅全域設定(application 作用域),不能按工作區覆蓋。", "config.characterProjectPath": "角色技能管理器使用的專案根目錄;留空時回退到 okScriptProjectPath 和目前工作區。", "config.characterMasterFile": "角色主表 JSON(相對於角色專案根目錄)。", "config.characterSkillsDirectory": "角色技能 JSON 目錄(相對於角色專案根目錄)。", diff --git a/src/annotationPanel.ts b/src/annotationPanel.ts index 88f11cb..c6732d1 100644 --- a/src/annotationPanel.ts +++ b/src/annotationPanel.ts @@ -42,16 +42,16 @@ class AnnotationController { attachHtml(): void { this.webview.html = annotationHtml(this.webview.cspSource, this.extensionUri, this.webview); - this.sendKeybindings(); + this.sendConfig(); } - /** 读取扩展设置并发送快捷键配置到 webview */ - private sendKeybindings(): void { + /** 读取扩展设置并发送面板配置到 webview(快捷键 + 坐标分隔偏好) */ + private sendConfig(): void { const cfg = vscode.workspace.getConfiguration('okScriptToolkit'); const kb = cfg.get>('annotationKeybindings'); - if (kb) { - void this.webview.postMessage({ type: 'config', keybindings: kb }); - } + // 坐标逗号后加空格是个人习惯 → application 作用域,只在用户设置里存在 + const copyCoordsSpace = cfg.get('copyCoordsSpace', true); + void this.webview.postMessage({ type: 'config', keybindings: kb, copyCoordsSpace }); } open(imagePath: string, imageList: string[]): void { @@ -128,6 +128,8 @@ class AnnotationController { }): Promise { switch (msg.type) { case 'ready': + // ready 重发一次配置:attachHtml 时机太早、webview 脚本可能还没挂监听 + this.sendConfig(); if (this._currentImage) { await this.loadImage(this._currentImage); } diff --git a/src/tempScreenshotPanel.ts b/src/tempScreenshotPanel.ts index e0b66e0..a22cc09 100644 --- a/src/tempScreenshotPanel.ts +++ b/src/tempScreenshotPanel.ts @@ -85,6 +85,19 @@ class TempScreenshotController { attachHtml(): void { this.webview.html = tempScreenshotHtml(this.webview, this.extensionUri); + this.pushCoordFormat(); + } + + /** + * 把「复制坐标逗号后加空格」偏好推给 webview。 + * application 作用域(个人习惯,不随工作区),读一次默认 true。 + * attach 与每次 refresh 都推:refresh 覆盖 ready / 视图可见 / 列表变化, + * 用户改设置后切一下视图就能生效,不用重开面板。 + */ + private pushCoordFormat(): void { + const copyCoordsSpace = vscode.workspace.getConfiguration('okScriptToolkit') + .get('copyCoordsSpace', true); + void this.webview.postMessage({ type: 'config', copyCoordsSpace }); } /* ---------- 推送列表 ---------- */ @@ -92,6 +105,7 @@ class TempScreenshotController { async refresh(): Promise { if (this.disposed) return; if (!this.isVisible()) return; + this.pushCoordFormat(); const items = this.store.list(); const metas: TempMeta[] = []; From 26b6cda836f2b070d8cd463002e58089a691942c Mon Sep 17 00:00:00 2001 From: AliceJump <149395013+AliceJump@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:23:13 +0800 Subject: [PATCH 2/4] chore: bump jetbrains submodule to a7fa3d7 (PR #6 merged: coords space separator) --- jetbrains | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetbrains b/jetbrains index cf4c7bc..a7fa3d7 160000 --- a/jetbrains +++ b/jetbrains @@ -1 +1 @@ -Subproject commit cf4c7bc7156800886c7d4f6568c05ffca57a5640 +Subproject commit a7fa3d7061fe51bd12c50f81da2cbf13f782efdc From a18e03dfef15210340133201ea25bb33161f069f Mon Sep 17 00:00:00 2001 From: AliceJump <149395013+AliceJump@users.noreply.github.com> Date: Fri, 25 Sep 2026 22:10:10 +0800 Subject: [PATCH 3/4] fix: hot-reload copyCoordsSpace in open panels Subscribe to onDidChangeConfiguration in both controllers and push the updated config to the webview instead of relying on view switches (CodeRabbit Minor). Subscriptions go through each controller disposables. --- src/annotationPanel.ts | 7 +++++++ src/tempScreenshotPanel.ts | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/annotationPanel.ts b/src/annotationPanel.ts index c6732d1..6ad9e2b 100644 --- a/src/annotationPanel.ts +++ b/src/annotationPanel.ts @@ -35,6 +35,13 @@ class AnnotationController { ) { this.disposables.push( webview.onDidReceiveMessage((msg) => { void this.onMessage(msg); }), + // 面板开着时改设置也要热更新(review 意见:不要靠「切一下视图」触发) + vscode.workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('okScriptToolkit.copyCoordsSpace') || + e.affectsConfiguration('okScriptToolkit.annotationKeybindings')) { + this.sendConfig(); + } + }), ); } diff --git a/src/tempScreenshotPanel.ts b/src/tempScreenshotPanel.ts index a22cc09..18f5d17 100644 --- a/src/tempScreenshotPanel.ts +++ b/src/tempScreenshotPanel.ts @@ -80,6 +80,12 @@ class TempScreenshotController { this.disposables.push( webview.onDidReceiveMessage((msg) => { void this.onMessage(msg); }), store.onChange(() => { void this.refresh(); }), + // 面板开着时改设置也要热更新(review 意见:不要靠「切一下视图」触发) + vscode.workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('okScriptToolkit.copyCoordsSpace')) { + this.pushCoordFormat(); + } + }), ); } From 2f5270df9275e20e746c9c7c11591c4da4332bc2 Mon Sep 17 00:00:00 2001 From: AliceJump <149395013+AliceJump@users.noreply.github.com> Date: Fri, 25 Sep 2026 22:25:43 +0800 Subject: [PATCH 4/4] test: update coord format expectations to copyCoordsSpace default Default is now "x, y, tox, toy" (space after comma); add toggle coverage via config messages in test_temp_screenshots. Fixes the failing vscode CI job. --- scripts/test_annotation_coords.js | 12 ++++---- scripts/test_temp_screenshots.js | 46 +++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/scripts/test_annotation_coords.js b/scripts/test_annotation_coords.js index 8ba55ef..3c5b1bb 100644 --- a/scripts/test_annotation_coords.js +++ b/scripts/test_annotation_coords.js @@ -160,7 +160,7 @@ function mouse(type, target, x, y, button = 0) { const copy = lastPost('copyText'); assert(copy, 'box-select in coord mode must post a copyText message'); - assert(copy.text === '0.1250,0.1000,0.6250,0.6000', + assert(copy.text === '0.1250, 0.1000, 0.6250, 0.6000', 'expected normalized x,y,tox,toy, got ' + copy.text); // 框留在画布上供继续调整,所以坐标模式不会自动退出 assert(coordBtn.classList.contains('active'), @@ -174,7 +174,7 @@ function mouse(type, target, x, y, button = 0) { mouse('mouseup', canvas, 350, 260); await flush(); assert(post('copyText').length === before + 1, 'moving the box must copy again'); - assert(lastPost('copyText').text === '0.1875,0.1667,0.6875,0.6667', + assert(lastPost('copyText').text === '0.1875, 0.1667, 0.6875, 0.6667', 'moved box must copy the updated coords, got ' + lastPost('copyText').text); /* ---------- 拖动手柄:缩放并重新复制 ---------- */ @@ -186,7 +186,7 @@ function mouse(type, target, x, y, button = 0) { await flush(); assert(post('copyText').length === before + 1, 'resizing the box must copy again'); // dx=60,dy=30 → 图像 +144,+72 → 宽 1104 高 612 - assert(lastPost('copyText').text === '0.1875,0.1667,0.7625,0.7333', + assert(lastPost('copyText').text === '0.1875, 0.1667, 0.7625, 0.7333', 'resized box must copy the updated coords, got ' + lastPost('copyText').text); /* ---------- 点击非交互部分清除坐标框 ---------- */ @@ -203,7 +203,7 @@ function mouse(type, target, x, y, button = 0) { mouse('mousemove', canvas, 500, 345); mouse('mouseup', canvas, 500, 345); await flush(); - assert(lastPost('copyText').text === '0.1250,0.1000,0.6250,0.6000', + assert(lastPost('copyText').text === '0.1250, 0.1000, 0.6250, 0.6000', 'a fresh box can be created after clearing, got ' + lastPost('copyText').text); /* ---------- 坐标框不进入标注数据(不落盘) ---------- */ @@ -219,7 +219,7 @@ function mouse(type, target, x, y, button = 0) { mouse('mousemove', canvas, 100, 120); mouse('mouseup', canvas, 100, 120); await flush(); - assert(lastPost('copyText').text === '0.1250,0.1000,0.6250,0.6000', + assert(lastPost('copyText').text === '0.1250, 0.1000, 0.6250, 0.6000', 'reverse drag must normalize to the same box, got ' + lastPost('copyText').text); /* ---------- 越界框选必须 clamp ---------- */ @@ -227,7 +227,7 @@ function mouse(type, target, x, y, button = 0) { mouse('mousemove', canvas, 5000, 5000); mouse('mouseup', canvas, 5000, 5000); await flush(); - assert(lastPost('copyText').text === '0.0000,0.0000,1.0000,1.0000', + assert(lastPost('copyText').text === '0.0000, 0.0000, 1.0000, 1.0000', 'out-of-range selection must clamp to 0..1, got ' + lastPost('copyText').text); /* ---------- 退出坐标模式应丢弃坐标框 ---------- */ diff --git a/scripts/test_temp_screenshots.js b/scripts/test_temp_screenshots.js index 102fdfa..18218d0 100644 --- a/scripts/test_temp_screenshots.js +++ b/scripts/test_temp_screenshots.js @@ -197,8 +197,8 @@ function mouse(type, target, x, y, button = 0) { const copy = lastPost('copyText'); assert(copy, 'box-select must post a copyText message'); - assert(copy.text === '0.2500,0.2000,0.7500,0.6667', - 'normalized coords must be x,y,tox,toy with 4 decimals, got ' + copy.text); + assert(copy.text === '0.2500, 0.2000, 0.7500, 0.6667', + 'default normalized coords must be "x, y, tox, toy" with 4 decimals (copyCoordsSpace=true), got ' + copy.text); assert(document.getElementById('selBox').classList.contains('visible'), 'the selection box must stay visible after copying so it can be compared against moving frames'); @@ -210,7 +210,7 @@ function mouse(type, target, x, y, button = 0) { mouse('mouseup', window, 250, 165); await flush(); assert(post('copyText').length === copyBefore + 1, 'moving the box must copy again'); - assert(lastPost('copyText').text === '0.3750,0.3333,0.8750,0.8000', + assert(lastPost('copyText').text === '0.3750, 0.3333, 0.8750, 0.8000', 'moved box must copy the updated coords, got ' + lastPost('copyText').text); /* ---------- 3b. 拖动手柄缩放并重新复制 ---------- */ @@ -221,7 +221,7 @@ function mouse(type, target, x, y, button = 0) { mouse('mouseup', window, 390, 247.5); await flush(); assert(post('copyText').length === copyBefore + 1, 'resizing the box must copy again'); - assert(lastPost('copyText').text === '0.3750,0.3333,0.9750,0.9333', + assert(lastPost('copyText').text === '0.3750, 0.3333, 0.9750, 0.9333', 'resized box must copy the updated coords, got ' + lastPost('copyText').text); /* ---------- 3c. 点击非交互部分清除坐标框 ---------- */ @@ -235,12 +235,46 @@ function mouse(type, target, x, y, button = 0) { assert(!document.getElementById('selBox').classList.contains('visible'), 'the box must disappear after clicking empty area'); + /* ---------- 3c-2. config 消息切换「逗号后加空格」偏好 ---------- */ + // 关掉空格 → 复制格式回退为 x,y,tox,toy;再打开 → 恢复默认("x, y, tox, toy") + window.postMessage({ type: 'config', copyCoordsSpace: false }, '*'); + await flush(); + copyBefore = post('copyText').length; + mouse('mousedown', overlay, 100, 50); + mouse('mousemove', window, 200, 150); + mouse('mouseup', window, 200, 150); + await flush(); + assert(post('copyText').length === copyBefore + 1, 'box-select must copy after toggling space off'); + assert(lastPost('copyText').text === '0.2500,0.0556,0.5000,0.5000', + // 几何值带前面步骤遗留的舞台 y 变换,这里只关心分隔符 + 'copyCoordsSpace=false must join coords with bare commas, got ' + lastPost('copyText').text); + // 清掉旧框:否则 (100,50) 落在框的 tl 手柄上,会触发缩放而不是新建框 + mouse('mousedown', overlay, 30, 30); + mouse('mouseup', overlay, 30, 30); + await flush(); + window.postMessage({ type: 'config', copyCoordsSpace: true }, '*'); + await flush(); + copyBefore = post('copyText').length; + mouse('mousedown', overlay, 100, 50); + mouse('mousemove', window, 200, 150); + mouse('mouseup', window, 200, 150); + await flush(); + assert(post('copyText').length === copyBefore + 1, 'box-select must copy after toggling space on'); + assert(lastPost('copyText').text === '0.2500, 0.0556, 0.5000, 0.5000', + 'copyCoordsSpace=true must join coords with ", ", got ' + lastPost('copyText').text); + // 清除,避免影响后面的越界 clamp 用例 + mouse('mousedown', overlay, 30, 30); + mouse('mouseup', overlay, 30, 30); + await flush(); + assert(!document.getElementById('selBox').classList.contains('visible'), + 'the box must disappear after the space-toggle cleanup click'); + // 越界框选必须 clamp 到 0..1(放在清除之后,避免复用上一次留下的框) mouse('mousedown', overlay, -50, -50); mouse('mousemove', window, 900, 900); mouse('mouseup', window, 900, 900); await flush(); - assert(lastPost('copyText').text === '0.0000,0.0000,1.0000,1.0000', + assert(lastPost('copyText').text === '0.0000, 0.0000, 1.0000, 1.0000', 'out-of-range selection must clamp to 0..1, got ' + lastPost('copyText').text); // 误触(极小框)不应产生复制 @@ -275,7 +309,7 @@ function mouse(type, target, x, y, button = 0) { await flush(); assert(carouselBtn.classList.contains('active'), 'the carousel must still be running after the box-selection completes'); - assert(lastPost('copyText').text === '0.3000,0.2333,0.8000,0.7000', + assert(lastPost('copyText').text === '0.3000, 0.2333, 0.8000, 0.7000', 'coords taken against a cycling frame must still normalize correctly, got ' + lastPost('copyText').text); carouselBtn.dispatchEvent(new window.MouseEvent('click', { bubbles: true }));