├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── dontknow.md │ └── feature_request.yaml └── workflows │ └── build.yml ├── .gitignore ├── .pnpmfile.cjs ├── .vscode ├── extensions.json ├── launch.json └── snippets.code-snippets ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README_en.md ├── assets ├── e-search.desktop ├── icns.src ├── icon.sh ├── icon.svg └── logo │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 32x32_black.png │ ├── 32x32_white.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── gen_logo.ts │ ├── icon.icns │ ├── icon.ico │ ├── macIconTemplate.png │ └── macIconTemplate@2x.png ├── biome.json ├── docs ├── develop │ ├── assets │ │ ├── clip_rect.excalidraw │ │ ├── clip_rect.svg │ │ ├── linear-gradient.excalidraw │ │ ├── linear-gradient.svg │ │ ├── long_clip.excalidraw │ │ ├── long_clip.svg │ │ ├── mouse_zb.excalidraw │ │ ├── mouse_zb.svg │ │ ├── zb.excalidraw │ │ └── zb.svg │ ├── clip.md │ ├── ding.md │ ├── main.md │ ├── ocr.md │ ├── overview.md │ ├── photoEditor.md │ ├── readme.md │ ├── start.md │ └── superRecorder.md └── use │ ├── clip.md │ ├── ding.md │ ├── editor.md │ ├── long_clip.md │ ├── main.md │ ├── ocr.md │ ├── qa.md │ ├── readme.md │ ├── record.md │ ├── save.md │ ├── search.md │ ├── setting.md │ ├── start.md │ ├── super_editor.md │ └── translate.md ├── electron-builder.config.js ├── electron.vite.config.ts ├── electron.vite.config_test.ts ├── lib ├── gtk-open-with ├── ipc.ts ├── kde-open-with ├── key.ts ├── open_with.ts ├── readme.md ├── store │ ├── renderStore.ts │ └── store.ts ├── time_format.ts └── translate │ ├── ar.json │ ├── en.json │ ├── eo.json │ ├── es.json │ ├── fr.json │ ├── ignore.json │ ├── readme.md │ ├── ru.json │ ├── source.json │ ├── tool.js │ ├── translate.ts │ └── zh-HANT.json ├── package.json ├── pnpm-lock.yaml ├── script ├── gen_icon_types.ts └── release_fast_download.ts ├── src ├── ShareTypes.d.ts ├── iconTypes.d.ts ├── main │ └── main.ts └── renderer │ ├── aiVision.html │ ├── aiVision │ └── aiVision.ts │ ├── assets │ ├── 1temple.svg │ ├── icon.svg │ ├── icons │ │ ├── add.svg │ │ ├── add_history.svg │ │ ├── ai_check.svg │ │ ├── arrow.svg │ │ ├── back.svg │ │ ├── blur.svg │ │ ├── brightness.svg │ │ ├── browser.svg │ │ ├── camera.svg │ │ ├── circle.svg │ │ ├── clear.svg │ │ ├── close.svg │ │ ├── concise.svg │ │ ├── contrast.svg │ │ ├── copy.svg │ │ ├── cut.svg │ │ ├── delete.svg │ │ ├── delete_enter.svg │ │ ├── ding.svg │ │ ├── down.svg │ │ ├── draw.svg │ │ ├── draw_select.svg │ │ ├── eraser.svg │ │ ├── excel.svg │ │ ├── file.svg │ │ ├── fill_storke.svg │ │ ├── filters.svg │ │ ├── free_draw.svg │ │ ├── free_select.svg │ │ ├── handle.svg │ │ ├── help.svg │ │ ├── hide.svg │ │ ├── history.svg │ │ ├── hue.svg │ │ ├── ignore.svg │ │ ├── img.svg │ │ ├── last.svg │ │ ├── last_last.svg │ │ ├── left.svg │ │ ├── line.svg │ │ ├── link.svg │ │ ├── long_clip.svg │ │ ├── main.svg │ │ ├── mask.svg │ │ ├── md.svg │ │ ├── mic.svg │ │ ├── minimize.svg │ │ ├── next.svg │ │ ├── next_next.svg │ │ ├── number.svg │ │ ├── ocr.svg │ │ ├── opacity.svg │ │ ├── open.svg │ │ ├── paste.svg │ │ ├── pause.svg │ │ ├── pixelate.svg │ │ ├── play_pause.svg │ │ ├── polygon.svg │ │ ├── polyline.svg │ │ ├── position.svg │ │ ├── position_back.svg │ │ ├── position_backwards.svg │ │ ├── position_forwards.svg │ │ ├── position_front.svg │ │ ├── record.svg │ │ ├── rect.svg │ │ ├── rect_select.svg │ │ ├── recume.svg │ │ ├── regex.svg │ │ ├── reload.svg │ │ ├── replace.svg │ │ ├── replace_all.svg │ │ ├── right.svg │ │ ├── saturation.svg │ │ ├── save.svg │ │ ├── scan.svg │ │ ├── screen.svg │ │ ├── search.svg │ │ ├── select_all.svg │ │ ├── setting.svg │ │ ├── shapes.svg │ │ ├── size.svg │ │ ├── space.svg │ │ ├── spray.svg │ │ ├── star.svg │ │ ├── start_record.svg │ │ ├── stop_record.svg │ │ ├── super_edit.svg │ │ ├── text.svg │ │ ├── toptop.svg │ │ ├── translate.svg │ │ ├── up.svg │ │ └── updown.svg │ └── sample_picture.svg │ ├── browser_bg.html │ ├── browser_bg │ └── browser_bg.ts │ ├── capture.html │ ├── clip │ └── clip_window.ts │ ├── css │ ├── clip.css │ ├── css.css │ ├── ding.css │ ├── recorder.css │ ├── recorderTip.css │ └── root.css │ ├── ding.html │ ├── ding │ └── ding.ts │ ├── editor.html │ ├── editor │ └── editor.ts │ ├── lib │ ├── ai.ts │ ├── history.ts │ └── removeObj.ts │ ├── ocr │ └── ocr.ts │ ├── photoEditor.html │ ├── photoEditor │ └── photoEditor.ts │ ├── recorder.html │ ├── recorder │ └── recorder.ts │ ├── recorderTip.html │ ├── recorderTip │ └── recorderTip.ts │ ├── root │ └── root.ts │ ├── screenShot │ └── screenShot.ts │ ├── setting.html │ ├── setting │ └── setting.ts │ ├── translate.html │ ├── translate │ └── translate.ts │ ├── translator.html │ ├── translator │ └── translator.ts │ ├── ui │ └── ui.ts │ ├── ui_test.html │ ├── videoEditor.html │ └── videoEditor │ └── videoEditor.ts ├── test ├── clip.html └── ui │ ├── clip.ts │ └── init.ts ├── tsconfig.json ├── tsconfig.node.json └── tsconfig.web.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | 7 | [*.{html,css,ts}] 8 | indent_style = space 9 | indent_size = 4 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: 错误报告 Bug Report 2 | description: "创建报告以帮助项目改进 Create a report to help us improve." 3 | title: "……存在……错误" 4 | labels: bug 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | 在提交新问题之前,请在[GitHub issues](https://github.com/xushengfeng/eSearch/issues?q=is%3Aissue)中搜索,避免重复提交。 11 | 如果有多个问题,请分开在多个issues中提交。 12 | 我会对问题尽快分析并回复,请耐心等待。 13 | 有时候会向您提问更多信息,这有助于问题的解决。 14 | 通过软件托盘的反馈,可以自动添加软件版本等信息。 15 | if you are NOT Chinese speaker, using English is welcome. 16 | - type: textarea 17 | id: main 18 | attributes: 19 | label: 描述问题 Description of the problem 20 | description: "请描述您遇到的问题" 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: steps 25 | attributes: 26 | label: 重现步骤 Steps to reproduce 27 | description: "请描述如何重现问题" 28 | validations: 29 | required: true 30 | - type: textarea 31 | id: expected 32 | attributes: 33 | label: 期望结果 Expected behavior 34 | description: "请描述您期望的结果" 35 | validations: 36 | required: false 37 | - type: textarea 38 | id: more 39 | attributes: 40 | label: 更多信息 Additional context 41 | description: "截屏、错误信息(按住 Ctrl+Shift+I 唤出控制台,切换到console选项卡查看报错)等" 42 | validations: 43 | required: false 44 | - type: input 45 | id: os 46 | attributes: 47 | label: 操作系统 OS 48 | description: "必要时附带架构(x64/arm64),来自软件的反馈会自动附带" 49 | validations: 50 | required: false 51 | - type: input 52 | id: v 53 | attributes: 54 | label: 软件版本 Software version 55 | validations: 56 | required: true 57 | - type: input 58 | attributes: 59 | label: 屏幕参数 Screen parameters 60 | description: "屏幕分辨率、缩放,如果是多屏,请把各个屏幕的参数和屏幕排布写出" 61 | validations: 62 | required: false 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/dontknow.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 无法确定 3 | about: 无法确定是错误还是需要新功能 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | 10 | 11 | 12 | 13 | **描述** 14 | 15 | **重现步骤** 16 | 17 | 1. 在……界面 18 | 2. 点击…… 19 | 3. 发生错误或不是我预想的那样 20 | 21 | **需求** 22 | 本应该显示 xxx,需要 xxx 新功能来代替 23 | 24 | **屏幕截图** 25 | 26 | 27 | **设备和软件信息** 28 | 29 | - 操作系统和具体版本: 30 | - 屏幕参数: 31 | - 软件版本: 32 | 33 | **自由描述** 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: 新需求 Feature Request 2 | description: "请描述你想要实现的功能。" 3 | title: "建议在……添加……功能/改进" 4 | labels: 新需求 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | 一个issue最好只描述一个问题或想法,如果有很多(特别是问题),最好分成几个issue,便于搜索。 11 | 在提交新问题之前,请在[GitHub issues](https://github.com/xushengfeng/eSearch/issues?q=is%3Aissue)中搜索,避免重复提交。 12 | 看看设置或[文档](https://github.com/xushengfeng/eSearch/blob/master/docs/use/start.md),是不是已经有了。 13 | - type: textarea 14 | attributes: 15 | label: 描述 Description 16 | description: "请自由地描述你的提议,可以提供图片或其他软件来更好描述" 17 | validations: 18 | required: true 19 | - type: input 20 | id: os 21 | attributes: 22 | label: 操作系统 OS 23 | description: "必要时附带架构(x64/arm64),来自软件的反馈会自动附带" 24 | validations: 25 | required: false 26 | - type: input 27 | id: v 28 | attributes: 29 | label: 当前软件版本 Current Version 30 | validations: 31 | required: false 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build/release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | release: 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [macos-latest, ubuntu-latest, windows-latest] 16 | arch: [x64, arm64] 17 | max-parallel: 3 18 | 19 | steps: 20 | - name: Check out Git repository 21 | uses: actions/checkout@v1 22 | 23 | - name: Install Node.js, NPM and Yarn 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 20 27 | 28 | - name: Install Linux Lib 29 | if: matrix.os == 'ubuntu-latest' 30 | run: | 31 | sudo apt-get update 32 | sudo apt-get install libx11-dev libxext-dev libxtst-dev libxrender-dev libxmu-dev libxmuu-dev rpm 33 | 34 | - name: ${{ matrix.os }} ${{ matrix.arch }} build 35 | env: 36 | M_ARCH: ${{matrix.arch}} 37 | run: | 38 | npm install -g pnpm@10.0.0 39 | pnpm install 40 | pnpm run dist 41 | 42 | - name: release 43 | uses: softprops/action-gh-release@v2 44 | with: 45 | files: "build/*" 46 | draft: false 47 | prerelease: true 48 | generate_release_notes: true 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | __pycache__ 3 | readme/todo.md 4 | out/ 5 | eSearch 6 | .VSCodeCounter/ 7 | ocr/ppocr 8 | .DS_Store 9 | build 10 | preload_config 11 | portable 12 | lib/win_rect.exe 13 | lib/copy.exe 14 | lib/ffmpeg 15 | assets/onnx 16 | lib/translate/*.csv 17 | -------------------------------------------------------------------------------- /.pnpmfile.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | readPackage: (pkg) => { 4 | // biome-ignore lint/performance/noDelete: 5 | delete pkg.optionalDependencies.canvas; 6 | return pkg; 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "biomejs.biome", 4 | "EditorConfig.EditorConfig" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceFolder}", 9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" 12 | }, 13 | "runtimeArgs": [ 14 | "--sourcemap" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /.vscode/snippets.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | // Place your eSearch 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and 3 | // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope 4 | // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is 5 | // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: 6 | // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. 7 | // Placeholders with the same ids are connected. 8 | // Example: 9 | // "Print to console": { 10 | // "scope": "javascript,typescript", 11 | // "prefix": "log", 12 | // "body": [ 13 | // "console.log('$1');", 14 | // "$2" 15 | // ], 16 | // "description": "Log output to console" 17 | // } 18 | "icon": { 19 | "scope": "html", 20 | "prefix": "icon", 21 | "body": [ 22 | "" 23 | ], 24 | "description": "icon" 25 | }, 26 | "comment": { 27 | "scope": "html", 28 | "prefix": "comment", 29 | "body": [ 30 | "$1", 31 | ], 32 | "description": "设置提示" 33 | } 34 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | 主要是关于 pr 的指南 4 | 5 | - [fork 本仓库](https://github.com/xushengfeng/eSearch/fork) 6 | - clone 到本地或直接在线上编辑器修改 7 | - 提交一个 Pull Request 8 | 9 | ## 编辑代码 10 | 11 | 首先这个项目最开始是我一拍脑袋写的,并且跨度有点大,存在大量中文变量、拼音变量、语法糖、if 嵌套等乱象,还望海涵。 12 | 13 | - 命名规则、函数习惯等都无所谓,还望写注释 14 | - 如果原来的代码过于抽象,可以联系我一起理解 15 | - electron 版本一般不会太旧,我还开启了"enable-experimental-web-platform-features",用些新语法能减轻心智负担 16 | 17 | 你可以在[开发文档](docs/develop/readme.md)查阅更多代码说明。 18 | 19 | ## 提交 20 | 21 | commit 命名我习惯使用`范围 修复/添加某某功能`,比如`截屏 修复创建图形坐标错误` 22 | 23 | ## 合并 24 | 25 | 请耐心等待我的合并:) 26 | 27 | ## 感谢 28 | 29 | 最后提前感谢你的贡献! 30 | -------------------------------------------------------------------------------- /assets/e-search.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=eSearch 3 | Comment=识屏 · 搜索 4 | GenericName=eSearch 5 | Exec=e-search %U 6 | Icon=e-search 7 | Type=Application 8 | StartupNotify=true 9 | Categories=Graphics;System;Utility; 10 | MimeType=image/png;image/jpg;image/svg; -------------------------------------------------------------------------------- /assets/icns.src: -------------------------------------------------------------------------------- 1 | mkdir MyIcon.iconset 2 | sips -z 16 16 logo/1024x1024.png --out MyIcon.iconset/icon_16x16.png 3 | sips -z 32 32 logo/1024x1024.png --out MyIcon.iconset/icon_16x16@2x.png 4 | sips -z 32 32 logo/1024x1024.png --out MyIcon.iconset/icon_32x32.png 5 | sips -z 64 64 logo/1024x1024.png --out MyIcon.iconset/icon_32x32@2x.png 6 | sips -z 128 128 logo/1024x1024.png --out MyIcon.iconset/icon_128x128.png 7 | sips -z 256 256 logo/1024x1024.png --out MyIcon.iconset/icon_128x128@2x.png 8 | sips -z 256 256 logo/1024x1024.png --out MyIcon.iconset/icon_256x256.png 9 | sips -z 512 512 logo/1024x1024.png --out MyIcon.iconset/icon_256x256@2x.png 10 | sips -z 512 512 logo/1024x1024.png --out MyIcon.iconset/icon_512x512.png 11 | cp logo/1024x1024.png MyIcon.iconset/icon_512x512@2x.png 12 | iconutil -c icns MyIcon.iconset 13 | rm -R MyIcon.iconset 14 | mv MyIcon.icns logo/icon.icns 15 | -------------------------------------------------------------------------------- /assets/icon.sh: -------------------------------------------------------------------------------- 1 | size=(16 32 48 64 128 256 512 1024) 2 | 3 | echo Making bitmaps from your svg... 4 | 5 | for i in ${size[@]}; do 6 | inkscape --export-type="png" --export-filename="logo/${i}x$i.png" -w $i -h $i icon.svg 7 | done 8 | convert -density 256x256 -background transparent icon.svg -define icon:auto-resize=256,48,32,16 -colors 256 logo/icon.ico 9 | -------------------------------------------------------------------------------- /assets/logo/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/1024x1024.png -------------------------------------------------------------------------------- /assets/logo/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/128x128.png -------------------------------------------------------------------------------- /assets/logo/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/16x16.png -------------------------------------------------------------------------------- /assets/logo/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/256x256.png -------------------------------------------------------------------------------- /assets/logo/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/32x32.png -------------------------------------------------------------------------------- /assets/logo/32x32_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/32x32_black.png -------------------------------------------------------------------------------- /assets/logo/32x32_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/32x32_white.png -------------------------------------------------------------------------------- /assets/logo/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/48x48.png -------------------------------------------------------------------------------- /assets/logo/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/512x512.png -------------------------------------------------------------------------------- /assets/logo/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/64x64.png -------------------------------------------------------------------------------- /assets/logo/gen_logo.ts: -------------------------------------------------------------------------------- 1 | // biome-ignore format: 2 | import { createCanvas, loadImage,ImageData } from "canvas"; 3 | import { writeFileSync } from "node:fs"; 4 | 5 | const img = await loadImage("./32x32.png"); 6 | 7 | const canvas = createCanvas(32, 32); 8 | const ctx = canvas.getContext("2d"); 9 | 10 | ctx.drawImage(img, 0, 0); 11 | 12 | const data = ctx.getImageData(0, 0, 32, 32).data; 13 | 14 | const black = new Uint8ClampedArray(32 * 32 * 4); 15 | const white = new Uint8ClampedArray(32 * 32 * 4); 16 | 17 | for (let i = 0; i < data.length; i += 4) { 18 | const a = data[i + 3]; 19 | for (let j = 0; j < 4; j++) { 20 | black[i + j] = data[i + j]; 21 | white[i + j] = data[i + j]; 22 | } 23 | if (a !== 0) 24 | for (let j = 0; j < 3; j++) { 25 | black[i + j] = 255 - a; 26 | } 27 | if (a !== 0) 28 | for (let j = 0; j < 3; j++) { 29 | white[i + j] = a; 30 | } 31 | } 32 | 33 | const blackImg = new ImageData(black, 32, 32); 34 | const whiteImg = new ImageData(white, 32, 32); 35 | 36 | const blackCanvas = createCanvas(32, 32); 37 | const whiteCanvas = createCanvas(32, 32); 38 | 39 | blackCanvas.getContext("2d").putImageData(blackImg, 0, 0); 40 | whiteCanvas.getContext("2d").putImageData(whiteImg, 0, 0); 41 | 42 | writeFileSync("./32x32_black.png", blackCanvas.toBuffer("image/png")); 43 | writeFileSync("./32x32_white.png", whiteCanvas.toBuffer("image/png")); 44 | -------------------------------------------------------------------------------- /assets/logo/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/icon.icns -------------------------------------------------------------------------------- /assets/logo/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/icon.ico -------------------------------------------------------------------------------- /assets/logo/macIconTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/macIconTemplate.png -------------------------------------------------------------------------------- /assets/logo/macIconTemplate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/assets/logo/macIconTemplate@2x.png -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "formatter": { 7 | "indentStyle": "space", 8 | "indentWidth": 4 9 | }, 10 | "linter": { 11 | "enabled": true, 12 | "rules": { 13 | "recommended": true, 14 | "complexity": { 15 | "useDateNow": "error" 16 | }, 17 | "correctness": { 18 | "useValidForDirection": "error" 19 | }, 20 | "style": { "noNonNullAssertion": "off" } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/develop/clip.md: -------------------------------------------------------------------------------- 1 | # 截屏 2 | 3 | 使用`node-screenshot`库,Wayland 平台调用了桌面的截屏软件,再读取图片,所以会有些慢。由于该库暂不支持 Linux Arm64,所以我在 nodejs 下模仿类似的操作,读取设置里的命令,调用外部截屏软件,然后读取图片。 4 | 5 | ## 坐标 6 | 7 | canvas 坐标与数学上的一致,在左上角的一个像素点应为`(0, 0)` `1x1`的矩形。包括图片的裁切,实际上是 canvas 上的整数矩形坐标。这些整数坐标描述了整数方格的一个个交点。对于框选和获取元素定位来说,这些**数学坐标**是最重要的。所以理论上,大小栏和鼠标跟随栏的坐标应该是数学坐标,放大镜的中心点应为相交线。 8 | 9 | 而我们需要的取色器却要获取一个像素点的信息,准确来说,是一个范围,一个 1x1 的矩形,也可以近似为 n.5 的信息,我们可以把他成为**像素坐标**。在取色器上,此时的坐标又应该是像素坐标,放大镜中间应该是中心像素。 10 | 11 | ![数学坐标和像素坐标示意图](assets/zb.svg) 12 | 13 | 我们可以发现,数学坐标和像素坐标这两个看似矛盾的概念在逻辑上可以相互转化,如果我们以数学坐标(整数,落在像素顶点上)为准,那这个坐标周围的四个像素就可以是取色目标;如果我们以像素坐标为准,那这个像素矩形的四个顶点也表示了四个数学坐标。后一种方式是**eSearch**的逻辑。然而在开发初期,一直到现在,都要面临鼠标事件的不准确问题。 14 | 15 | ![鼠标坐标](assets/mouse_zb.svg) 16 | 17 | 在 Chrome 126(Linux Wayland)实验可知,鼠标事件的坐标都是整数,应该是经过`math.round`转换后的数学坐标。比如$(1.5, 2.5)$都被映射成$2$。我们需要的整数数学坐标直接使用鼠标事件的坐标即可。 18 | 19 | 至于像素坐标,由于鼠标事件无法提供精确的坐标,所以我们似乎无法计算。在画布缩放==1 时,我们可以简单地用鼠标事件坐标表示,可以映射所有像素,肉眼也看不出差别。但缩放!=1 时,特别是>1 时,我们可以看到鼠标所在图片像素的位置,这就需要精准计算了,可以获取鼠标在屏幕上的坐标(`offsetX`在`scale`的画布上也近似了),通过缩放转化计算,我们可以获取高精度的数学坐标,通过`Math.floor`或`Math.ceil`取整,得到像素坐标。 20 | 21 | 说完具体实践,我们再说回理论,尽管数学坐标和像素坐标可以相互转化,但他们都有四种情况,如何使用和显示?**eSearch**像素坐标优先,因为放大后像素本身比像素顶点更明显,鼠标指在像素上,交互也应该是像素坐标才对。在取色、框选等输入交互中使用像素坐标,而代码处理都是使用数学坐标。因此绘制矩形框选需要数学坐标,取框选前后两个像素的外框作为几个数学坐标。 22 | 23 | ![像素坐标作为框选的数学坐标](assets/clip_rect.svg) 24 | 25 | 自由框选就不能这么做了,所以直接用像素左上角的数学坐标即可。大小栏不直接参与交互,显示数学坐标即可。鼠标跟随栏上的坐标,同样用像素左上角的数学坐标,这样会导致不从左上角开始的框选显示坐标和框选坐标有出入。 26 | 27 | ## 广截屏 28 | 29 | 即滚动截屏 30 | 31 | 思路:获取滚动前后位移,拼接 32 | 33 | 由于我们很难通过系统 API 层面获取滚动的像素,比如一些网页可以监听滚动事件自己设置滚动距离,所以我们通过分析图片来获取滚动距离。 34 | 35 | 需要至少两张截屏图片,一张滚动前,一张滚动后。使用`uiohook-napi`获取鼠标滚动事件和方向键,也可以定时截屏。 36 | 37 | 可以通过特征点算法来分析图片位移,但`opencv.js`没有提供,所以我只使用了最简单的模板匹配算法。 38 | 39 | 由实践可知,模板需要小于源图片大小,否则匹配结果不准确,所以我们需要裁切第二张图片。 40 | 41 | 还要考虑的是一些界面有固定元素,比如导航栏等,我们可以通过异或算法来消除这些固定元素,但在 eSearch 中,我使用了简单的裁切,一般固定元素在图片四周,所以我们自己裁切,大部分情况下效果不错。 42 | 43 | 举个例子:有“目”字形的页面结构,第 1、3 个“口”可能是固定元素,第 2 可能是滚动元素。我们预期得到一个中间长,上下固定元素均保留的长截屏。我们裁切第 2 作为模板,匹配上一张截屏,得到位移。如果位移向下,把 2、3 覆盖到上一张截屏,这样 1 保留,2 延伸,并覆盖了上一张的 3。其他方向同理,如果考虑上下左右滚动的广截屏,以九宫格来切割。 44 | 45 | ![广截屏示意图](assets/long_clip.svg) 46 | -------------------------------------------------------------------------------- /docs/develop/ding.md: -------------------------------------------------------------------------------- 1 | # 贴图 2 | 3 | 贴图需要置于顶层、可缩放、可移动、可调节透明度、可鼠标穿透,还需要管理其他贴图(取消穿透的贴图)。 4 | 5 | 首先自然想到使用`BrowserWindow`暂时每一张贴图,但在缩放时网页反应不及时,透明窗口在 Windows 下无法调节大小。 6 | 7 | 所以我们只能考虑通过 web 绘制每一张贴图的窗口(不考虑额外语言或技术的实现)。 8 | 9 | 我们需要创建一个透明的全屏置顶窗口,通过主进程发送的图片和坐标(来自截屏)来绘制每一张贴图。 10 | 11 | 为了模拟窗口效果,我们只允许贴图接收鼠标事件,无贴图的位置鼠标穿透。见[主进程-透明窗口](main.md#透明窗口) 12 | -------------------------------------------------------------------------------- /docs/develop/main.md: -------------------------------------------------------------------------------- 1 | # 主进程 2 | 3 | ## 异形窗口 4 | 5 | [electron 讨论](https://github.com/electron/electron/issues/1335) 6 | 7 | 贴图、广截屏、屏幕翻译都使用了异形透明窗口,部分区域接收鼠标和键盘事件,其他区域只负责显示。 8 | 9 | 通过`screen.getCursorScreenPoint()`获取鼠标位置,通过减去窗口位置,计算出相对位置,把相对位置发送到渲染进程。 10 | 11 | 渲染进程根据相对位置,通过`elementFromPoint(s)()`获取鼠标所在元素,这里可以通过 js 判断是否为可点击元素,再把`boolean`发送给主进程,主进程根据`boolean`设置鼠标穿透`setIgnoreMouseEvents`。 12 | 13 | 然而 macOS 和 Wayland 不支持在窗口外获取鼠标位置,当窗口鼠标穿透时就再也无法获取鼠标位置了。广截屏操作不多,使用了全局快捷键代替鼠标点击,其他的暂时没有解决方案。 14 | 15 | ## 运行目录 16 | 17 | 根据编译出来的 js 文件深度,可以通过`path.join(__dirname, 向上目录深度)`获取你想要的运行目录,**eSearch**编译的目录为`eSearch/out/main/main.js`和`eSearch/out/renderer/*.html`,定义的`runPath`为`join(__dirname, '../../')`也就是项目目录。 18 | 19 | 需要注意**eSearch**不使用 asar 打包,`app.isPackaged`时的`__dirname`应该有所不同,具体请自己实验。 20 | 21 | ## 系统语言 22 | 23 | 一般可以通过`app.getLocale()`获取当前语言。然而,为了减小打包体积,**eSearch**移除了`locales`目录,而`app.getLocale()`依赖于`locales`目录。更好的方法是`app.getPreferredSystemLanguages()`,这是真正的获取系统语言的 api。 24 | -------------------------------------------------------------------------------- /docs/develop/ocr.md: -------------------------------------------------------------------------------- 1 | # OCR 2 | 3 | 使用 PaddleOCR 实现文字识别。为了在 web(Electron)上使用,需要把 PaddleOCR 的 Python 代码转换为 JavaScript,同时把模型转换为[ONNX](https://onnx.ai/)格式。 4 | 5 | 原本打算使用 Paddle.js,但当时支持有限,所以代码是直接从 Python 中翻译过来的,一边调试一边学习,解码部分参考了 Paddle.js 的实现。后来 LLM 横空出世,一些图片处理功能在以前没来得及实现的都使用 AI 翻译了。 6 | 7 | ## PaddleOCR 分析 8 | 9 | 基于 v3 版本,尽管现在的模型是 v4。 10 | 11 | 文字识别分为文字检测(rec)和文字识别(det)(目前不考虑方向、排版等)。 12 | 13 | 文字检测获取文字坐标(Paddle 里按行),裁切后送入文字识别模型。 14 | 15 | ### 文字检测 16 | 17 | 结果是二值图,每个像素点为 0 或 1,代表是否有文字。通过 opencv 查找轮廓,确定边框。 18 | 19 | ### 文字识别 20 | 21 | 输入是裁切后的图像,输出数组的一级是按字的数组,二级是字典索引对应的概率。字典一字一行,行号-1 是每个字的索引。输出的概率列中,对应了每个字与图片中文字的相似程度,找到概率最大的索引,对应的文字就是结果文字。 22 | 23 | 24 | 25 | ## 排版分析 26 | 27 | 使用模型进行排版识别的成本有点高,我打算用算法来分析。 28 | 29 | 文字检测输出的矩形坐标比较精细,像一行里的话,模型会把空格隔开的小段文字视为一个矩形区域。 30 | 31 | 我们先把这些小片段合并成一行,再合并成一段。 32 | 33 | ### 合并为行 34 | 35 | 一开始的算法是把 y 坐标相近(偏差不超过平均高度 50%)的小片段合并在一起,这种算法在大部分情况下运行良好,但遇到左右分栏的文本,会粗暴地跨栏合并,毕竟他们 y 坐标确实是相近的。 36 | 37 | 通过分析,我们知道分栏的间距一般都会比句子空格大,或用线条或颜色分隔开。现在只考虑间距问题。 38 | 39 | 我们把矩形按行分类,y 相差不大(0.5 高度)且直接间距小于高度的合一起。 40 | 41 | ### 分栏 42 | 43 | 上面的操作已经隐式分栏了,同一 y 坐标存在不同的 x 坐标,意味着属于不同栏。 44 | 45 | 模拟人阅读方式,从左上开始,遍历 y 坐标。找出中心最近的矩形,通过一定规则的过滤(左、中、右至少有一个大致对齐),判断是添加到已有的栏还是新建栏。 46 | 47 | ### 合并为段 48 | 49 | 有两种分段方式:通过空行和通过行首空格。 50 | 51 | 空行分段同样可以使用间距分析,直接统计间距频度,以下是一个真实数据: 52 | 53 | ```json 54 | { 55 | "-3": 2, 56 | "-2": 1, 57 | "0": 4, 58 | "1": 4, 59 | "2": 4, 60 | "3": 3, 61 | "4": 4, 62 | "5": 5, 63 | "7": 1, 64 | "8": 1, 65 | "9": 1, 66 | "10": 1, 67 | "13": 1 68 | } 69 | ``` 70 | 71 | 我们注意到 5-7 的位置应该是我们需要分段的间距:大于等于 7 的间距都是段间距,其他是行间距。取导数绝对值最大的即可。我还提升他们使其大于 0,并去掉最大频率的数来减少极端数据的影响。 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/develop/overview.md: -------------------------------------------------------------------------------- 1 | # 整体说明 2 | 3 | ## 技术选型 4 | 5 | 使用 Electron 开发。高情商:JS 适合快速开发,低情商:我不擅长 C++和 Rust。 6 | 7 | 跨平台方案的体积和内存自然是很大,所以我尽量让功能对得上他的体积。tauri 也不错,只能希望以后 Windows 和 macOS 下也有类似 ArchLinux 下 Electron 共享运行时的方案。 8 | 9 | 使用 Biome 作为格式化和检查工具,使用 vite 作为打包工具。 10 | 11 | 前端: 12 | 13 | 使用 TypeScript,很少使用类型体操,仅作为简单的类型标注。主进程开启了 strict 模式,但渲染进程没有,历史遗留问题太多。 14 | 15 | 框架: 16 | 17 | 使用自己开发的[dkh-ui](https://github.com/xushengfeng/dkh-ui),只是对 JQuery 的拙劣模仿。我学不会`vue`和`react`,搞不懂 hook,ref 这些概念,所以你可以在代码里看到直接使用`document`和`HTML`的操作,当然也在逐步转为 dkh。 18 | 19 | 库: 20 | 21 | 截屏库: [node-screenshots](https://github.com/nashaofu/node-screenshots) 22 | 23 | 图片编辑库:[fabric.js](https://github.com/fabricjs/fabric.js) 24 | 25 | 本地文字识别:[eSearch-OCR](https://github.com/xushengfeng/eSearch-OCR),基于`onnx`和`paddleOCR`实现。 26 | 27 | 其他库可以参考`package.json`文件。 28 | 29 | ## 文件说明 30 | 31 | `electron-builder.config.js` electron-builder 配置文件,用于打包\ 32 | `assets` 程序图标\ 33 | `src` 项目源码 34 | 35 | `src/main/main.ts` 主进程 处理 cli,窗口管理 36 | 37 | `src/renderer/assets` 渲染进程资源,如图标\ 38 | `src/renderer/browser_bg` 主页面浏览器错误提示\ 39 | `src/renderer/clip` 截屏界面\ 40 | `src/renderer/css` 样式文件\ 41 | `src/renderer/ding`贴图界面,贴图屏幕翻译\ 42 | `src/renderer/editor` 主页面,编辑器,OCR、以图搜图和二维码识别也在此运行\ 43 | `src/renderer/photoEditor` 高级图片编辑器\ 44 | `src/renderer/recorder` 录屏提示栏、录屏编辑\ 45 | `src/renderer/recorderTip` 录屏框选、光标和按键提示\ 46 | `src/renderer/root` 初始化样式,保持各个页面的统一\ 47 | `src/renderer/screenShot` 简单封装的截屏库,用于 cli 截屏、截屏界面截屏和屏幕翻译截屏\ 48 | `src/renderer/setting` 设置界面\ 49 | `src/renderer/translate` 主页面翻译,封装了常用 API\ 50 | `src/renderer/translator` 实时屏幕翻译\ 51 | `src/renderer/videoEditor` 高级录屏编辑器\ 52 | 53 | `src/renderer/capture.html` 截屏页面 54 | 55 | `src/ShareTypes.d.ts` 配置类型定义,主进程和渲染进程 ipc 时类型定义,在这种多页面的项目中很方便 56 | 57 | `lib/store` 自己写的设置存储库,参考了`electron-store`,但直接使用 ts 进行类型定义,不依赖`ajv`\ 58 | `lib/translate` 翻译库,用于多语言国际化 59 | 60 | OCR 模型和人像抠图模型在打包或编译时下载,不放在 git 里了,见`electron-builder.config.js` 61 | 62 | ## 其他 63 | 64 | 图标: 65 | 66 | 使用 svg 图标,通过 `` 显示。 67 | -------------------------------------------------------------------------------- /docs/develop/photoEditor.md: -------------------------------------------------------------------------------- 1 | # 高级图片编辑器 2 | 3 | ## 渐变 4 | 5 | 归纳[CSS 渐变](https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient),可以分为线性(linear)渐变、径向(radial)渐变、锥形(conic)渐变和他们的重复渐变。 6 | 7 | Canvas API 渐变的实现,可以参考[MDN CanvasGradient](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasGradient),但更多参数,颜色断点仅支持百分比。 8 | 9 | 我们的渐变不需要太多功能,参数就只是中心位置、角度、重复和颜色断点。 10 | 11 | ### 线性渐变 12 | 13 | 参考[MDN linear-gradient()](https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/linear-gradient),需要注意线性轴的位置,对应`ctx.createLinearGradient` 14 | 15 | 为了保证边角也应用渐变,不是裁切或填充,需要对轴进行计算,MDN 上有一副图示: 16 | 17 | ![MDN linear-gradient 轴示意图](https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient/linear-gradient/linear-gradient.png) 18 | 19 | 由于是线性,我们可以在轴的垂直方向平移轴以方便计算。 20 | 21 | ![平移后](./assets/linear-gradient.svg) 22 | 23 | 角度(angle)与一般数学上的定义不同,从-y 到+x,我们先不管这么多,现在只需要计算轴长,具体坐标才考虑角度。图中角假设为锐角。 24 | 25 | 起始坐标为$(0,h)$,终止坐标为$(\sin(a),h-\cos(a))$ 26 | -------------------------------------------------------------------------------- /docs/develop/readme.md: -------------------------------------------------------------------------------- 1 | # 开发文档 2 | 3 | 本文档用于备忘、pr 指引和技术分享 4 | 5 | - [开始](start.md) 6 | - [整体说明](overview.md) 7 | - [主进程](main.md) 8 | - [截屏](clip.md) 9 | - [OCR](ocr.md) 10 | - [高级图片编辑](photoEditor.md) 11 | - [超级录屏](superRecorder.md) 12 | -------------------------------------------------------------------------------- /docs/develop/start.md: -------------------------------------------------------------------------------- 1 | # 开始 2 | 3 | ## 准备工作 4 | 5 | - vscode 6 | - npm 7 | - node.js 8 | - git 9 | 10 | 大陆用户为了方便访问 GitHub,可以使用[Watt Toolkit](https://steampp.net/)进行免费的 GitHub 加速。 11 | 12 | 建议使用`pnpm`作为包管理工具,同时设置镜像源为 npmmirror.com: 13 | 14 | ```shell 15 | pnpm config set registry https://registry.npmmirror.com/ 16 | ``` 17 | 18 | 设置 Electron 的镜像源: 19 | 20 | ```shell 21 | pnpm config set electron_mirror https://npmmirror.com/mirrors/electron/ 22 | ``` 23 | 24 | 克隆项目到本地: 25 | 26 | ```shell 27 | git clone https://github.com/xushengfeng/eSearch.git 28 | # 如果不需要代码回溯 29 | git clone --depth=1 https://github.com/xushengfeng/eSearch.git 30 | ``` 31 | 32 | 使用 vscode 打开`eSearch`文件夹,安装依赖: 33 | 34 | ```shell 35 | pnpm install 36 | ``` 37 | 38 | ## 运行与编译 39 | 40 | 运行: 41 | 42 | ```shell 43 | pnpm run start 44 | ``` 45 | 46 | 由于我还没配置好 vite 下调试带有`.node`的库,所以无法使用`pnpm run dev`。 47 | 48 | 打包: 49 | 50 | ```shell 51 | pnpm run pack 52 | ``` 53 | 54 | 编译: 55 | 56 | ```shell 57 | pnpm run dist 58 | ``` 59 | 60 | 打包后的文件位于`build`文件夹下,已经有可执行文件,编译则生成安装包。 61 | -------------------------------------------------------------------------------- /docs/use/ding.md: -------------------------------------------------------------------------------- 1 | # 贴图 2 | 3 | 选取的区域变成一个窗口放在屏幕上。 4 | 5 | 鼠标在贴图上通过滚轮滚动可进行缩放。鼠标放在窗口上,顶部弹出工具栏。 6 | 7 | 可以改变窗口透明度和大小,可以再次编辑、最小化、归位和关闭。 8 | 9 | ### 归位 10 | 11 | 归位可以让窗口回到最开始位置和大小,于原来截屏位置贴合以至于无法察觉。 12 | 13 | ### 悬浮条 鼠标穿透 14 | 15 | 在屏幕左上角会常驻竖向条形的提示条,点击它,可以展开控制栏,进行贴图窗口最小化还原或关闭。 16 | 17 | 还可以控制贴图鼠标穿透,使贴图成为一个在屏幕上的图案,无法与鼠标交互,在一些情况下很有用。 18 | 19 | --- 20 | 21 | 实际上贴图“窗口”都是模拟出来的,你可以在系统的一些视图发现有一个全屏的窗口覆盖在屏幕上,显示多个贴图窗口,其他地方透明。 22 | 23 | > [!NOTE] 24 | > 25 | > macOS 和 Wayland 似乎不支持获取软件外鼠标位置,这导致了贴图无法点击。 26 | > 可以在配置文件中 贴图 强制鼠标穿透 添加快捷键,手动设置贴图是否可点击。这是全屏范围的,如果你想点击其他界面,需要再次使用快捷键 27 | 28 | ### 裁切放大 29 | 30 | 通过滚轮可以缩放窗口大小,但有时候我们需要放大来看清贴图,但一起放大的窗口会遮挡住我们的视线,如何只放大图片而不改变窗口大小呢? 31 | 32 | 按Ctrl+鼠标滚轮,就可以做到。 33 | 34 | ### 变换 35 | 36 | 在设置中添加变换,可通过数字键切换不同的变换效果。 37 | 38 | 变换使用 CSS,你可以在[mdn transform](https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform)或[mdn filter](https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter)了解更多信息。 39 | 40 | 常见变换(可直接复制到设置): 41 | 42 | | 效果 | css | 43 | | -------- | ---------------------------- | 44 | | 水平翻转 | `transform: rotateY(180deg)` | 45 | | 竖直翻转 | `transform: rotateX(180deg)` | 46 | | 灰度 | `filter: grayscale(100%)` | 47 | | 反色 | `filter: invert(100%)` | 48 | 49 | > [!NOTE] 50 | > 51 | > filter 函数可以传入 svg 滤镜,包括柏林噪音、转置滤镜等你可以在互联网上获取更多资料 52 | -------------------------------------------------------------------------------- /docs/use/editor.md: -------------------------------------------------------------------------------- 1 | # 主页面 2 | 3 | 主要是编辑器,也有浏览功能。 4 | 5 | ### 搜索和翻译 6 | 7 | 按下按钮,**eSearch**将对全文进行相应的搜索或翻译操作。如果不想搜索或翻译全文,可以用鼠标选区部分文字,这样可以搜索或翻译部分文字。 8 | 9 | ### 编辑栏 10 | 11 | 选中时,文字旁边弹出一个编辑栏。 12 | 13 | 你可以进行打开链接、搜索、翻译等操作。 14 | 15 | ### 自动删除换行 16 | 17 | [见 OCR 校对](ocr.md#自动删除换行) 18 | 19 | ### 历史记录 20 | 21 | 点击历史记录,可以查看之前在主页面的文字记录。手动编辑后的文字不会自动保存到历史记录,除非你按下保存到历史记录按钮。 22 | 23 | ## 搜索窗口 24 | 25 | 搜索窗口是**eSearch**自带的一个简单的浏览器,在主页面上生成。 26 | 27 | 若设置了浏览器中打开,搜索的链接将在系统默认的浏览器打开,否则,将在搜索窗口打开。 28 | 29 | ### 标签页管理 30 | 31 | 在标签页中打开链接,他可能在新标签页中打开。当标签页过多时,他们不会变短,而是让整个标签页区域可滚动。虽然标签页是横向滚动的,但你仍可以直接用鼠标纵向滚动。 32 | 33 | 标签页可使用关闭按钮、鼠标中键或鼠标右键关闭,关闭后将展示左边的标签页。所有标签页关闭后,搜索窗口将关闭。 34 | 35 | ### 保存到历史记录 36 | 37 | 搜索窗口不支持历史记录。如果你搜索到重要的内容,可以点击添加到历史记录,此时浏览的标签页网址将保存到主要历史记录,可在主页面查看。 38 | 39 | ### 浏览器打开 40 | 41 | 即使你打开了搜索窗口,你仍然可以通过浏览器打开按钮在浏览器打开当前标签页的网址。在设置中勾选是否在浏览器打开后自动关闭标签页。 42 | 43 | ## 图片区 44 | 45 | 显示 OCR 输入的图片。 46 | 47 | 清空后,图片区还可以上传其他图片进行 OCR 识别,支持多张图片。 48 | 49 | 图片区还用于[辅助 OCR 校对](ocr.md#原图校对)。 50 | 51 | 可以在设置里设置 OCR 文字行数超过一定值自动显示图片区。 52 | 53 | ## 拼写检查 54 | 55 | 拼写检查面板可以通过传统的算法检查拼写错误,也可以通过 AI 来检查。可用于 OCR 校对。 56 | 57 | AI 检查需要在设置中添加在线 AI。 58 | 59 | 随着文字的修改,拼写提示列表也会更新。AI 检查需要手动重新检查。 60 | 61 | 鼠标放在列表项目上,编辑区会跳转并高亮可能错误的地方,图片区也跳转到相应的区域并高亮。软件会猜测多种可能的正确拼写,并给出预览,点击应用。如果没有合适的拼写,点击即可跳转到原文,可手动修改。 62 | 63 | 不是所有的建议都是符合原文,如果给出的建议与图片不符,忽略这些建议即可。 64 | 65 | 当然,在拼写检查时,你可以放心编辑文字的任何区域,拼写检查会自动识别。拼写检查可撤回。 66 | 67 | ## 页面模式 68 | 69 | 主页面可以有搜索模式和翻译模式,搜索模式下通过 OCR 等识别的结果自动搜索显示,翻译模式下也同样自动翻译显示。 70 | 71 | 默认是自动模式,单行文字根据英文占比进行搜索或翻译,多行显示编辑器,不进行额外操作。 72 | 73 | 页面模式可以在托盘设置,也可以在[cli](main.md#cli)指定。 74 | 75 | ## 简洁模式 76 | 77 | 在顶栏点击简洁模式按钮,可以隐藏主页面的工具栏,只显示编辑区。 78 | 79 | 进行网页浏览时,只保留顶栏。 80 | 81 | ## 失焦关闭 82 | 83 | 若不勾选顶栏的固定按钮,则**eSearch**将在失去焦点后自动关闭,实现即用即走的效果。 84 | 85 | ## 窗口置顶 86 | 87 | 在顶栏点击置顶按钮,可以将主页面置顶,始终显示在最前面,类似[贴图](ding.md)。 88 | 89 | --- 90 | 91 | 编辑技巧: 92 | 93 | 双击文本,选中词语,三击选中整行。 94 | 95 | 选择一段文字,除了按住鼠标拖动外,还可以按住Shift,点击,即可选择。 96 | 97 | 这些技巧不仅仅适用于编辑器,在其他文本(设置中)和其他软件(浏览器、Word)中也同样适用,可以在此文档界面试试。 98 | 99 | --- 100 | 101 | 高级功能: 102 | 103 | ## 简洁标签管理 104 | 105 | 可设置标签为图标模式,只显示图标,不显示标题和关闭按钮。 106 | 107 | 可设置标签图标为灰度模式,更协调。 108 | -------------------------------------------------------------------------------- /docs/use/long_clip.md: -------------------------------------------------------------------------------- 1 | # 广截屏 2 | 3 | 即滚动截屏。无尽的信息超出的屏幕,因而诞生了滚动,为了捕获他们,也有了广截屏。 4 | 5 | 之所以起名为广截屏,是因为它不仅支持纵向滚动,还支持横向滚动,甚至组合起来,支持万向滚动。 6 | 7 | 点击广截屏按钮,截屏立即开始。你需要通过鼠标滚轮或方向键手动**缓慢**滚动页面。只要周围还有空间,软件可以实时预览滚动拼接效果。点击右下角的红点,结束截屏。此时编辑界面会弹出。 8 | 9 | > [!TIP] 10 | > 11 | > 你可以在按钮右上角选择仅仅纵向滚动(长截屏)还是万向滚动(广截屏),一般普通的页面长截屏就够用了。 12 | > 13 | > 长截屏拼接率更高。 14 | 15 | > [!TIP] 16 | > 17 | > 为了更好地拼接,框选区域不要太小,框选内容尽量独特,重复的元素不要太多。 18 | > 19 | > 滚动速度不要太快,拼接效果会更好。 20 | 21 | > [!TIP] 22 | > 23 | > Shift+🖱 滚轮 可以横向滚动页面。 24 | 25 | > [!NOTE] 26 | > 27 | > 在特定的操作系统或桌面环境下,广截屏可能出现问题,比如 macOS 和 Wayland 无法识别滚动和结束截屏。 28 | > 29 | > 设置里提供了一个选项,可以设置为定时截屏拼接,以及通过快捷键结束截屏。 30 | -------------------------------------------------------------------------------- /docs/use/qa.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | ## 反馈&建议 4 | 5 | 在[github](https://github.com/xushengfeng/eSearch/issues)提交反馈 6 | 7 | ## 兼容性 8 | 9 | 由于 eSearch 框架使用 Electron,将不再支持 Win7、Win8 10 | 11 | 但仍然可以运行在 Win10、MacOS、Linux 等平台上。 12 | 13 | 最后支持 Win7 等的版本为 1.12.4,但支持 OCR 的版本是 1.8.0 14 | 15 | 可以自己[编译 eSearch](../develop/start.md) ,把一些库降级,并且还能使用新版本的功能(当然肯定不完整了) 16 | 17 | 降级的库有: 18 | 19 | ``` 20 | electron -> 22.3.27 21 | onnxruntime-node -> 1.12.0或更低版本 22 | ``` 23 | 24 | 编辑`package.json`文件,找到`dependencies`和`devDependencies`下的`electron`和`onnxruntime-node`,更改版本号,然后运行`pnpm install`命令重新安装依赖。 25 | -------------------------------------------------------------------------------- /docs/use/readme.md: -------------------------------------------------------------------------------- 1 | # 欢迎阅读 eSearch 使用文档 2 | 3 |
4 | Translations 5 | Immersive Translate can help you translate this document into your native language. 6 |
7 | 8 | 你可以进一步了解 eSearch 的功能 9 | 10 | 你现在看到的版本可能是与软件相适应的版本,如果你想看最新的文档版本(包括开发版甚至是正在开发的未发布的版本),请访问[GitHub master 分支](https://github.com/xushengfeng/eSearch/blob/master/docs/use/readme.md)。 11 | 12 | 在这之前,你需要安装 eSearch,如果有运行库按钮提示,请下载并按照提示安装。 13 | 14 | 由于 eSearch 是电脑软件,你还需要了解电脑操作的基本知识,包括鼠标左中右键的点击、快捷键的使用等。 15 | 16 | 在[使用说明(点击打开)](start.md)中,你将了解到 eSearch 的基本使用方法,各个功能的具体用法、提示技巧和高级功能。 17 | 18 | 尽管 eSearch 尽量避免的一些问题并给予提示,但仍有一些[常见问题](qa.md)需要给出来参考。 19 | 20 | 如果遇到操作上的问题,可以在[GitHub Discussions](https://github.com/xushengfeng/eSearch/discussions)进行反馈。如果软件有错误,或者有任何建议,欢迎在[GitHub Issues](https://github.com/xushengfeng/eSearch/issues)中提出。 21 | 22 | > [!TIP] 23 | > 24 | > 如果你很难访问 GitHub,可以使用[Watt Toolkit](https://steampp.net/)进行免费的 GitHub 加速,也可以通过[Gitee](https://gitee.com/xsf-root/eSearch/blob/master/docs/use/readme.md)访问本文档的镜像。 25 | 26 | > [!NOTE] 27 | > 28 | > 本人文字水平有限,文档尚未涵盖所有功能,欢迎大家补充。 29 | -------------------------------------------------------------------------------- /docs/use/record.md: -------------------------------------------------------------------------------- 1 | # 录屏 2 | 3 | ## 常规录屏 4 | 5 | 用于录制长视频,比如会议、游戏等。 6 | 7 | 在弹出的控制面板,可以点击红色按钮开始录制。 8 | 9 | 在控制面板,你可以勾选录音设备(如系统内录或麦克风)来控制音频输入。 10 | 11 | 可以在控制面板开启摄像头。 12 | 13 | 控制面板录制后显示停止录制按钮、录制时间和放弃录制按钮。 14 | 15 | 你可以在录制条上查看录制时间,可以执行停止录制和暂停操作。 16 | 17 | 有时候你不想你的录制环境出镜,这时候可以使用虚拟背景。通过 AI 技术替换背景,你可以选择不显示背景、模糊背景或使用图片和视频替换。当然最好的方式是在一个独立的摄像头录制环境(比如一面白墙),虚拟背景更多是一种娱乐性质的功能。 18 | 19 | 录制结束,可修改首尾时间来裁切录屏。 20 | 21 | 选择保存位置,等待视频处理操作完成。 22 | 23 | ## 超级录屏 24 | 25 | 超级录屏提供了自动运镜功能,可以自动聚焦鼠标所在位置。用于教程、计算机操作演示,不支持录制声音。 26 | 27 | 考虑到性能,默认最大只支持录制 5 分钟,可以在设置中修改。 28 | 29 | 录制结束后,编辑窗口全屏展示。从上往下为播放器、播放器控制器、预览轴、运镜轴、帧轴、导出。 30 | 31 | 播放器可以以帧跳转播放。帧轴也可以跳转帧。 32 | 33 | > [TIP] 34 | > 35 | > 在当前帧下,可使用截屏打开,进行编辑、保存等操作。 36 | > 37 | > 当然,不会影响录屏内容,因为录屏编辑器不是专业的视频编辑器。 38 | 39 | 可导出为 Gif、mp4、webm 等格式。 40 | 41 | ### 运镜轴 42 | 43 | 运镜轴由 4 条轴组成,从上到下分别吗镜头轴、速度轴、事件轴和移除轴。 44 | 45 | 运镜轴按帧均匀显示,而不是时间,所以可能与预览轴播放指示有错位。 46 | 47 | 双击镜头轴空白位置可新建关键点。 48 | 49 | 在速度轴、事件轴和移除轴上拖动,可创建关键区间,可以移动和改变他们的大小。右键删除区间。 50 | 51 | #### 镜头位置 52 | 53 | 点击镜头关键点,可以编辑。 54 | 55 | 拖动框选可改变镜头聚焦位置,通过滚轮缩放可改变大小(变焦)。 56 | 57 | 镜头的改变有过渡时间和过渡动画,可改变过渡完成的帧,可设置过渡的时间。 58 | 59 | 结束后可保存。 60 | 61 | ### 导航 62 | 63 | 点击预览轴、运镜轴顶部、帧轴都可以跳转。 64 | 65 | 点击`<`和`>`按钮可跳转 1 帧,按住Ctrl可跳转 10 帧,按住Shift可跳转 100 帧。 66 | 67 | 点击`<<`和`>>`按钮可跳转 1 秒,按住Ctrl可跳转 10 秒,按住Shift可跳转 60 秒。 68 | 69 | ### 帧轴 70 | 71 | 显示当前暂停帧附近的几个帧预览。在下面提示该帧的时间、对应播放序列的编号(从 0 开始)。如果删除了某些帧,后面的帧还会提示对应未删除前的编号(也就是运镜轴的编号)。 72 | -------------------------------------------------------------------------------- /docs/use/save.md: -------------------------------------------------------------------------------- 1 | # 保存 2 | 3 | 截屏、录屏、贴图支持保存。 4 | 5 | 保存需要你选择保存地址,开启快速保存后,保存到上次保存的位置。 6 | 7 | 截屏可保存为 PNG、JPG、WebP 和 SVG 格式。 8 | 9 | 贴图只能保存为 PNG 格式。 10 | 11 | 录屏可保存为 mp4、webm、gif、mkv、mov、avi、ts、mpeg、flv 等包装器格式。 12 | 13 | SVG 格式是一种可编辑矢量格式,但保存到文件里的截屏是栅格格式,只有图像编辑产生的形状(除了滤镜外)是矢量格式,便于后期修改。 14 | 15 | > [!IMPORTANT] 16 | > 17 | > SVG 时可编辑的,所以会保留图片原始信息,裁切和形状遮挡等可以后期去除,小心不要泄露隐私。 18 | 19 | ## 可通过**eSearch**打开一些图片 20 | 21 | 安装**eSearch**后,可以在文件管理器 PNG、JPG、WebP、SVG 等图片右键选择打开方式中使用**eSearch**打开。请放心,**eSearch** *不会*修改为*默认*图片打开方式,如果遇到了,请反馈错误。 22 | -------------------------------------------------------------------------------- /docs/use/search.md: -------------------------------------------------------------------------------- 1 | # 图片识别 2 | 3 | 提供了 Google、百度、Yandex 的以图搜图功能。 4 | 5 | 提供一个很简单的 AI 交互界面,可以与 AI 大模型进行对话。 6 | -------------------------------------------------------------------------------- /docs/use/setting.md: -------------------------------------------------------------------------------- 1 | # 设置 2 | 3 | 更改设置后,左下角会显示操作历史。如果误触,或者想放弃本次设置修改,你可以通过点击列表项目撤销。 4 | 5 | --- 6 | 7 | 高级功能 8 | 9 | ## 源文件配置 10 | 11 | 设置使用 [JSON](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/JSON) 格式进行配置,你可以在设置里打开源文件进行编辑。关于 JSON,你需要注意英文符号、双引号、逗号的使用。最好使用 VSCode 等编辑器进行编辑,它有语法检查、自动完成等方便功能。错误格式的 JSON 会导致程序恢复默认设置。 12 | 13 | ## 自定义配置目录 14 | 15 | 默认配置存储在`appData`目录下,在不同平台中: 16 | 17 | - Windows: `%AppData%/eSearch` 18 | - macOS: `~/Library/Application Support/eSearch` 19 | - Linux: `~/.config/eSearch` 20 | 21 | 你可以在设置里设置自定义配置目录,这个选项是配置的配置,会自动生成`preload_config`保存在运行目录下。支持相对路径和绝对路径。 22 | 23 | 如果你想要便携版,保存配置到运行目录,可以参考[另一篇文档](main.md#便携版绿色版)。 24 | -------------------------------------------------------------------------------- /docs/use/start.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | 运行**eSearch**。按下 Alt+C 或 ⌥C 即可进行截屏。当然也可以右键点击托盘处的图标,点击“截屏搜索”进行截屏。 4 | 5 | 此时默认**框选全屏**。 6 | 7 | 按下鼠标并拖动,画出框,即可框选你想要的区域。 8 | 9 | 鼠标旁边有一个“工具栏”,你可以点击按钮进行各种操作: 10 | 11 | 文字识别 12 | 13 | [图片识别](search.md) 14 | 15 | 扫描二维码 16 | 17 | 其他应用打开 18 | 19 | 固定在屏幕上 20 | 21 | 录屏 22 | 23 | 广截屏 24 | 25 | 屏幕翻译 26 | 27 | 高级图像编辑 28 | 29 | 复制图片到剪贴板 30 | 31 | 保存 32 | 33 | 按取消截屏搜索 34 | 35 | 进行文字识别、以图搜图 36 | 或扫描二维码后,产生的文字信息将在主页面展示。你可以在主页面进行各种文字编辑,并进行搜索和 翻译。 37 | 38 | 除了工具栏,你还会注意到其他栏,这些可以在截屏查看。 39 | 40 | 应用托盘、后台等说明,请见软件主体。 41 | 42 | 设置 43 | 44 | 点击蓝色超链接,由更详细的使用说明。 45 | -------------------------------------------------------------------------------- /docs/use/super_editor.md: -------------------------------------------------------------------------------- 1 | # 高级图片编辑器 2 | 3 | > [!IMPORTANT] 4 | > 5 | > 该功能目前处于测试阶段,可能存在一些问题,可以在 设置-工具栏 中添加该按钮以使用。 6 | 7 | 高级图片编辑在截屏编辑之后,无法再回退到截屏的编辑,属于最终加工。 8 | 9 | 它在原先图片的基础上向外添加一些装饰。 10 | 11 | ## 圆角 12 | 13 | 为图片添加圆角。 14 | 15 | 外圆角用于整个图片,计算方式为内圆角加上 XY 边距小的那个。 16 | 17 | ## 纯色背景 18 | 19 | 使用 CSS 颜色值,不填写则为透明。 20 | 21 | ## 渐变背景 22 | 23 | 可以使用线性渐变、径向渐变、圆锥渐变。 24 | 25 | 基本参数为起始位置、角度、颜色值。 26 | 27 | 位置仅径向渐变和圆锥渐变支持,使用 0~1 表示比例坐标,左上角为(0,0),右下角为(1,1),中间为(0.5,0.5)。 28 | 29 | 角度仅线性渐变和圆锥渐变支持,使用 0~360 表示角度值。角度均从竖直向上顺时针方向计算(与时钟方向一致)。线性渐变的角度值表示渐变的方向,圆锥渐变的角度值表示起始角度。 30 | 31 | > [!TIP] 32 | > 33 | > 关于这些渐变,你可以查看 [CSS 渐变](https://developer.mozilla.org/zh-CN/docs/Web/CSS/gradient) 了解更多信息。语法上他们并不相同,需要注意。 34 | 35 | ## 图片背景 36 | 37 | 图片显示方式为居中填充。 38 | 39 | ## 配置 40 | 41 | 按+号记住当前配置作为模板预设,之后可以直接从列表中选择应用。 42 | -------------------------------------------------------------------------------- /electron.vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "electron-vite"; 2 | import { ViteImageOptimizer } from "vite-plugin-image-optimizer"; 3 | import * as path from "node:path"; 4 | import { tmpdir } from "node:os"; 5 | 6 | export default defineConfig({ 7 | main: { 8 | build: { 9 | minify: "esbuild", 10 | }, 11 | }, 12 | renderer: { 13 | build: { 14 | rollupOptions: { 15 | input: { 16 | editor: path.resolve(__dirname, "src/renderer/editor.html"), 17 | clip: path.resolve(__dirname, "src/renderer/capture.html"), 18 | setting: path.resolve( 19 | __dirname, 20 | "src/renderer/setting.html", 21 | ), 22 | ding: path.resolve(__dirname, "src/renderer/ding.html"), 23 | recorder: path.resolve( 24 | __dirname, 25 | "src/renderer/recorder.html", 26 | ), 27 | recorderTip: path.resolve( 28 | __dirname, 29 | "src/renderer/recorderTip.html", 30 | ), 31 | browser_bg: path.resolve( 32 | __dirname, 33 | "src/renderer/browser_bg.html", 34 | ), 35 | translator: path.resolve( 36 | __dirname, 37 | "src/renderer/translator.html", 38 | ), 39 | translate: path.resolve( 40 | __dirname, 41 | "src/renderer/translate.html", 42 | ), 43 | photoEditor: path.resolve( 44 | __dirname, 45 | "src/renderer/photoEditor.html", 46 | ), 47 | videoEditor: path.resolve( 48 | __dirname, 49 | "src/renderer/videoEditor.html", 50 | ), 51 | aiVision: path.resolve( 52 | __dirname, 53 | "src/renderer/aiVision.html", 54 | ), 55 | }, 56 | }, 57 | assetsInlineLimit: 0, 58 | minify: "esbuild", 59 | sourcemap: true, 60 | }, 61 | plugins: [ 62 | ViteImageOptimizer({ 63 | cache: true, 64 | cacheLocation: path.join(tmpdir(), "eSearch_build"), 65 | logStats: false, 66 | include: /.*\svg$/, 67 | }), 68 | ], 69 | }, 70 | }); 71 | -------------------------------------------------------------------------------- /electron.vite.config_test.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "electron-vite"; 2 | import { ViteImageOptimizer } from "vite-plugin-image-optimizer"; 3 | import * as path from "node:path"; 4 | import { tmpdir } from "node:os"; 5 | 6 | export default defineConfig({ 7 | main: { 8 | build: { 9 | minify: "esbuild", 10 | }, 11 | }, 12 | renderer: { 13 | build: { 14 | rollupOptions: { 15 | input: { 16 | editor: path.resolve(__dirname, "src/renderer/ui_test.html"), 17 | }, 18 | }, 19 | assetsInlineLimit: 0, 20 | minify: "esbuild", 21 | sourcemap: true, 22 | }, 23 | plugins: [ 24 | ViteImageOptimizer({ 25 | cache: true, 26 | cacheLocation: path.join(tmpdir(), "eSearch_build"), 27 | logStats: false, 28 | }), 29 | ], 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /lib/gtk-open-with: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/lib/gtk-open-with -------------------------------------------------------------------------------- /lib/kde-open-with: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushengfeng/eSearch/c22880078e2e301a04efb5e23db1dba0657323ff/lib/kde-open-with -------------------------------------------------------------------------------- /lib/open_with.ts: -------------------------------------------------------------------------------- 1 | import { renderSendSync } from "./ipc"; 2 | const { exec } = require("node:child_process"); 3 | function open(path: string) { 4 | switch (process.platform) { 5 | case "win32": 6 | exec( 7 | `rundll32.exe C:\\Windows\\system32\\shell32.dll,OpenAs_RunDLL ${path}`, 8 | ); 9 | break; 10 | case "linux": { 11 | const run_path = renderSendSync("runPath", []); 12 | // 判断桌面环境 13 | exec("echo $XDG_SESSION_DESKTOP", (e, desktop) => { 14 | if (desktop === "KDE\n") { 15 | exec(`cd ${run_path}lib && ./kde-open-with ${path}`); 16 | } else { 17 | exec(`cd ${run_path}lib && ./gtk-open-with ${path}`); 18 | } 19 | }); 20 | break; 21 | } 22 | case "darwin": { 23 | const { canceled: c, filePaths: paths } = renderSendSync( 24 | "clip_mac_app", 25 | [], 26 | ); 27 | if (!c) { 28 | const co = `open -a ${paths[0].replace(/ /g, "\\ ")} ${path}`; 29 | exec(co); 30 | } 31 | 32 | break; 33 | } 34 | } 35 | } 36 | export default open; 37 | -------------------------------------------------------------------------------- /lib/readme.md: -------------------------------------------------------------------------------- 1 | # gtk-open-with 2 | 3 | https://github.com/timgott/gtk-open-with 4 | 5 | gtk license: LGPL-2.1 6 | 7 | # kde-open-with 8 | 9 | https://github.com/xushengfeng/kde-open-with 10 | 11 | kde frameworks license:LGPL 12 | 13 | # win_rect.exe 14 | 15 | 用于 Windows 下窗口识别 16 | 17 | https://github.com/xushengfeng/win_rect 18 | 19 | # copy.exe 20 | 21 | 用于 Windows 下的复制(自动搜索和选中搜索需要用到) 22 | 23 | https://github.com/xushengfeng/win_rect -------------------------------------------------------------------------------- /lib/store/renderStore.ts: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require("electron"); 2 | import type { setting } from "../../src/ShareTypes"; // 使用类型映射和条件类型来解析路径 3 | 4 | type Join = K extends string | number 5 | ? P extends string | number 6 | ? `${K}.${P}` 7 | : never 8 | : never; 9 | 10 | type Paths = T extends object 11 | ? { 12 | [K in keyof T]-?: K extends string | number 13 | ? `${K}` | Join> 14 | : never; 15 | }[keyof T] 16 | : never; 17 | 18 | type SettingPath = Paths; 19 | 20 | type GetValue = P extends `${infer K}.${infer Rest}` 21 | ? K extends keyof T 22 | ? GetValue 23 | : never 24 | : P extends keyof T 25 | ? T[P] 26 | : never; 27 | 28 | const store = { 29 | get:

(path: P): GetValue => { 30 | return ipcRenderer.sendSync("store", { type: "get", path }); 31 | }, 32 | set:

( 33 | path: P, 34 | value: GetValue | (unknown & {}), 35 | ): void => { 36 | ipcRenderer.send("store", { type: "set", path, value }); 37 | }, 38 | getAll: () => { 39 | return ipcRenderer.sendSync("store", { type: "getAll" }) as setting; 40 | }, 41 | setAll: (value: setting) => { 42 | ipcRenderer.send("store", { type: "setAll", value }); 43 | }, 44 | }; 45 | 46 | export default store; 47 | 48 | export type { SettingPath, GetValue }; 49 | -------------------------------------------------------------------------------- /lib/store/store.ts: -------------------------------------------------------------------------------- 1 | const { app } = require("electron"); 2 | const fs = require("node:fs") as typeof import("fs"); 3 | const path = require("node:path") as typeof import("path"); 4 | import type { GetValue, SettingPath } from "./renderStore"; 5 | import type { setting } from "../../src/ShareTypes"; 6 | 7 | type data = { 8 | [key: string]: unknown; 9 | }; 10 | 11 | class Store { 12 | private configPath: string; 13 | private data: data | undefined; 14 | 15 | constructor() { 16 | this.configPath = path.join(app.getPath("userData"), "config.json"); 17 | if (!fs.existsSync(this.configPath)) { 18 | this.init(); 19 | } 20 | this.data = this.getStore(); 21 | } 22 | 23 | private init() { 24 | fs.writeFileSync(this.configPath, "{}"); 25 | this.data = {}; 26 | } 27 | 28 | private getStore() { 29 | if (this.data) return this.data; 30 | let str = "{}"; 31 | try { 32 | str = fs.readFileSync(this.configPath).toString() || "{}"; 33 | } catch (error) { 34 | this.init(); 35 | } 36 | return JSON.parse(str) as data; 37 | } 38 | 39 | private setStore(data: data) { 40 | this.data = data; 41 | fs.writeFileSync(this.configPath, JSON.stringify(data, null, 2)); 42 | } 43 | 44 | set

( 45 | keyPath: P, 46 | value: GetValue | (unknown & {}), 47 | ): void { 48 | const store = this.getStore(); 49 | const pathx = keyPath.split("."); 50 | let obj = store; 51 | for (let i = 0; i < pathx.length; i++) { 52 | const p = pathx[i]; 53 | if (i === pathx.length - 1) obj[p] = value; 54 | else { 55 | if (obj[p]?.constructor !== Object) { 56 | if (!Number.isNaN(Number(pathx[i + 1]))) { 57 | obj[p] = []; 58 | } else { 59 | obj[p] = {}; 60 | } 61 | } 62 | // @ts-ignore 63 | obj = obj[p]; 64 | } 65 | } 66 | this.setStore(store); 67 | } 68 | get

(keyPath: P): GetValue { 69 | const store = this.getStore(); 70 | const pathx = keyPath.split("."); 71 | const lastp = pathx.pop() ?? ""; 72 | // @ts-ignore 73 | const lastobj = pathx.reduce((p, c) => { 74 | return p[c] || {}; 75 | }, store); 76 | return lastobj[lastp]; 77 | } 78 | 79 | clear() { 80 | this.init(); 81 | } 82 | 83 | getAll() { 84 | return this.getStore(); 85 | } 86 | setAll(data: data) { 87 | this.setStore(data); 88 | } 89 | } 90 | 91 | export default Store; 92 | -------------------------------------------------------------------------------- /lib/time_format.ts: -------------------------------------------------------------------------------- 1 | function format(_fmt: string, date: Date) { 2 | let fmt = _fmt; 3 | const opt = { 4 | YYYY: date.getFullYear(), 5 | YY: date.getFullYear() % 1000, 6 | MM: String(date.getMonth() + 1).padStart(2, "0"), 7 | M: date.getMonth() + 1, 8 | DD: String(date.getDate()).padStart(2, "0"), 9 | D: date.getDate(), 10 | d: date.getDay(), 11 | HH: String(date.getHours()).padStart(2, "0"), 12 | H: date.getHours(), 13 | hh: String(date.getHours() % 12).padStart(2, "0"), 14 | h: date.getHours() % 12, 15 | mm: String(date.getMinutes()).padStart(2, "0"), 16 | m: date.getMinutes(), 17 | ss: String(date.getSeconds()).padStart(2, "0"), 18 | s: date.getSeconds(), 19 | S: date.getMilliseconds(), 20 | }; 21 | for (const k in opt) { 22 | const ret = new RegExp(`${k}`, "g"); 23 | fmt = fmt.replace(ret, `${String(opt[k])}`); 24 | } 25 | return fmt; 26 | } 27 | export default format; 28 | -------------------------------------------------------------------------------- /lib/translate/ignore.json: -------------------------------------------------------------------------------- 1 | ["×5", "×10"] 2 | -------------------------------------------------------------------------------- /lib/translate/readme.md: -------------------------------------------------------------------------------- 1 | # 翻译 Translate 2 | 3 | 创建 csv,指定存在的语言进行编辑或指定新语言 4 | Create a csv, specify an existing language for editing or specify a new language 5 | 6 | > [!TIP] 7 | > 选项`-a`用于输出全部文字。如果你发现原来的翻译存在不足,需要修改,请使用这一选项。如果你需要跟进翻译,即 eSearch 的某些文字已经修改,但翻译未修改,请不使用这一选项 8 | > 9 | > The option `- a` is used to output all text. If you find that the original translation is inadequate and need to be modified, please use this option. If you need to follow up on the translation, that is, some text in eSearch has been modified, but the translation has not been modified, please do not use this option 10 | 11 | ```shell 12 | node lib/translate/tool.js -l en 13 | # en.csv 14 | # 或 or 15 | node lib/translate/tool.js -l en -a 16 | ``` 17 | 18 | ```csv 19 | "abc","你好世界","" 20 | ... 21 | ``` 22 | 23 | with arg `-e`,output Chinese with English 24 | 25 | ```csv 26 | "abc","你好世界","Hello World","" 27 | ... 28 | ``` 29 | 30 | 编辑 edit 31 | 32 | > [!TIP] 33 | > AI prompt:“以下是 csv 文件,把第二列翻译成 en 并复制到第三列” 34 | 35 | ```csv 36 | "abc","你好世界","Hello World" 37 | ... 38 | ``` 39 | 40 | 保存 save: 41 | 42 | ```shell 43 | node lib/translate/tool.js -i en.csv 44 | ``` 45 | 46 | 只有在 source.json 里定义的文字才能被翻译。如果找不到需要翻译的文字,那可能是我没针对某些页面进行国际化,请在 issue 上提交 bug,指明需要国际化位置 47 | 48 | Only words defined in source.json can be translated. If you can't find the text that needs to be translated, it may be that I have not internationalized some pages. Please submit bug on issue to indicate the location where you need to be internationalized. 49 | 50 | ## 标记翻译进度 51 | 52 | `tool.js`定义了 srcCommit,借助 git 的 diff 功能,来方便地了解需要翻译什么内容。翻译完某个 id 后,将其添加到对应语言的`finishId`,再次输出 csv 时,将忽略他,这样可以专注与未翻译/修改的翻译。如果全部翻译完,请把 srcCommit 的`id`改为 latestSrcId,并清空`finishId` 53 | 54 | `tool.js` defines srcCommit. With the diff feature of git, you can easily understand what needs to be translated. After translating an id, add it to the `finishiId` of the corresponding language, and ignore it when you output csv again, so that you can focus on the untranslated / modified translation. If you have finished the translation, please change the `id` of srcCommit to latestSrcId and clear `finishiId` 55 | 56 | ## 开发者 57 | 58 | 一般地,若 source.json 没有定义语言,在 console 控制台以红色 🟥 字体和背景输出,若未翻译,以蓝色 🟦 字体和背景 59 | 60 | In general, if source.json does not have a defined language, it will be output in red 🟥 font and background on the console, and in blue 🟦 font and background if not translated. 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eSearch", 3 | "version": "14.8.0", 4 | "description": "识屏 · 搜索", 5 | "main": "./out/main/main.js", 6 | "scripts": { 7 | "pack": "npm run build && electron-builder --dir -c electron-builder.config.js", 8 | "dist": "npm run build && electron-builder -p never -c electron-builder.config.js", 9 | "start": "electron-vite --ignoreConfigWarning preview", 10 | "dev": "electron-vite --ignoreConfigWarning dev", 11 | "build": "electron-vite --ignoreConfigWarning build", 12 | "uitest": "electron-vite -c electron.vite.config_test.ts" 13 | }, 14 | "author": { 15 | "name": "xsf", 16 | "email": "xushengfeng_zg@163.com" 17 | }, 18 | "homepage": "https://github.com/xushengfeng/eSearch/", 19 | "license": "GPL-3.0", 20 | "dependencies": { 21 | "@erase2d/fabric": "^1.1.6", 22 | "@techstark/opencv-js": "4.10.0-release.1", 23 | "chroma-js": "^3.1.1", 24 | "diff-match-patch": "^1.0.5", 25 | "diff-match-patch-line-and-word": "^0.1.3", 26 | "dkh-ui": "^0.14.0", 27 | "download": "^8.0.0", 28 | "esearch-ocr": "^8.4.4", 29 | "esearch-seg": "^1.1.4", 30 | "fabric": "^6.4.0", 31 | "franc": "^6.2.0", 32 | "fuse.js": "^7.1.0", 33 | "gifenc": "^1.0.3", 34 | "hex-to-css-filter": "^5.4.0", 35 | "hotkeys-js": "^3.13.7", 36 | "iso-639-3-to-1": "^1.0.0", 37 | "minimist": "^1.2.8", 38 | "mp4-muxer": "^5.1.3", 39 | "node-screenshots": "^0.2.1", 40 | "onnxruntime-node": "^1.21.0", 41 | "qr-scanner-wechat": "^0.1.3", 42 | "remarkable": "^2.0.1", 43 | "sortablejs": "^1.15.2", 44 | "uiohook-napi": "^1.5.4", 45 | "webm-muxer": "^5.0.2", 46 | "xtranslator": "^1.3.2" 47 | }, 48 | "devDependencies": { 49 | "@biomejs/biome": "^1.9.4", 50 | "@types/chroma-js": "^3.1.1", 51 | "@types/diff-match-patch": "^1.0.34", 52 | "@types/minimist": "^1.2.5", 53 | "@types/node": "^22.9.3", 54 | "@types/sortablejs": "^1.15.8", 55 | "archiver": "^7.0.1", 56 | "canvas": "^3.1.0", 57 | "csv-parse": "^5.5.5", 58 | "electron": "^35.0.1", 59 | "electron-builder": "^25.1.8", 60 | "electron-vite": "^2.1.0", 61 | "svgo": "^3.3.2", 62 | "typescript": "^5.7.2", 63 | "vite": "^5.4.12", 64 | "vite-plugin-image-optimizer": "^1.1.8", 65 | "xtimelog": "^1.0.5" 66 | }, 67 | "optionalDependencies": { 68 | "node-screenshots-darwin-arm64": "0.1.9", 69 | "node-screenshots-darwin-x64": "0.1.9", 70 | "node-screenshots-linux-x64-gnu": "0.1.9", 71 | "node-screenshots-linux-x64-musl": "0.1.9", 72 | "node-screenshots-win32-arm64-msvc": "0.1.9", 73 | "node-screenshots-win32-ia32-msvc": "0.1.9", 74 | "node-screenshots-win32-x64-msvc": "0.1.9" 75 | }, 76 | "pnpm": { 77 | "neverBuiltDependencies": [ 78 | "canvas" 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /script/gen_icon_types.ts: -------------------------------------------------------------------------------- 1 | const path = require("node:path") as typeof import("path"); 2 | const fs = require("node:fs") as typeof import("fs"); 3 | 4 | const assetsPath = path.join( 5 | __dirname, 6 | "..", 7 | "src", 8 | "renderer", 9 | "assets", 10 | "icons", 11 | ); 12 | const typesPath = path.join(__dirname, "..", "src", "iconTypes.d.ts"); 13 | 14 | console.log(assetsPath); 15 | 16 | const iconTypes = fs 17 | .readdirSync(assetsPath, { withFileTypes: true }) 18 | .filter((dirent) => !dirent.isDirectory()) 19 | .map((dirent) => dirent.name); 20 | 21 | const content = `export type RawIconType = ${iconTypes.map((name) => `"${name}"`).join(" | ")};\n`; 22 | 23 | const content2 = `export type IconType = ${iconTypes.map((name) => `"${name.replace(/\.svg$/, "")}"`).join(" | ")};\n`; 24 | 25 | fs.writeFileSync(typesPath, `${content}\n${content2}`); 26 | -------------------------------------------------------------------------------- /script/release_fast_download.ts: -------------------------------------------------------------------------------- 1 | const release: Record> = { 2 | win32: { gh: ["exe", "zip"], arch: ["x64", "arm64"] }, 3 | linux: { gh: ["AppImage", "deb", "rpm", "tar.gz"], arch: ["x64", "arm64"] }, 4 | darwin: { gh: ["dmg", "zip"], arch: ["x64", "arm64"] }, 5 | }; 6 | 7 | function getUrl(url: string) { 8 | const version = require("../package.json").version; 9 | let t = "| | Windows | macOS | Linux|\n| --- | --- | --- | --- |\n"; 10 | for (const arch of ["x64", "arm64"]) { 11 | t += `|${arch}| `; 12 | for (const p of ["win32", "darwin", "linux"]) { 13 | if (!release[p].arch.includes(arch)) continue; 14 | t += `${(release[p].gh || []).map((i) => `[${i}](${url.replaceAll("$v", version).replace("$arch", arch).replace("$p", p).replace("$h", i)})`).join(" ")}|`; 15 | } 16 | t += "\n"; 17 | } 18 | return t; 19 | } 20 | 21 | console.log( 22 | "⚡镜像下载:\n", 23 | getUrl( 24 | "https://github.moeyy.xyz/https://github.com/xushengfeng/eSearch/releases/download/$v/eSearch-$v-$p-$arch.$h", 25 | ), 26 | ); 27 | -------------------------------------------------------------------------------- /src/iconTypes.d.ts: -------------------------------------------------------------------------------- 1 | export type RawIconType = "add.svg" | "add_history.svg" | "ai_check.svg" | "arrow.svg" | "back.svg" | "blur.svg" | "brightness.svg" | "browser.svg" | "camera.svg" | "circle.svg" | "clear.svg" | "close.svg" | "concise.svg" | "contrast.svg" | "copy.svg" | "cut.svg" | "delete.svg" | "delete_enter.svg" | "ding.svg" | "down.svg" | "draw.svg" | "draw_select.svg" | "eraser.svg" | "excel.svg" | "file.svg" | "fill_storke.svg" | "filters.svg" | "free_draw.svg" | "free_select.svg" | "handle.svg" | "help.svg" | "hide.svg" | "history.svg" | "hue.svg" | "ignore.svg" | "img.svg" | "last.svg" | "last_last.svg" | "left.svg" | "line.svg" | "link.svg" | "long_clip.svg" | "main.svg" | "mask.svg" | "md.svg" | "mic.svg" | "minimize.svg" | "next.svg" | "next_next.svg" | "number.svg" | "ocr.svg" | "opacity.svg" | "open.svg" | "paste.svg" | "pause.svg" | "pixelate.svg" | "play_pause.svg" | "polygon.svg" | "polyline.svg" | "position.svg" | "position_back.svg" | "position_backwards.svg" | "position_forwards.svg" | "position_front.svg" | "record.svg" | "rect.svg" | "rect_select.svg" | "recume.svg" | "regex.svg" | "reload.svg" | "replace.svg" | "replace_all.svg" | "right.svg" | "saturation.svg" | "save.svg" | "scan.svg" | "screen.svg" | "search.svg" | "select_all.svg" | "setting.svg" | "shapes.svg" | "size.svg" | "space.svg" | "spray.svg" | "star.svg" | "start_record.svg" | "stop_record.svg" | "super_edit.svg" | "text.svg" | "toptop.svg" | "translate.svg" | "up.svg" | "updown.svg"; 2 | 3 | export type IconType = "add" | "add_history" | "ai_check" | "arrow" | "back" | "blur" | "brightness" | "browser" | "camera" | "circle" | "clear" | "close" | "concise" | "contrast" | "copy" | "cut" | "delete" | "delete_enter" | "ding" | "down" | "draw" | "draw_select" | "eraser" | "excel" | "file" | "fill_storke" | "filters" | "free_draw" | "free_select" | "handle" | "help" | "hide" | "history" | "hue" | "ignore" | "img" | "last" | "last_last" | "left" | "line" | "link" | "long_clip" | "main" | "mask" | "md" | "mic" | "minimize" | "next" | "next_next" | "number" | "ocr" | "opacity" | "open" | "paste" | "pause" | "pixelate" | "play_pause" | "polygon" | "polyline" | "position" | "position_back" | "position_backwards" | "position_forwards" | "position_front" | "record" | "rect" | "rect_select" | "recume" | "regex" | "reload" | "replace" | "replace_all" | "right" | "saturation" | "save" | "scan" | "screen" | "search" | "select_all" | "setting" | "shapes" | "size" | "space" | "spray" | "star" | "start_record" | "stop_record" | "super_edit" | "text" | "toptop" | "translate" | "up" | "updown"; 4 | -------------------------------------------------------------------------------- /src/renderer/aiVision.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/renderer/assets/1temple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 75 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 83 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 71 | 75 | 79 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 86 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/blur.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 78 | 82 | 83 | 84 | 88 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 71 | 75 | 80 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 83 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/concise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 83 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/contrast.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 71 | 75 | 81 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/ding.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 83 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 83 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/draw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/draw_select.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/eraser.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 87 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 71 | 75 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/filters.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 85 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/free_select.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 79 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/hue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 71 | 75 | 80 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/last.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 48 | 49 | 51 | 62 | 73 | 74 | 78 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/last_last.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 48 | 49 | 51 | 62 | 73 | 74 | 78 | 83 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 83 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/long_clip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 71 | 75 | 80 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/main.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/minimize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 48 | 49 | 51 | 62 | 73 | 74 | 78 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/next_next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 48 | 49 | 51 | 62 | 73 | 74 | 78 | 83 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/paste.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 81 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 71 | 75 | 79 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/play_pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 48 | 49 | 51 | 62 | 73 | 74 | 78 | 82 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/polygon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/polyline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/record.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 71 | 75 | 81 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/rect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/rect_select.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/recume.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 45 | 46 | 48 | 59 | 70 | 71 | 75 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 83 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 80 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/setting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 81 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/size.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 78 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/space.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/start_record.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 48 | 49 | 51 | 62 | 73 | 74 | 78 | 86 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/stop_record.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 48 | 49 | 51 | 62 | 73 | 74 | 78 | 86 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 38 | 44 | 45 | 47 | 58 | 69 | 70 | 74 | 78 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/toptop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 83 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 39 | 49 | 50 | 52 | 63 | 74 | 75 | 79 | 83 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/renderer/assets/sample_picture.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/browser_bg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/browser_bg/browser_bg.ts: -------------------------------------------------------------------------------- 1 | import { ele, initDKH, p, view } from "dkh-ui"; 2 | import { t } from "../../../lib/translate/translate"; 3 | 4 | initDKH({ pureStyle: true }); 5 | 6 | const pEl = view("y") 7 | .style({ 8 | width: "100vw", 9 | height: "100vh", 10 | alignItems: "center", 11 | justifyContent: "center", 12 | }) 13 | .addInto(); 14 | const h1 = ele("h1") 15 | .addInto(pEl) 16 | .bindSet((v: string, el) => { 17 | el.innerText = t(v); 18 | }); 19 | const details = view().addInto(pEl); 20 | 21 | const search = new URLSearchParams(location.search); 22 | 23 | if (navigator.onLine) { 24 | switch (search.get("type")) { 25 | case "did-fail-load": 26 | h1.sv("加载失败"); 27 | details 28 | .clear() 29 | .add([ 30 | p(`${t("错误代码:")}${search.get("err_code")}`), 31 | p(`${t("错误描述:")}${search.get("err_des")}`), 32 | ]); 33 | break; 34 | case "render-process-gone": 35 | h1.sv("进程崩溃"); 36 | break; 37 | case "unresponsive": 38 | h1.sv("页面未响应"); 39 | break; 40 | case "certificate-error": 41 | h1.sv("证书错误"); 42 | break; 43 | default: 44 | break; 45 | } 46 | } else { 47 | h1.sv("无网络连接"); 48 | } 49 | -------------------------------------------------------------------------------- /src/renderer/capture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/renderer/css/ding.css: -------------------------------------------------------------------------------- 1 | body { 2 | --hover-color: var(--bar-hover-color) !important; 3 | } 4 | ::-webkit-scrollbar { 5 | display: none; 6 | } 7 | html, 8 | body { 9 | margin: 0; 10 | width: 100%; 11 | height: 100%; 12 | } 13 | #photo { 14 | width: 100%; 15 | height: 100%; 16 | } 17 | .ding_photo { 18 | position: fixed; 19 | overflow: hidden; 20 | &:focus { 21 | outline: none; 22 | } 23 | } 24 | .ding_photo:hover, 25 | .ding_photo_h { 26 | box-shadow: var(--shadow); 27 | } 28 | #tool_bar { 29 | overflow: hidden; 30 | position: absolute; 31 | width: 100%; 32 | z-index: 2; 33 | } 34 | #tool_bar_c { 35 | display: none; 36 | align-items: center; 37 | backdrop-filter: var(--blur); 38 | background: var(--bar-bg); 39 | width: 100%; 40 | transform: translateY(-105%); 41 | transition: var(--transition); 42 | user-select: none; 43 | } 44 | #tool_bar_c > div { 45 | display: flex; 46 | align-items: center; 47 | width: auto; 48 | } 49 | .img { 50 | position: absolute; 51 | width: 100%; 52 | user-select: none; 53 | } 54 | 55 | #b { 56 | border-radius: var(--border-radius); 57 | overflow: hidden; 58 | transition: var(--transition); 59 | } 60 | #b:hover { 61 | box-shadow: var(--shadow); 62 | } 63 | #b > button { 64 | transition: var(--transition); 65 | background-color: transparent; 66 | padding: 0; 67 | width: 32px; 68 | height: 32px; 69 | } 70 | #b > button > .icon { 71 | width: 32px; 72 | } 73 | 74 | .minimize { 75 | opacity: 0 !important; 76 | pointer-events: none !important; 77 | } 78 | 79 | #dock { 80 | position: fixed; 81 | height: 50px; 82 | width: 10px; 83 | left: 0; 84 | top: 0; 85 | transition: var(--transition); 86 | z-index: 2; 87 | } 88 | 89 | .dock { 90 | width: 200px !important; 91 | height: 100% !important; 92 | top: 0 !important; 93 | border-radius: 0 !important; 94 | } 95 | 96 | .dock_left { 97 | left: 0 !important; 98 | } 99 | 100 | .dock_right { 101 | left: calc(100vw - 200px) !important; 102 | } 103 | 104 | #dock > div { 105 | overflow: hidden; 106 | width: 100%; 107 | display: none; 108 | } 109 | #dock img { 110 | width: 100%; 111 | } 112 | .i_bar { 113 | position: absolute; 114 | right: 8px; 115 | border-radius: var(--o-padding); 116 | } 117 | -------------------------------------------------------------------------------- /src/renderer/css/recorder.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | #m { 6 | overflow: hidden; 7 | display: flex; 8 | flex-direction: column; 9 | align-items: center; 10 | border-radius: var(--border-radius); 11 | justify-content: space-between; 12 | height: 100vh; 13 | transition: var(--transition); 14 | } 15 | 16 | #video { 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | width: 100%; 21 | height: 100%; 22 | border-radius: var(--border-radius); 23 | overflow: hidden; 24 | } 25 | #v_p { 26 | border-radius: var(--border-radius); 27 | overflow: hidden; 28 | } 29 | video { 30 | border-radius: var(--border-radius); 31 | position: relative; 32 | } 33 | 34 | #s { 35 | height: 0; 36 | } 37 | .s_show { 38 | padding: 8px; 39 | height: auto !important; 40 | } 41 | 42 | #jdt { 43 | -webkit-appearance: none; 44 | width: 100%; 45 | border-radius: 2px; 46 | background: var(--hover-color); 47 | margin: 0; 48 | } 49 | 50 | #jdt::-webkit-slider-thumb { 51 | -webkit-appearance: none; 52 | background-color: var(--font-color); 53 | border-radius: 2px; 54 | height: 16px; 55 | width: 4px; 56 | } 57 | input { 58 | vertical-align: middle; 59 | font-family: var(--main-font); 60 | } 61 | input[type="number"] { 62 | width: 16px; 63 | padding: 0; 64 | } 65 | 66 | #t_nt, 67 | #t_t { 68 | font-family: var(--monospace); 69 | } 70 | 71 | #t_start, 72 | #t_end { 73 | width: 100px; 74 | } 75 | 76 | select { 77 | border: none; 78 | background-color: transparent; 79 | font-size: 1em; 80 | } 81 | 82 | .pro_p { 83 | width: 100%; 84 | height: 2rem; 85 | line-height: 2rem; 86 | border-radius: var(--border-radius); 87 | overflow: hidden; 88 | background: var(--hover-color); 89 | margin-top: 4px; 90 | } 91 | .pro { 92 | width: 0%; 93 | height: 100%; 94 | transition: var(--transition); 95 | border-radius: var(--border-radius); 96 | white-space: nowrap; 97 | } 98 | .pro_running { 99 | background-color: #9999ff; 100 | } 101 | .pro_ok { 102 | background-color: #99ff99; 103 | } 104 | .pro_error { 105 | background-color: #ff9999; 106 | } 107 | 108 | .hide_log { 109 | display: none; 110 | } 111 | 112 | textarea { 113 | width: 100%; 114 | min-height: 2lh; 115 | } 116 | -------------------------------------------------------------------------------- /src/renderer/css/recorderTip.css: -------------------------------------------------------------------------------- 1 | body { 2 | user-select: none; 3 | } 4 | 5 | #recorder_rect { 6 | border: 1px dashed #000; 7 | box-sizing: border-box; 8 | width: 100vw; 9 | height: 100vh; 10 | } 11 | 12 | #mouse_c { 13 | display: none; 14 | position: fixed; 15 | width: 24px; 16 | height: 24px; 17 | transform: translate(-50%, -50%); 18 | border-radius: 50%; 19 | background-color: #ff08; 20 | overflow: hidden; 21 | } 22 | 23 | #recorder_bar { 24 | position: absolute; 25 | } 26 | 27 | #recorder_key { 28 | display: flex; 29 | gap: 4px; 30 | } 31 | 32 | #recorder_key > div { 33 | display: flex; 34 | transition: var(--transition); 35 | } 36 | #recorder_key kbd { 37 | transition: var(--transition); 38 | display: flex; 39 | flex-direction: column-reverse; 40 | min-width: 2em; 41 | height: 2em; 42 | border-radius: 0.25em; 43 | justify-content: space-between; 44 | } 45 | 46 | .only_key { 47 | font-size: 1em; 48 | font-weight: bold; 49 | align-items: center; 50 | justify-content: center !important; 51 | } 52 | .main_key { 53 | font-size: 1em; 54 | font-weight: bold; 55 | height: 60%; 56 | } 57 | .top_key { 58 | font-size: 0.75em; 59 | height: 30%; 60 | line-height: 100%; 61 | } 62 | .right_key { 63 | align-items: end; 64 | } 65 | 66 | #recorder_key .key_hidden { 67 | opacity: 0.5; 68 | } 69 | 70 | #mouse_c > div:nth-child(1) { 71 | width: 10px; 72 | height: 100%; 73 | } 74 | #mouse_c > div:nth-child(2) { 75 | width: 4px; 76 | height: 100%; 77 | } 78 | #mouse_c > div:nth-child(3) { 79 | width: 10px; 80 | height: 100%; 81 | } 82 | 83 | .wheel_u { 84 | clip-path: polygon(50% 0, 0% 100%, 100% 100%); 85 | } 86 | .wheel_d { 87 | clip-path: polygon(50% 100%, 0% 0%, 100% 0%); 88 | } 89 | .wheel_l { 90 | clip-path: polygon(0 50%, 100% 0%, 100% 100%); 91 | } 92 | .wheel_r { 93 | clip-path: polygon(100% 50%, 0% 0%, 0% 100%); 94 | } 95 | -------------------------------------------------------------------------------- /src/renderer/ding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/renderer/editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/renderer/lib/removeObj.ts: -------------------------------------------------------------------------------- 1 | type ort = typeof import("onnxruntime-node"); 2 | 3 | async function removeObj(op: { 4 | ort: ort; 5 | session: Awaited>; 6 | img: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement; 7 | mask: ImageData; 8 | }) { 9 | const w = 10 | op.img instanceof HTMLImageElement ? op.img.naturalWidth : op.img.width; 11 | const h = 12 | op.img instanceof HTMLImageElement 13 | ? op.img.naturalHeight 14 | : op.img.height; 15 | const imputImg = new OffscreenCanvas(w, h).getContext("2d"); 16 | if (!imputImg) throw new Error("imputImg is null"); 17 | imputImg.drawImage(op.img, 0, 0); 18 | const imgData = imputImg.getImageData(0, 0, w, h); 19 | 20 | const ort = op.ort; 21 | 22 | const maskOrt = op.session; 23 | const r = new Uint8Array(w * h); 24 | const g = new Uint8Array(w * h); 25 | const b = new Uint8Array(w * h); 26 | for (let i = 0; i < w * h; i++) { 27 | r[i] = imgData.data[4 * i]; 28 | g[i] = imgData.data[4 * i + 1]; 29 | b[i] = imgData.data[4 * i + 2]; 30 | } 31 | const input = new ort.Tensor("uint8", [...r, ...g, ...b], [1, 3, h, w]); 32 | const m = new Uint8Array(w * h); 33 | for (let i = 0; i < w * h; i++) { 34 | m[i] = op.mask.data[4 * i]; 35 | } 36 | const maskInput = new ort.Tensor("uint8", m, [1, 1, h, w]); 37 | const output = await maskOrt.run({ 38 | [maskOrt.inputNames[0]]: input, 39 | [maskOrt.inputNames[1]]: maskInput, 40 | }); 41 | console.log(output); 42 | const outputData0 = output[maskOrt.outputNames[0]].data as Uint8Array; 43 | const outputData = new ImageData(w, h); 44 | const wh = w * h; 45 | for (let i = 0; i < w * h; i++) { 46 | outputData.data[4 * i] = outputData0[i]; 47 | outputData.data[4 * i + 1] = outputData0[i + wh]; 48 | outputData.data[4 * i + 2] = outputData0[i + wh * 2]; 49 | outputData.data[4 * i + 3] = 255; 50 | } 51 | return outputData; 52 | } 53 | 54 | export default removeObj; 55 | -------------------------------------------------------------------------------- /src/renderer/photoEditor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/recorder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/renderer/recorderTip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/renderer/setting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/translate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/renderer/translator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/ui/ui.ts: -------------------------------------------------------------------------------- 1 | import { button, ele, image, initDKH, input, pack, select, view } from "dkh-ui"; 2 | import { Class, getImgUrl } from "../root/root"; 3 | import type { IconType } from "../../iconTypes"; 4 | 5 | console.log("hi"); 6 | 7 | function iconBEl(src: IconType) { 8 | return button().add(image(getImgUrl(`${src}.svg`), "icon").class("icon")); 9 | } 10 | 11 | initDKH({ pureStyle: true }); 12 | 13 | pack(document.body).style({ background: "var(--bg)" }); 14 | 15 | const p = view("x").style({ gap: "8px", padding: "8px" }).addInto(); 16 | p.add([ 17 | button("文字按钮"), 18 | iconBEl("translate"), 19 | select([{ value: "1" }]), 20 | input().sv("hi"), 21 | input("number"), 22 | input("checkbox"), 23 | view() 24 | .class(Class.group) 25 | .add([button("hi"), select([])]), 26 | ]); 27 | 28 | ele("textarea").addInto(); 29 | 30 | const p2 = view("x") 31 | .style({ background: "linear-gradient(blue, pink)" }) 32 | .addInto(); 33 | const bar = view().class(Class.glassBar).addInto(p2); 34 | bar.add([iconBEl("translate")]); 35 | -------------------------------------------------------------------------------- /src/renderer/ui_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eSearch - 高级编辑 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/videoEditor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/ui/clip.ts: -------------------------------------------------------------------------------- 1 | import { initConfig, restoreConfig, runApp, sleep } from "./init.ts"; 2 | 3 | initConfig(); 4 | 5 | const app = runApp(); 6 | 7 | await sleep(10000); 8 | 9 | await sleep(10000); 10 | 11 | app.kill(); 12 | app.kill("SIGKILL"); 13 | 14 | console.log("stop app"); 15 | 16 | restoreConfig(); 17 | 18 | process.exit(0); 19 | -------------------------------------------------------------------------------- /test/ui/init.ts: -------------------------------------------------------------------------------- 1 | import { exec, execSync } from "node:child_process"; 2 | import { renameSync, rmSync } from "node:fs"; 3 | 4 | const appPath = 5 | process.platform === "linux" 6 | ? `${process.env.HOME}/.config/eSearch` 7 | : process.platform === "win32" 8 | ? `${process.env.APPDATA}/eSearch` 9 | : process.platform === "darwin" 10 | ? `${process.env.HOME}/Library/Application Support/eSearch` 11 | : ""; 12 | const configPath = `${appPath}/config.json`; 13 | const backupPath = `${appPath}/config.json.bak`; 14 | 15 | function runApp() { 16 | return exec("pnpm run start"); 17 | } 18 | 19 | function initConfig() { 20 | renameSync(configPath, backupPath); 21 | } 22 | 23 | function restoreConfig() { 24 | try { 25 | rmSync(configPath); 26 | } catch (error) {} 27 | renameSync(backupPath, configPath); 28 | } 29 | 30 | function sleep(ms: number) { 31 | return new Promise((resolve) => setTimeout(resolve, ms)); 32 | } 33 | 34 | export { runApp, initConfig, restoreConfig, sleep }; 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.web.json" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "sourceMap": false, 6 | "strict": true, 7 | "jsx": "preserve", 8 | "esModuleInterop": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "skipLibCheck": true, 14 | "noImplicitAny": false, 15 | "noImplicitReturns": true, 16 | "composite": true, 17 | "types": [ 18 | "node" 19 | ] 20 | }, 21 | "include": [ 22 | "electron.vite.config.*", 23 | "src/main/*", 24 | "src/preload/*", 25 | "lib/**/*.ts", 26 | "src/renderer/screenShot/*" 27 | ], 28 | } -------------------------------------------------------------------------------- /tsconfig.web.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "sourceMap": false, 6 | "strict": true, 7 | "jsx": "preserve", 8 | "esModuleInterop": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "skipLibCheck": true, 14 | "noImplicitAny": false, 15 | "noImplicitReturns": true, 16 | "composite": true, 17 | "lib": [ 18 | "ESNext", 19 | "DOM", 20 | "DOM.Iterable" 21 | ], 22 | }, 23 | "include": [ 24 | "src/renderer/**/*.ts", 25 | "src/preload/*.d.ts", 26 | "lib/**/*.ts", 27 | ] 28 | } --------------------------------------------------------------------------------