Skip to content

Commit 638580c

Browse files
wehosHongzhi Wenclaude
authored
feat(screenshot): 截图工具加 Snipaste 式标注工具栏 (#1571)
* feat(screenshot): 截图工具加 Snipaste 式标注工具栏 框选后在选区下方浮出工具栏,可在图上画完再确认。纯前端,全部落在 app-crop.js 的现有裁剪遮罩里。 - 工具:矩形/椭圆/箭头/画笔/荧光笔/文字/马赛克,调色板+线宽 S/M/L,撤销/重做/保存/确认/取消 - 标注存图片自然坐标,renderAnnotations(ctx,mapFn,scale) 同供实时预览与烤制,所见即所得,抗 resize - 马赛克 commit 时像素化进缓存 canvas,避免每帧重算 - 快捷键 Ctrl+Z/Y、Delete 撤最后一笔、Enter 确认;右键绘图中丢草稿、空闲退回选择工具 - 复制剪贴板/保存到文件输出原清晰度 PNG;发猫娘的 720p 压缩仍在 app-buttons 裁剪之外,不变 - 按钮观感对齐 react .composer-tool-btn;工具栏始终桌面布局,触摸仅服务真手机 - 8 语言补 cropTool*/cropUndo/cropRedo/cropSave 键 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(screenshot): 清选区复位工具 + resize 按图片坐标重映射选区 - clearSelection 后复位到选择工具:否则清选区后工具栏已隐藏、绘图工具仍激活, 再拖拽会被绘图分支拦截画成隐形标注,用户无法重新框选(Codex P2) - onResize 先用旧 metrics 把选区换算成图片坐标,computeImgMetrics 后再映射回 canvas, 避免直接 clampSel 旧 canvas 坐标导致选区相对截图漂移(Codex P2) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(screenshot): 绘制限定在选区内 + 工具栏无障碍语义 - 绘图起笔必须在选区内(!sel || !hitTestInside 直接忽略),草稿点 clamp 到选区, 不再因选区外点击产生被 clip 掉的隐形标注、污染撤销栈(CodeRabbit) - 工具/颜色/线宽按钮补 aria-pressed 选中态;调色板纯色按钮补 i18n aria-label (8 语言加 cropColorRed/Yellow/Green/Blue/White/Black)(CodeRabbit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3ba10ac commit 638580c

10 files changed

Lines changed: 963 additions & 49 deletions

File tree

static/app-crop.js

Lines changed: 678 additions & 49 deletions
Large diffs are not rendered by default.

static/css/index.css

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,6 +2248,155 @@ body.react-chat-window-dragging {
22482248
}
22492249
}
22502250

2251+
/* ===== 截图标注工具栏 ===== */
2252+
.crop-toolbar {
2253+
position: absolute;
2254+
z-index: 6;
2255+
display: flex;
2256+
flex-wrap: wrap;
2257+
align-items: center;
2258+
gap: 4px;
2259+
max-width: calc(100vw - 24px);
2260+
padding: 6px 8px;
2261+
border-radius: 12px;
2262+
background: rgba(8, 11, 18, 0.85);
2263+
border: 1px solid rgba(255, 255, 255, 0.12);
2264+
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.4);
2265+
backdrop-filter: blur(18px);
2266+
-webkit-backdrop-filter: blur(18px);
2267+
pointer-events: auto;
2268+
user-select: none;
2269+
animation: crop-fade-in 0.15s ease;
2270+
}
2271+
2272+
.crop-tool-group {
2273+
display: flex;
2274+
align-items: center;
2275+
gap: 2px;
2276+
}
2277+
2278+
.crop-tool-divider {
2279+
width: 1px;
2280+
align-self: stretch;
2281+
margin: 4px;
2282+
background: rgba(255, 255, 255, 0.14);
2283+
}
2284+
2285+
/* 按钮观感对齐 react 输入框的 .composer-tool-btn */
2286+
.crop-tool-btn {
2287+
display: inline-flex;
2288+
align-items: center;
2289+
justify-content: center;
2290+
width: 32px;
2291+
height: 32px;
2292+
padding: 0;
2293+
border: 0;
2294+
border-radius: 6px;
2295+
background: transparent;
2296+
color: rgba(255, 255, 255, 0.82);
2297+
cursor: pointer;
2298+
transition: background 0.15s ease, color 0.15s ease;
2299+
}
2300+
2301+
.crop-tool-btn:hover {
2302+
background: rgba(68, 183, 254, 0.12);
2303+
color: #8bd8ff;
2304+
}
2305+
2306+
.crop-tool-btn.is-active {
2307+
background: rgba(68, 183, 254, 0.18);
2308+
color: #44b7fe;
2309+
}
2310+
2311+
.crop-tool-btn:disabled {
2312+
opacity: 0.4;
2313+
cursor: not-allowed;
2314+
}
2315+
2316+
.crop-tool-btn svg {
2317+
display: block;
2318+
pointer-events: none;
2319+
}
2320+
2321+
.crop-tool-btn:focus-visible,
2322+
.crop-color-swatch:focus-visible,
2323+
.crop-width-btn:focus-visible {
2324+
outline: 2px solid rgba(68, 183, 254, 0.85);
2325+
outline-offset: 1px;
2326+
}
2327+
2328+
.crop-tool-confirm { color: #34c759; }
2329+
.crop-tool-confirm:hover { background: rgba(52, 199, 89, 0.16); color: #4cd964; }
2330+
.crop-tool-confirm.is-active { background: rgba(52, 199, 89, 0.16); color: #34c759; }
2331+
2332+
.crop-tool-cancel { color: #ff6b7a; }
2333+
.crop-tool-cancel:hover { background: rgba(220, 53, 69, 0.18); color: #ff8a97; }
2334+
2335+
/* 调色板 */
2336+
.crop-color-swatch {
2337+
width: 18px;
2338+
height: 18px;
2339+
margin: 0 1px;
2340+
padding: 0;
2341+
border-radius: 50%;
2342+
border: 2px solid rgba(255, 255, 255, 0.25);
2343+
cursor: pointer;
2344+
transition: transform 0.12s ease, border-color 0.15s ease, box-shadow 0.15s ease;
2345+
}
2346+
2347+
.crop-color-swatch:hover { transform: scale(1.12); }
2348+
2349+
.crop-color-swatch.is-active {
2350+
border-color: #fff;
2351+
box-shadow: 0 0 0 2px rgba(68, 183, 254, 0.7);
2352+
}
2353+
2354+
/* 线宽 S / M / L */
2355+
.crop-width-btn {
2356+
min-width: 26px;
2357+
height: 28px;
2358+
padding: 0 6px;
2359+
border: 0;
2360+
border-radius: 6px;
2361+
background: transparent;
2362+
color: rgba(255, 255, 255, 0.7);
2363+
font-size: 0.8rem;
2364+
font-weight: 600;
2365+
cursor: pointer;
2366+
transition: background 0.15s ease, color 0.15s ease;
2367+
}
2368+
2369+
.crop-width-btn:hover { background: rgba(68, 183, 254, 0.12); color: #8bd8ff; }
2370+
.crop-width-btn.is-active { background: rgba(68, 183, 254, 0.18); color: #44b7fe; }
2371+
2372+
/* 文字工具的浮层 textarea */
2373+
.crop-text-editor {
2374+
position: absolute;
2375+
z-index: 7;
2376+
margin: 0;
2377+
padding: 1px 2px;
2378+
min-width: 40px;
2379+
border: 1px dashed rgba(68, 183, 254, 0.9);
2380+
border-radius: 3px;
2381+
background: rgba(0, 0, 0, 0.18);
2382+
color: #fff;
2383+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
2384+
font-weight: 600;
2385+
line-height: 1.28;
2386+
white-space: pre;
2387+
overflow: hidden;
2388+
outline: none;
2389+
resize: none;
2390+
pointer-events: auto;
2391+
caret-color: currentColor;
2392+
}
2393+
2394+
/* 绘图工具激活时隐藏选区手柄与网格线,避免误触 */
2395+
.crop-selection-box.crop-selection-box--drawing .crop-selection-handle,
2396+
.crop-selection-box.crop-selection-box--drawing .crop-selection-grid-line {
2397+
display: none;
2398+
}
2399+
22512400
#text-input-row {
22522401
display: flex;
22532402
gap: 8px;

static/locales/en.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,23 @@
697697
"cropTabRecapturing": "Recapturing...",
698698
"cropConfirmTitle": "Confirm crop",
699699
"cropClearSelectionTitle": "Clear selection",
700+
"cropToolSelect": "Select / Move",
701+
"cropToolRect": "Rectangle",
702+
"cropToolEllipse": "Ellipse",
703+
"cropToolArrow": "Arrow",
704+
"cropToolPen": "Pen",
705+
"cropToolHighlight": "Highlighter",
706+
"cropToolText": "Text",
707+
"cropToolMosaic": "Mosaic",
708+
"cropUndo": "Undo",
709+
"cropRedo": "Redo",
710+
"cropSave": "Save to file",
711+
"cropColorRed": "Red",
712+
"cropColorYellow": "Yellow",
713+
"cropColorGreen": "Green",
714+
"cropColorBlue": "Blue",
715+
"cropColorWhite": "White",
716+
"cropColorBlack": "Black",
700717
"avatarPreview": "Show current avatar",
701718
"avatarPreviewLabel": "Current avatar",
702719
"avatarPreviewCardTitle": "Current Avatar",

static/locales/es.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,23 @@
697697
"cropTabRecapturing": "Recapturando...",
698698
"cropConfirmTitle": "Confirmar recorte",
699699
"cropClearSelectionTitle": "Borrar selección",
700+
"cropToolSelect": "Seleccionar / mover",
701+
"cropToolRect": "Rectángulo",
702+
"cropToolEllipse": "Elipse",
703+
"cropToolArrow": "Flecha",
704+
"cropToolPen": "Lápiz",
705+
"cropToolHighlight": "Marcador",
706+
"cropToolText": "Texto",
707+
"cropToolMosaic": "Mosaico",
708+
"cropUndo": "Deshacer",
709+
"cropRedo": "Rehacer",
710+
"cropSave": "Guardar en archivo",
711+
"cropColorRed": "Rojo",
712+
"cropColorYellow": "Amarillo",
713+
"cropColorGreen": "Verde",
714+
"cropColorBlue": "Azul",
715+
"cropColorWhite": "Blanco",
716+
"cropColorBlack": "Negro",
700717
"avatarPreview": "Mostrar avatar actual",
701718
"avatarPreviewLabel": "avatar actual",
702719
"avatarPreviewCardTitle": "Avatar actual",

static/locales/ja.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,23 @@
697697
"cropTabRecapturing": "再撮影中...",
698698
"cropConfirmTitle": "トリミングを確定",
699699
"cropClearSelectionTitle": "選択をクリア",
700+
"cropToolSelect": "選択/移動",
701+
"cropToolRect": "四角形",
702+
"cropToolEllipse": "楕円",
703+
"cropToolArrow": "矢印",
704+
"cropToolPen": "ペン",
705+
"cropToolHighlight": "蛍光ペン",
706+
"cropToolText": "テキスト",
707+
"cropToolMosaic": "モザイク",
708+
"cropUndo": "元に戻す",
709+
"cropRedo": "やり直し",
710+
"cropSave": "ファイルに保存",
711+
"cropColorRed": "",
712+
"cropColorYellow": "",
713+
"cropColorGreen": "",
714+
"cropColorBlue": "",
715+
"cropColorWhite": "",
716+
"cropColorBlack": "",
700717
"avatarPreview": "現在のアバターを表示",
701718
"avatarPreviewLabel": "現在のアバター",
702719
"avatarPreviewCardTitle": "現在のアバター",

static/locales/ko.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,23 @@
697697
"cropTabRecapturing": "다시 캡처 중...",
698698
"cropConfirmTitle": "자르기 확인",
699699
"cropClearSelectionTitle": "선택 영역 지우기",
700+
"cropToolSelect": "선택/이동",
701+
"cropToolRect": "사각형",
702+
"cropToolEllipse": "타원",
703+
"cropToolArrow": "화살표",
704+
"cropToolPen": "",
705+
"cropToolHighlight": "형광펜",
706+
"cropToolText": "텍스트",
707+
"cropToolMosaic": "모자이크",
708+
"cropUndo": "실행 취소",
709+
"cropRedo": "다시 실행",
710+
"cropSave": "파일로 저장",
711+
"cropColorRed": "빨강",
712+
"cropColorYellow": "노랑",
713+
"cropColorGreen": "초록",
714+
"cropColorBlue": "파랑",
715+
"cropColorWhite": "흰색",
716+
"cropColorBlack": "검정",
700717
"avatarPreview": "현재 아바타 표시",
701718
"avatarPreviewLabel": "현재 아바타",
702719
"avatarPreviewCardTitle": "현재 아바타",

static/locales/pt.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,23 @@
697697
"cropTabRecapturing": "Recapturando...",
698698
"cropConfirmTitle": "Confirmar recorte",
699699
"cropClearSelectionTitle": "Limpar seleção",
700+
"cropToolSelect": "Selecionar / mover",
701+
"cropToolRect": "Retângulo",
702+
"cropToolEllipse": "Elipse",
703+
"cropToolArrow": "Seta",
704+
"cropToolPen": "Caneta",
705+
"cropToolHighlight": "Marcador",
706+
"cropToolText": "Texto",
707+
"cropToolMosaic": "Mosaico",
708+
"cropUndo": "Desfazer",
709+
"cropRedo": "Refazer",
710+
"cropSave": "Salvar em arquivo",
711+
"cropColorRed": "Vermelho",
712+
"cropColorYellow": "Amarelo",
713+
"cropColorGreen": "Verde",
714+
"cropColorBlue": "Azul",
715+
"cropColorWhite": "Branco",
716+
"cropColorBlack": "Preto",
700717
"avatarPreview": "Mostrar avatar atual",
701718
"avatarPreviewLabel": "Avatar atual",
702719
"avatarPreviewCardTitle": "Avatar atual",

static/locales/ru.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,23 @@
697697
"cropTabRecapturing": "Повторный захват...",
698698
"cropConfirmTitle": "Подтвердить обрезку",
699699
"cropClearSelectionTitle": "Очистить выделение",
700+
"cropToolSelect": "Выбор / перемещение",
701+
"cropToolRect": "Прямоугольник",
702+
"cropToolEllipse": "Эллипс",
703+
"cropToolArrow": "Стрелка",
704+
"cropToolPen": "Карандаш",
705+
"cropToolHighlight": "Маркер",
706+
"cropToolText": "Текст",
707+
"cropToolMosaic": "Мозаика",
708+
"cropUndo": "Отменить",
709+
"cropRedo": "Повторить",
710+
"cropSave": "Сохранить в файл",
711+
"cropColorRed": "Красный",
712+
"cropColorYellow": "Жёлтый",
713+
"cropColorGreen": "Зелёный",
714+
"cropColorBlue": "Синий",
715+
"cropColorWhite": "Белый",
716+
"cropColorBlack": "Чёрный",
700717
"avatarPreview": "Показать текущий аватар",
701718
"avatarPreviewLabel": "Текущий аватар",
702719
"avatarPreviewCardTitle": "Текущий аватар",

static/locales/zh-CN.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,23 @@
697697
"cropTabRecapturing": "正在重新截图...",
698698
"cropConfirmTitle": "确认截图",
699699
"cropClearSelectionTitle": "取消选区",
700+
"cropToolSelect": "选择/移动",
701+
"cropToolRect": "矩形",
702+
"cropToolEllipse": "椭圆",
703+
"cropToolArrow": "箭头",
704+
"cropToolPen": "画笔",
705+
"cropToolHighlight": "荧光笔",
706+
"cropToolText": "文字",
707+
"cropToolMosaic": "马赛克",
708+
"cropUndo": "撤销",
709+
"cropRedo": "重做",
710+
"cropSave": "保存到文件",
711+
"cropColorRed": "红色",
712+
"cropColorYellow": "黄色",
713+
"cropColorGreen": "绿色",
714+
"cropColorBlue": "蓝色",
715+
"cropColorWhite": "白色",
716+
"cropColorBlack": "黑色",
700717
"avatarPreview": "显示当前头像",
701718
"avatarPreviewLabel": "当前头像",
702719
"avatarPreviewCardTitle": "当前头像",

static/locales/zh-TW.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,23 @@
697697
"cropTabRecapturing": "正在重新截圖...",
698698
"cropConfirmTitle": "確認截圖",
699699
"cropClearSelectionTitle": "取消選區",
700+
"cropToolSelect": "選擇/移動",
701+
"cropToolRect": "矩形",
702+
"cropToolEllipse": "橢圓",
703+
"cropToolArrow": "箭頭",
704+
"cropToolPen": "畫筆",
705+
"cropToolHighlight": "螢光筆",
706+
"cropToolText": "文字",
707+
"cropToolMosaic": "馬賽克",
708+
"cropUndo": "復原",
709+
"cropRedo": "重做",
710+
"cropSave": "儲存至檔案",
711+
"cropColorRed": "紅色",
712+
"cropColorYellow": "黃色",
713+
"cropColorGreen": "綠色",
714+
"cropColorBlue": "藍色",
715+
"cropColorWhite": "白色",
716+
"cropColorBlack": "黑色",
700717
"avatarPreview": "顯示當前頭像",
701718
"avatarPreviewLabel": "當前頭像",
702719
"avatarPreviewCardTitle": "當前頭像",

0 commit comments

Comments
 (0)