Commit 52c2df09 by chenwl

完成v1.0.1版本

parents
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
\ No newline at end of file
node_modules
dist
out
.gitignore
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
globals: {
rs: true,
RSEvent: true,
RSData: true
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'@electron-toolkit',
'@vue/eslint-config-prettier'
],
rules: {
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off'
// 'no-undef': 'off'
}
}
node_modules
dist
out
.DS_Store
*.log*
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
singleQuote: true
semi: false
printWidth: 100
trailingComma: none
# 千寻智能AI助手
## 技术集合
> electron [官网直达](https://www.electronjs.org/)
> electron-vite [官网直达](https://cn.electron-vite.org/guide/)
> electron-builder [官网直达](https://www.electron.build/)
> vue3 [官网直达](https://cn.vuejs.org/)
> node-gyp [官网直达](https://github.com/nodejs/node-gyp)
## 环境
1. 安装 `node` 版本 管理器 `nvm`,最新版本就可以
2. 安装后执行 `nvm ls` 查看当前 `node` 版本列表
3. `nvm install 20` 安装 `node 20` 版本
4. `nvm use 20` 切换到 `node 20` 版本
5. `node -v` 查看 `node` 版本
6. 安装 `python 3.9.6` 版本
7. 安装 `npm` 源管理器 `nrm`, `npm install -g nrm`, `nrm ls` 查看当前 `npm` 源列表
8. 切换到 `tencent``nrm use tencent`
9. 安装 `node-gyp 10.1.0` 版本 `npm install -g node-gyp@10.1.0`
10. 进入 `/extend/electron-screenshots/react-screenshots`, 执行 `npm ci`
11. 进入 `/extend/electron-screenshots`, 执行 `npm ci`
12. 进入根目录,执行 `npm ci`, 安装依赖包
13. 执行 `npm run dev` 启动项目
14. 执行 `npm run build:mac` 执行编译、打包、签名项目
## 工程目录
```js
ai-assistant
├── README.md
├── build //编译配置目录,一般不需要更改
├── entitlements.mac.plist
├── icon.icns
├── icon.ico
├── icon.png
├── icons
├── mac
├── png
└── win
├── logo.ico
└── logo.png
├── dev-app-update.yml
├── electron-builder.yml
├── electron.vite.config.mjs // 工程配置文件,参考 electron-vite
├── extend //扩展组件,截图相关
├── electron-screenshots
├── README.md
├── lib
├── package-lock.json
├── package.json
└── react-screenshots
├── package-lock.json
├── package.json
├── resources //静态资源目录,一般用在主线程
├── icon.png
├── lib
├── mac
└── win
├── logo.ico
├── logo.png
├── tray-w.ico
└── tray32x32@2x.png
└── src // 源码目录
├── dataCenter // 数据中心操作库,在主线程、渲染线程都可使用
└── RSData
├── eventCenter // 事件中心操作库, 在主线程、渲染线程都可使用
├── RSEvent
└── constant // 事件名称定义文件,所有事件名称都在此定义
├── main // 主线程代码目录
├── index.js // 主线程入口文件
├── init // 一些初始化使用的功能类
├── lib // 一些工具实现类,功能扩展实现
├── page // 定义窗口相关,跟渲染进程 vue-router 对应
├── raisoundRecordSdk // ASR相关
├── router.js // 注册page
└── tools // 一些工具方法
├── preload // 预渲染文件,在渲染进程使用
├── index.js
├── useAppMethods.js
├── useFFIMethods.js
└── usePageMethods.js
└── renderer // 渲染进程,开发跟web类似,使用 vue3
├── index.html
└── src
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.device.usb</key>
<true/>
</dict>
</plist>
File added
File added
File added
provider: generic
url: https://example.com/auto-updates
updaterCacheDirName: ai-mouse-updater
appId: com.raisound.QxznAI-Hub
productName: 千寻智能AI助手
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
asarUnpack:
- resources/**
win:
icon: build/logo.ico
executableName: 威鼠
squirrelWindows:
iconUrl: build/logo.ico
nsis:
artifactName: VMouse-${version}-setup.${ext}
shortcutName: 威鼠
uninstallDisplayName: VMouse
# 安装图标
installerIcon: build/logo.ico
# 卸载图标
uninstallerIcon: build/logo.ico
# 创建桌面快捷方式
createDesktopShortcut: true
# 创建开始菜单快捷方式
createStartMenuShortcut: true
# 是否创建一键安装程序或辅助
oneClick: false
# 是否允许用户更改安装目录
allowToChangeInstallationDirectory: true
# 不显示安装模式页面 默认为使用这台电脑的任何人安装(所有用户)
perMachine: true
mac:
icon: build/icons/mac/icon.icns
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: 授权访问摄像头
- NSMicrophoneUsageDescription: 授权访问麦克风
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
dmg:
artifactName: QxznAI-Hub-${version}.${ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: VMouse.raisound.com
category: Utility
icon: build/logo.png
appImage:
artifactName: ${productName}-${version}.${ext}
npmRebuild: false
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin({ exclude: ['vue-cli-plugin-electron-builder'] })],
build: {
rollupOptions: {
external: ['electron-screenshots', 'electron-edge-js']
}
},
resolve: {
alias: {
'@main': resolve('src/main'),
'@resources': resolve('resources')
}
}
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {
resolve: {
alias: {
'@renderer': resolve('src/renderer/src')
}
},
plugins: [vue()]
}
})
# electron-screenshots
> electron 截图插件
## Prerequisites
- electron >= 11
## Install
[![NPM](https://nodei.co/npm/electron-screenshots.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/electron-screenshots/)
## Usage
```ts
import debug from "electron-debug";
import { app, globalShortcut } from "electron";
import Screenshots from "./screenshots";
app.whenReady().then(() => {
const screenshots = new Screenshots();
globalShortcut.register("ctrl+shift+a", () => {
screenshots.startCapture();
screenshots.$view.webContents.openDevTools();
});
// 点击确定按钮回调事件
screenshots.on("ok", (e, buffer, bounds) => {
console.log("capture", buffer, bounds);
});
// 点击取消按钮回调事件
screenshots.on("cancel", () => {
console.log("capture", "cancel1");
});
screenshots.on("cancel", (e) => {
// 执行了preventDefault
// 点击取消不会关闭截图窗口
e.preventDefault();
console.log("capture", "cancel2");
});
// 点击保存按钮回调事件
screenshots.on("save", (e, buffer, bounds) => {
console.log("capture", buffer, bounds);
});
// 保存后的回调事件
screenshots.on("afterSave", (e, buffer, bounds, isSaved) => {
console.log("capture", buffer, bounds);
console.log("isSaved", isSaved) // 是否保存成功
});
debug({ showDevTools: true, devToolsMode: "undocked" });
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
```
### 注意
- 如果使用了 webpack 打包主进程,请在主进程 webpack 配置中修改如下配置,否则可能会出现不能调用截图窗口的情况
```js
{
externals: {
'electron-screenshots': 'require("electron-screenshots")'
}
}
```
- `vue-cli-plugin-electron-builder`配置示例[vue-cli-plugin-electron-builder-issue](https://github.com/nashaofu/vue-cli-plugin-electron-builder-issue/blob/0f774a90b09e10b02f86fcb6b50645058fe1a4e8/vue.config.js#L1-L8)
```js
// vue.config.js
module.exports = {
publicPath: ".",
pluginOptions: {
electronBuilder: {
// 不打包,使用 require 加载
externals: ["electron-screenshots"],
},
},
};
```
- esc 取消截图,可用以下代码实现按 esc 取消截图
```js
globalShortcut.register("esc", () => {
if (screenshots.$win?.isFocused()) {
screenshots.endCapture();
}
});
```
- 加速截图界面展示,不销毁`BrowserWindow`,减少创建窗口的开销,可用以下代码实现。**需注意,启用该功能,会导致`window-all-closed`事件不触发,因此需要手动关闭截图窗口**
```js
// 是否复用截图窗口,加快截图窗口显示,默认值为 false
// 如果设置为 true 则会在第一次调用截图窗口时创建,后续调用时直接使用
// 且由于窗口不会 close,所以不会触发 app 的 `window-all-closed` 事件
const screenshots = new Screenshots({
singleWindow: true,
});
```
## Methods
- `Debugger`类型产考[debug](https://github.com/debug-js/debug)中的`Debugger`类型
```ts
export type LoggerFn = (...args: unknown[]) => void;
export type Logger = Debugger | LoggerFn;
export interface Lang {
magnifier_position_label?: string;
operation_ok_title?: string;
operation_cancel_title?: string;
operation_save_title?: string;
operation_redo_title?: string;
operation_undo_title?: string;
operation_mosaic_title?: string;
operation_text_title?: string;
operation_brush_title?: string;
operation_arrow_title?: string;
operation_ellipse_title?: string;
operation_rectangle_title?: string;
}
export interface ScreenshotsOpts {
lang?: Lang;
// 调用日志,默认值为 debug('electron-screenshots')
// debug https://www.npmjs.com/package/debug
logger?: Logger;
// 是否复用截图窗口,加快截图窗口显示,默认值为 false
// 如果设置为 true 则会在第一次调用截图窗口时创建,后续调用时直接使用
// 且由于窗口不会 close,所以不会触发 app 的 `window-all-closed` 事件
singleWindow?: boolean;
}
```
| 名称 | 说明 | 返回值 |
| ------------------------------------------------- | ---------------- | ------ |
| `constructor(opts: ScreenshotsOpts): Screenshots` | 调用截图方法截图 | - |
| `startCapture(): Promise<void>` | 调用截图方法截图 | - |
| `endCapture(): Promise<void>` | 手动结束截图 | - |
| `setLang(lang: Lang): Promise<void>` | 修改语言 | - |
## Events
- 数据类型
```ts
interface Bounds {
x: number;
y: number;
width: number;
height: number;
}
export interface Display {
id: number;
x: number;
y: number;
width: number;
height: number;
}
export interface ScreenshotsData {
bounds: Bounds;
display: Display;
}
class Event {
public defaultPrevented = false;
public preventDefault(): void {
this.defaultPrevented = true;
}
}
```
| 名称 | 说明 | 回调参数 |
| ------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------- |
| ok | 截图确认事件 | `(event: Event, buffer: Buffer, data: ScreenshotsData) => void` |
| cancel | 截图取消事件 | `(event: Event) => void` |
| save | 截图保存事件 | `(event: Event, buffer: Buffer, data: ScreenshotsData) => void` |
| afterSave | 截图保存(取消保存)后的事件 | `(event: Event, buffer: Buffer, data: ScreenshotsData, isSaved: boolean) => void` |
| windowCreated | 截图窗口被创建后触发 | `($win: BrowserWindow) => void` |
| windowClosed | 截图窗口被关闭后触发,对`BrowserWindow` `closed` 事件的转发 | `($win: BrowserWindow) => void` |
### 说明
- event: 事件对象
- buffer: png 图片 buffer
- bounds: 截图区域信息
- display: 截图的屏幕
- `event`对象可调用`preventDefault`方法来阻止默认事件,例如阻止默认保存事件
```ts
const screenshots = new Screenshots({
lang: {
magnifier_position_label: "Position",
operation_ok_title: "Ok",
operation_cancel_title: "Cancel",
operation_save_title: "Save",
operation_redo_title: "Redo",
operation_undo_title: "Undo",
operation_mosaic_title: "Mosaic",
operation_text_title: "Text",
operation_brush_title: "Brush",
operation_arrow_title: "Arrow",
operation_ellipse_title: "Ellipse",
operation_rectangle_title: "Rectangle",
},
});
screenshots.on("save", (e, buffer, data) => {
// 阻止插件自带的保存功能
// 用户自己控制保存功能
e.preventDefault();
// 用户可在这里自己定义保存功能
console.log("capture", buffer, data);
});
screenshots.startCapture();
```
## Screenshot
![screenshot](../../screenshot.jpg)
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable no-console */
var electron_1 = require("electron");
var _1 = __importDefault(require("."));
electron_1.app.whenReady().then(function () {
var screenshots = new _1.default({
lang: {
operation_rectangle_title: '矩形2323',
},
singleWindow: true,
});
screenshots.$view.webContents.openDevTools();
electron_1.globalShortcut.register('ctrl+shift+c', function () {
screenshots.startCapture();
});
screenshots.on('windowCreated', function ($win) {
$win.on('focus', function () {
electron_1.globalShortcut.register('esc', function () {
if ($win === null || $win === void 0 ? void 0 : $win.isFocused()) {
screenshots.endCapture();
}
});
});
$win.on('blur', function () {
electron_1.globalShortcut.unregister('esc');
});
});
// 防止不能关闭截图界面
electron_1.globalShortcut.register('ctrl+shift+q', function () {
electron_1.app.quit();
});
// 点击确定按钮回调事件
screenshots.on('ok', function (e, buffer, bounds) {
console.log('capture', buffer, bounds);
});
// 点击取消按钮回调事件
screenshots.on('cancel', function () {
console.log('capture', 'cancel1');
screenshots.setLang({
operation_ellipse_title: 'ellipse',
operation_rectangle_title: 'rectangle',
});
});
// 点击保存按钮回调事件
screenshots.on('save', function (e, buffer, bounds) {
console.log('capture', buffer, bounds);
});
var mainWin = new electron_1.BrowserWindow({
show: true,
});
mainWin.removeMenu();
mainWin.loadURL('https://github.com/nashaofu');
});
electron_1.app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
electron_1.app.quit();
}
});
export default class Event {
defaultPrevented: boolean;
preventDefault(): void;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Event = /** @class */ (function () {
function Event() {
this.defaultPrevented = false;
}
Event.prototype.preventDefault = function () {
this.defaultPrevented = true;
};
return Event;
}());
exports.default = Event;
import { Rectangle } from 'electron';
export interface Display extends Rectangle {
id: number;
scaleFactor: number;
}
declare const _default: () => Display;
export default _default;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var electron_1 = require("electron");
exports.default = (function () {
var point = electron_1.screen.getCursorScreenPoint();
var _a = electron_1.screen.getDisplayNearestPoint(point), id = _a.id, bounds = _a.bounds, scaleFactor = _a.scaleFactor;
// https://github.com/nashaofu/screenshots/issues/98
return {
id: id,
x: Math.floor(bounds.x),
y: Math.floor(bounds.y),
width: Math.floor(bounds.width),
height: Math.floor(bounds.height),
scaleFactor: scaleFactor,
};
});
"use strict";
module.exports = require('.').default;
/// <reference types="node" />
import { Debugger } from 'debug';
import { BrowserView, BrowserWindow } from 'electron';
import Events from 'events';
import { Bounds } from './preload';
export type LoggerFn = (...args: unknown[]) => void;
export type Logger = Debugger | LoggerFn;
export interface TranslateMapItem {
type: string;
text: string;
lang: string;
langText: string;
}
export interface Lang {
magnifier_position_label?: string;
operation_ok_title?: string;
operation_cancel_title?: string;
operation_save_title?: string;
operation_redo_title?: string;
operation_undo_title?: string;
operation_mosaic_title?: string;
operation_text_title?: string;
operation_brush_title?: string;
operation_arrow_title?: string;
operation_ellipse_title?: string;
operation_rectangle_title?: string;
}
export interface ScreenshotsOpts {
lang?: Lang;
logger?: Logger;
singleWindow?: boolean;
}
export { Bounds };
export default class Screenshots extends Events {
$win: BrowserWindow | null;
$view: BrowserView;
private logger;
private singleWindow;
private isReady;
constructor(opts?: ScreenshotsOpts);
/**
* 开始截图
*/
startCapture(): Promise<void>;
/**
* 结束截图
*/
endCapture(): Promise<void>;
/**
* 设置语言
*/
sendTranslateText(translateMap: TranslateMapItem[]): Promise<void>;
/**
* 设置语言
*/
setLang(lang: Partial<Lang>): Promise<void>;
private reset;
/**
* 初始化窗口
*/
private createWindow;
private capture;
/**
* 绑定ipc时间处理
*/
private listenIpc;
}
/**
* 如果string字符串长度小于 length 则在左侧填充字符
* 如果超出length长度则截断超出的部分。
* @param {unknown} string
* @param {string} chars
* @param {number} length
*/
export default function padStart(string: unknown, length?: number, chars?: string): string;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* 如果string字符串长度小于 length 则在左侧填充字符
* 如果超出length长度则截断超出的部分。
* @param {unknown} string
* @param {string} chars
* @param {number} length
*/
function padStart(string, length, chars) {
if (length === void 0) { length = 0; }
if (chars === void 0) { chars = ' '; }
var str = String(string);
while (str.length < length) {
str = "".concat(chars).concat(str);
}
return str;
}
exports.default = padStart;
import { Display } from './getDisplay';
export interface Bounds {
x: number;
y: number;
width: number;
height: number;
}
export interface ScreenshotsData {
bounds: Bounds;
display: Display;
}
"use strict";
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable no-console */
var electron_1 = require("electron");
var map = new Map();
electron_1.contextBridge.exposeInMainWorld('screenshots', {
ready: function () {
console.log('contextBridge ready');
electron_1.ipcRenderer.send('SCREENSHOTS:ready');
},
reset: function () {
console.log('contextBridge reset');
electron_1.ipcRenderer.send('SCREENSHOTS:reset');
},
save: function (arrayBuffer, data) {
console.log('contextBridge save', arrayBuffer, data);
electron_1.ipcRenderer.send('SCREENSHOTS:save', Buffer.from(arrayBuffer), data);
},
cancel: function () {
console.log('contextBridge cancel');
electron_1.ipcRenderer.send('SCREENSHOTS:cancel');
},
ok: function (arrayBuffer, data) {
console.log('contextBridge ok', arrayBuffer, data);
electron_1.ipcRenderer.send('SCREENSHOTS:ok', Buffer.from(arrayBuffer), data);
},
translate: function (arrayBuffer, data) {
console.log('contextBridge translate', arrayBuffer, data);
electron_1.ipcRenderer.send('SCREENSHOTS:translate', Buffer.from(arrayBuffer), data);
},
on: function (channel, fn) {
var _a;
console.log('contextBridge on', fn);
var listener = function (event) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
console.log.apply(console, __spreadArray(['contextBridge on', channel, fn], args, false));
fn.apply(void 0, args);
};
var listeners = (_a = map.get(fn)) !== null && _a !== void 0 ? _a : {};
listeners[channel] = listener;
map.set(fn, listeners);
electron_1.ipcRenderer.on("SCREENSHOTS:".concat(channel), listener);
},
off: function (channel, fn) {
var _a;
console.log('contextBridge off', fn);
var listeners = (_a = map.get(fn)) !== null && _a !== void 0 ? _a : {};
var listener = listeners[channel];
delete listeners[channel];
if (!listener) {
return;
}
electron_1.ipcRenderer.off("SCREENSHOTS:".concat(channel), listener);
},
});
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "electron-screenshots",
"version": "0.5.26",
"description": "electron 截图插件",
"types": "lib/index.d.ts",
"main": "lib/index.cjs.js",
"module": "lib/index.js",
"files": [
"lib/**"
],
"scripts": {
"prepublishOnly": "npm run build",
"start": "cross-env DEBUG=electron-screenshots electron lib/demo.js",
"dev": "tsc --sourceMap --watch",
"build": "npm run lint && npm run clean && tsc",
"lint": "eslint . --ext .js,.ts --fix",
"clean": "rimraf lib"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nashaofu/screenshots.git"
},
"keywords": [
"electron",
"shortcut",
"screenshot",
"cropper"
],
"author": "nashaofu",
"license": "MIT",
"bugs": {
"url": "https://github.com/nashaofu/screenshots/issues"
},
"homepage": "https://github.com/nashaofu/screenshots/tree/master/packages/electron-screenshots#readme",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"dependencies": {
"debug": "^4.3.4",
"fs-extra": "^11.1.1",
"node-screenshots": "^0.1.9",
"react-screenshots": "file:react-screenshots"
},
"peerDependencies": {
"electron": ">=14"
},
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/fs-extra": "^11.0.1",
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"cross-env": "^7.0.3",
"electron": "^23.2.0",
"eslint": "^8.37.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"rimraf": "^4.4.1",
"typescript": "^5.0.2"
}
}
# react-screenshots
> a screenshot cropper tool by react
## Install
[![NPM](https://nodei.co/npm/react-screenshots.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/react-screenshots/)
## Usage
1. web 中使用
```ts
import React, { ReactElement, useCallback } from "react";
import Screenshots, { Bounds } from "react-screenshots";
import url from "./image.jpg";
interface Bounds {
x: number;
y: number;
width: number;
height: number;
}
export default function App(): ReactElement {
const onSave = useCallback((blob: Blob, bounds: Bounds) => {
console.log("save", blob, bounds);
console.log(URL.createObjectURL(blob));
}, []);
const onCancel = useCallback(() => {
console.log("cancel");
}, []);
const onOk = useCallback((blob: Blob, bounds: Bounds) => {
console.log("ok", blob, bounds);
console.log(URL.createObjectURL(blob));
}, []);
return (
<Screenshots
url={url}
width={window.innerWidth}
height={window.innerHeight}
lang={{
operation_undo_title: "Undo",
operation_mosaic_title: "Mosaic",
operation_text_title: "Text",
operation_brush_title: "Brush",
operation_arrow_title: "Arrow",
operation_ellipse_title: "Ellipse",
operation_rectangle_title: "Rectangle",
}}
onSave={onSave}
onCancel={onCancel}
onOk={onOk}
/>
);
}
```
2. electron 中使用
- electron 中使用可直接加载渲染进程的页面,页面路径为`require.resolve('react-screenshots/electron/electron.html')`,不推荐自己手动开发主进程,推荐直接使用`electron-screenshots`模块
```ts
interface ScreenshotsData {
bounds: Bounds
display: Display
}
interface GlobalScreenshots {
ready: () => void
reset: () => void
save: (arrayBuffer: ArrayBuffer, data: ScreenshotsData) => void
cancel: () => void
ok: (arrayBuffer: ArrayBuffer, data: ScreenshotsData) => void
on: (channel: string, fn: ScreenshotsListener) => void
off: (channel: string, fn: ScreenshotsListener) => void
}
// 需要在electron的preload中提前初始化这个对象,用于渲染进程与主进程通信
window.screenshots: GlobalScreenshots
```
## Props
```ts
interface Bounds {
x: number;
y: number;
width: number;
height: number;
}
interface Lang {
magnifier_position_label: string;
operation_ok_title: string;
operation_cancel_title: string;
operation_save_title: string;
operation_redo_title: string;
operation_undo_title: string;
operation_mosaic_title: string;
operation_text_title: string;
operation_brush_title: string;
operation_arrow_title: string;
operation_ellipse_title: string;
operation_rectangle_title: string;
}
```
| 名称 | 说明 | 类型 | 是否必选 |
| -------- | -------------------- | -------------------------------------- | -------- |
| url | 要编辑的图像资源地址 | `string` | 是 |
| width | 画布宽度 | `number` | 是 |
| height | 画布宽度 | `number` | 是 |
| lang | 多语言支持,默认中文 | `Partial<Lang>` | 否 |
| onSave | 保存按钮回调 | `(blob: Blob, bounds: Bounds) => void` | 否 |
| onCancel | 取消按钮回调 | `() => void` | 否 |
| onOk | 取消按钮回调 | `(blob: Blob, bounds: Bounds) => void` | 否 |
### example
```js
import React from "react";
function App() {
return (
<Screenshot
url="./example.png"
width={window.innerWidth}
height={window.innerHeight}
onSave={() => {}}
onCancel={() => {}}
onOk={() => {}}
/>
);
}
```
## Screenshot
![screenshot](../../screenshot.jpg)
## Icons
[Iconfont](https://at.alicdn.com/t/project/572327/6f652e79-fb8b-4164-9fb3-40a705433d93.html?spm=a313x.7781069.1998910419.34)
This source diff could not be displayed because it is too large. You can view the blob instead.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>react-screenshots</title>
<script type="module" crossorigin src="./assets/index-2071bda7.js"></script>
<link rel="stylesheet" href="./assets/electron-79934bbd.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
import { Point, Bounds } from '../types';
export default function getBoundsByPoints({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, width: number, height: number): Bounds;
import React from 'react';
import './index.less';
declare const _default: React.NamedExoticComponent<object>;
export default _default;
import React, { PointerEvent, ReactNode } from 'react';
import './index.less';
export interface ScreenshotsButtonProps {
title: string;
icon: string;
checked?: boolean;
disabled?: boolean;
option?: ReactNode;
onClick?: (e: PointerEvent<HTMLDivElement>) => unknown;
}
declare const _default: React.NamedExoticComponent<ScreenshotsButtonProps>;
export default _default;
import { Bounds, Point } from '../types';
export default function getBoundsByPoints({ x: x1, y: y1 }: Point, { x: x2, y: y2 }: Point, bounds: Bounds, width: number, height: number, resizeOrMove: string): Bounds;
import { Point, Bounds } from '../types';
export default function getBoundsByPoints(e: MouseEvent, resizeOrMove: string, point: Point, bounds: Bounds): Point[];
import React from 'react';
import './index.less';
export declare enum ResizePoints {
ResizeTop = "top",
ResizetopRight = "top-right",
ResizeRight = "right",
ResizeRightBottom = "right-bottom",
ResizeBottom = "bottom",
ResizeBottomLeft = "bottom-left",
ResizeLeft = "left",
ResizeLeftTop = "left-top",
Move = "move"
}
declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<React.RefAttributes<CanvasRenderingContext2D>>>;
export default _default;
import { Bounds, History } from '../types';
export default function isPointInDraw(bounds: Bounds, canvas: HTMLCanvasElement | null, history: History, e: MouseEvent): false | import("../types").HistoryItem<any, any> | undefined;
import React from 'react';
import './index.less';
export interface ColorProps {
value: string;
onChange: (value: string) => void;
}
declare const _default: React.NamedExoticComponent<ColorProps>;
export default _default;
import React, { Dispatch, SetStateAction } from 'react';
import { EmiterRef, History, Bounds, CanvasContextRef } from './types';
import { Lang } from './zh_CN';
export interface ScreenshotsContextStore {
url?: string;
image: HTMLImageElement | null;
width: number;
height: number;
lang: Lang;
emiterRef: EmiterRef;
canvasContextRef: CanvasContextRef;
history: History;
bounds: Bounds | null;
cursor?: string;
operation?: string;
translateMap?: Array<{
type: string;
text: string;
lang: string;
langText: string;
}>;
}
export interface ScreenshotsContextDispatcher {
call?: <T>(funcName: string, ...args: T[]) => void;
setHistory?: Dispatch<SetStateAction<History>>;
setBounds?: Dispatch<SetStateAction<Bounds | null>>;
setCursor?: Dispatch<SetStateAction<string | undefined>>;
setOperation?: Dispatch<SetStateAction<string | undefined>>;
}
export interface ScreenshotsContextValue {
store: ScreenshotsContextStore;
dispatcher: ScreenshotsContextDispatcher;
}
declare const _default: React.Context<ScreenshotsContextValue>;
export default _default;
import { ReactElement } from 'react';
import './index.less';
export default function ScreenshotsLoading(): ReactElement;
import React from 'react';
import './index.less';
export interface ScreenshotsMagnifierProps {
x: number;
y: number;
}
declare const _default: React.NamedExoticComponent<ScreenshotsMagnifierProps>;
export default _default;
import React from 'react';
import { Bounds } from '../types';
import './index.less';
export declare const ScreenshotsOperationsCtx: React.Context<Bounds | null>;
declare const _default: React.NamedExoticComponent<object>;
export default _default;
import React, { ReactElement, ReactNode } from 'react';
import { Point } from '../types';
import './index.less';
export interface ScreenshotsOptionProps {
open?: boolean;
content?: ReactNode;
children: ReactElement;
}
export type Position = Point;
export declare enum Placement {
Bottom = "bottom",
Top = "top"
}
declare const _default: React.NamedExoticComponent<ScreenshotsOptionProps>;
export default _default;
import React from 'react';
import './index.less';
export interface SizeProps {
value: number;
onChange: (value: number) => void;
}
declare const _default: React.NamedExoticComponent<SizeProps>;
export default _default;
import React from 'react';
import './index.less';
export interface SizeColorProps {
size: number;
color: string;
onSizeChange: (value: number) => void;
onColorChange: (value: string) => void;
}
declare const _default: React.NamedExoticComponent<SizeColorProps>;
export default _default;
export interface SizeInfo {
sizingStyle: string;
paddingSize: number;
borderSize: number;
boxSizing: string;
}
export interface Size {
width: number;
height: number;
}
export declare function getComputedSizeInfo(node: HTMLElement): {
sizingStyle: string;
paddingSize: number;
borderSize: number;
boxSizing: string;
};
export default function calculateNodeSize(textarea: HTMLTextAreaElement, value: string, maxWidth: number, maxHeight: number): Size;
import React, { FocusEvent } from 'react';
import './index.less';
export interface TextInputProps {
x: number;
y: number;
maxWidth: number;
maxHeight: number;
size: number;
color: string;
value: string;
onChange: (value: string) => unknown;
onBlur: (e: FocusEvent<HTMLTextAreaElement>) => unknown;
}
declare const _default: React.NamedExoticComponent<TextInputProps>;
export default _default;
import { Bounds, History } from './types';
interface ComposeImageOpts {
image: HTMLImageElement;
width: number;
height: number;
history: History;
bounds: Bounds;
}
export default function composeImage({ image, width, height, history, bounds }: ComposeImageOpts): Promise<Blob>;
export {};
export { Bounds } from './types';
export { Lang } from './zh_CN';
export { default, ScreenshotsProps } from './';
import { Bounds } from '../types';
export interface BoundsDispatcher {
set: (bounds: Bounds) => void;
reset: () => void;
}
export type BoundsValueDispatcher = [Bounds | null, BoundsDispatcher];
export default function useBounds(): BoundsValueDispatcher;
export type CallDispatcher = <T extends unknown[]>(funcName: string, ...args: T) => void;
export default function useCall(): CallDispatcher;
import { CanvasContextRef } from '../types';
export default function useCanvasContextRef(): CanvasContextRef;
export default function useCanvasMousedown(onMousedown: (e: MouseEvent) => unknown): void;
export default function useCanvasMousemove(onMousemove: (e: MouseEvent) => unknown): void;
export default function useCanvasMouseup(onMouseup: (e: MouseEvent) => unknown): void;
export interface CursorDispatcher {
set: (cursor: string) => void;
reset: () => void;
}
export type CursorValueDispatcher = [string | undefined, CursorDispatcher];
export default function useCursor(): CursorValueDispatcher;
import { ScreenshotsContextDispatcher } from '../ScreenshotsContext';
export default function useDispatcher(): ScreenshotsContextDispatcher;
import { HistoryItemSource } from '../types';
export default function useDrawSelect(onDrawSelect: (action: HistoryItemSource<unknown, unknown>, e: MouseEvent) => unknown): void;
import { EmiterListener } from '../types';
export interface EmiterDispatcher {
on: (event: string, listener: EmiterListener) => void;
off: (event: string, listener: EmiterListener) => void;
emit: (event: string, ...args: unknown[]) => void;
reset: () => void;
}
export default function useEmiter(): EmiterDispatcher;
import { History, HistoryItem } from '../types';
export interface HistoryValue extends History {
top?: HistoryItem<unknown, unknown>;
}
export interface HistoryDispatcher {
push: <S, E>(action: HistoryItem<S, E>) => void;
pop: () => void;
undo: () => void;
redo: () => void;
set: (history: History) => void;
select: <S, E>(action: HistoryItem<S, E>) => void;
clearSelect: () => void;
reset: () => void;
}
export type HistoryValueDispatcher = [HistoryValue, HistoryDispatcher];
export default function useHistory(): HistoryValueDispatcher;
import { Lang } from '../zh_CN';
export default function useLang(): Lang;
export interface OperationDispatcher {
set: (operation: string) => void;
reset: () => void;
}
export type OperationValueDispatcher = [
string | undefined,
OperationDispatcher
];
export default function useOperation(): OperationValueDispatcher;
export type ResetDispatcher = () => void;
export default function useReset(): ResetDispatcher;
import { ScreenshotsContextStore } from '../ScreenshotsContext';
export default function useStore(): ScreenshotsContextStore;
import { ReactElement } from 'react';
import './icons/iconfont.less';
import './icons/extend/iconfont.less';
import './screenshots.less';
import { Lang } from './zh_CN';
export interface TranslateMapItem {
type: string;
text: string;
lang: string;
langText: string;
}
export interface ScreenshotsProps {
url?: string;
width: number;
height: number;
lang?: Partial<Lang>;
className?: string;
translateMap?: TranslateMapItem[];
[key: string]: unknown;
}
export default function Screenshots({ url, width, height, lang, className, translateMap, ...props }: ScreenshotsProps): ReactElement;
import { ArrowData, ArrowEditData } from '.';
import { HistoryItemSource } from '../../types';
export declare function getEditedArrowData(action: HistoryItemSource<ArrowData, ArrowEditData>): {
x1: number;
x2: number;
y1: number;
y2: number;
size: number;
color: string;
};
export default function draw(ctx: CanvasRenderingContext2D, action: HistoryItemSource<ArrowData, ArrowEditData>): void;
import { ReactElement } from 'react';
export interface ArrowData {
size: number;
color: string;
x1: number;
x2: number;
y1: number;
y2: number;
}
export declare enum ArrowEditType {
Move = 0,
MoveStart = 1,
MoveEnd = 2
}
export interface ArrowEditData {
type: ArrowEditType;
x1: number;
x2: number;
y1: number;
y2: number;
}
export default function Arrow(): ReactElement;
import { BrushData, BrushEditData } from '.';
import { HistoryItemSource } from '../../types';
export default function draw(ctx: CanvasRenderingContext2D, action: HistoryItemSource<BrushData, BrushEditData>): void;
import { ReactElement } from 'react';
import { Point } from '../../types';
export interface BrushData {
size: number;
color: string;
points: Point[];
}
export interface BrushEditData {
x1: number;
y1: number;
x2: number;
y2: number;
}
export default function Brush(): ReactElement;
import { ReactElement } from 'react';
export default function Cancel(): ReactElement;
import { EllipseData, EllipseEditData } from '.';
import { HistoryItemSource } from '../../types';
export declare function getEditedEllipseData(action: HistoryItemSource<EllipseData, EllipseEditData>): {
x1: number;
x2: number;
y1: number;
y2: number;
size: number;
color: string;
};
export default function draw(ctx: CanvasRenderingContext2D, action: HistoryItemSource<EllipseData, EllipseEditData>): void;
import { ReactElement } from 'react';
export interface EllipseData {
size: number;
color: string;
x1: number;
y1: number;
x2: number;
y2: number;
}
export declare enum EllipseEditType {
Move = 0,
ResizeTop = 1,
ResizeRightTop = 2,
ResizeRight = 3,
ResizeRightBottom = 4,
ResizeBottom = 5,
ResizeLeftBottom = 6,
ResizeLeft = 7,
ResizeLeftTop = 8
}
export interface EllipseEditData {
type: EllipseEditType;
x1: number;
y1: number;
x2: number;
y2: number;
}
export default function Ellipse(): ReactElement;
import { ReactElement } from 'react';
export interface MosaicTile {
x: number;
y: number;
color: number[];
}
export interface MosaicData {
size: number;
tiles: MosaicTile[];
}
export default function Mosaic(): ReactElement;
import { ReactElement } from 'react';
export default function Ok(): ReactElement;
import { RectangleData, RectangleEditData } from '.';
import { HistoryItemSource } from '../../types';
export declare function getEditedRectangleData(action: HistoryItemSource<RectangleData, RectangleEditData>): {
x1: number;
x2: number;
y1: number;
y2: number;
size: number;
color: string;
};
export default function draw(ctx: CanvasRenderingContext2D, action: HistoryItemSource<RectangleData, RectangleEditData>): void;
import { ReactElement } from 'react';
export interface RectangleData {
size: number;
color: string;
x1: number;
y1: number;
x2: number;
y2: number;
}
export declare enum RectangleEditType {
Move = 0,
ResizeTop = 1,
ResizeRightTop = 2,
ResizeRight = 3,
ResizeRightBottom = 4,
ResizeBottom = 5,
ResizeLeftBottom = 6,
ResizeLeft = 7,
ResizeLeftTop = 8
}
export interface RectangleEditData {
type: RectangleEditType;
x1: number;
y1: number;
x2: number;
y2: number;
}
export default function Rectangle(): ReactElement;
import { ReactElement } from 'react';
export default function Redo(): ReactElement;
import { ReactElement } from 'react';
export default function Save(): ReactElement;
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment