├── .babelrc ├── .gitignore ├── .npmrc ├── .prettierrc ├── README.md ├── demos ├── child.html ├── index.html ├── main.js ├── package-lock.json ├── package.json ├── renderer.js └── spider.gif ├── images ├── ability.png ├── chromium.png ├── electron.png ├── ipcRenderer.png ├── ipcRenderer2.png └── xiaoguo.gif ├── index.html ├── koa-mongodb ├── README.md ├── index.js └── package.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── ppt └── 桌面端开发之Electron200628.pdf ├── src ├── lib │ └── capture │ │ ├── iconfont │ │ ├── demo.css │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ │ ├── index.html │ │ ├── js │ │ ├── capture.js │ │ └── draw.js │ │ └── main.js ├── login.html ├── main │ ├── crash-reporter.js │ ├── download.js │ ├── ipc.js │ ├── main.js │ ├── menus │ │ ├── about.js │ │ ├── file-menu.js │ │ ├── index.js │ │ └── mac-app-menu.js │ ├── tray │ │ └── index.js │ ├── updater.js │ └── windows │ │ └── index.js ├── pic.html ├── renderer │ ├── App.jsx │ ├── components │ │ ├── EmojiPackage │ │ │ ├── index.js │ │ │ └── index.scss │ │ └── Message │ │ │ ├── Image.jsx │ │ │ ├── Image.scss │ │ │ ├── Text.js │ │ │ ├── Text.scss │ │ │ ├── Tip.js │ │ │ ├── Tip.scss │ │ │ ├── index.js │ │ │ └── index.scss │ ├── constants.js │ ├── container │ │ ├── Chat │ │ │ ├── Chatlist.js │ │ │ ├── Messages.jsx │ │ │ ├── index.jsx │ │ │ └── index.scss │ │ └── User │ │ │ ├── Setting.jsx │ │ │ ├── index.jsx │ │ │ └── index.scss │ ├── context-manager.js │ ├── index.ejs │ ├── index.jsx │ └── utils.js ├── resources │ ├── icns │ │ └── spider.icns │ └── images │ │ ├── favicon.ico │ │ ├── icons │ │ ├── bubble.png │ │ ├── bubble_green.png │ │ ├── dianhua.png │ │ ├── download.png │ │ ├── jianqie.png │ │ ├── lianxiren.png │ │ ├── lianxiren_green.png │ │ ├── lifangti.png │ │ ├── lifangti_green.png │ │ ├── refresh-line.png │ │ ├── ren.png │ │ ├── rotateleft.png │ │ ├── rotateright.png │ │ ├── santiaogang.png │ │ ├── shipin.png │ │ ├── shouji.png │ │ ├── sousuo.png │ │ ├── wenjian.png │ │ ├── xiaolian.png │ │ ├── xiaoxi.png │ │ ├── zoomin.png │ │ └── zoomout.png │ │ ├── users │ │ ├── bianfuxia-big.png │ │ ├── bianfuxia.png │ │ ├── chaoren-big.png │ │ ├── chaoren.png │ │ ├── gangtiexia-big.png │ │ ├── gangtiexia.png │ │ ├── jinganglang-big.png │ │ ├── jinganglang.png │ │ ├── leishen-big.png │ │ ├── leishen.png │ │ ├── lvdengxia-big.png │ │ ├── lvdengxia.png │ │ ├── lvjuren-big.png │ │ ├── lvjuren.png │ │ ├── meiguoduichang-big.png │ │ ├── meiguoduichang.png │ │ ├── ren-big.png │ │ ├── ren.png │ │ ├── shandianxia-big.png │ │ ├── shandianxia.png │ │ ├── sishi-big.png │ │ ├── sishi.png │ │ ├── user.png │ │ ├── xiaochou-big.png │ │ ├── xiaochou.png │ │ ├── yiren-big.png │ │ ├── yiren.png │ │ ├── zhizhuxia-big.png │ │ ├── zhizhuxia.png │ │ ├── zhizhuxia_1-big.png │ │ └── zhizhuxia_1.png │ │ ├── zhizhuxia.png │ │ ├── zhizhuxia_big.png │ │ └── zhizhuxia_small.png └── setting.html ├── todo.md ├── updater-server ├── index.js ├── package-lock.json ├── package.json ├── parsedump.js └── public │ └── spiderchat-1.0.1-mac.zip ├── upload └── default.png ├── webpack.config.js ├── webpack_configs ├── absulotePath.js ├── index.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js └── ws-server ├── client.html └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0", 5 | "stage-1" 6 | ], 7 | "plugins": [ 8 | "transform-runtime" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .idea 4 | .git 5 | .DS_Store 6 | dist 7 | build 8 | crash 9 | error 10 | .vscode -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | electron_mirror=http://npm.taobao.org/mirrors/electron/ 2 | registry=https://registry.npm.taobao.org/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": false, 5 | "singleQuote": false, 6 | "TrailingCooma": "all", 7 | "bracketSpacing": true, 8 | "jsxBracketSameLine": false, 9 | "arrowParens": "avoid" 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron-learn 2 | 3 | 官网:https://electronjs.org/ 4 | 5 | - [electron-learn](#electron-learn) 6 | - [1. 本次项目功能点](#1-本次项目功能点) 7 | - [目前支持的功能点](#目前支持的功能点) 8 | - [使用方式](#使用方式) 9 | - [2. electron相关软件安装](#2-electron相关软件安装) 10 | - [nvm 安装](#nvm-安装) 11 | - [Node.js/NPM 安装](#nodejsnpm-安装) 12 | - [node 安装加速机器](#node-安装加速机器) 13 | - [Electron 安装](#electron-安装) 14 | - [Electron 加速技巧](#electron-加速技巧) 15 | - [常见问题](#常见问题) 16 | - [3. electron 原理](#3-electron-原理) 17 | - [2.1. 使用 Electron 的 API](#21-使用-electron-的-api) 18 | - [3.2. 使用 Node.js 的 API](#32-使用-nodejs-的-api) 19 | - [4. electron 常用 api](#4-electron-常用-api) 20 | - [4.1. app](#41-app) 21 | - [4.2. BrowserWindow](#42-browserwindow) 22 | - [4.3. ipcMain 和 ipcRenderer](#43-ipcmain-和-ipcrenderer) 23 | - [4.4. Menu/MenuItem(菜单/菜单项)](#44-menumenuitem菜单菜单项) 24 | - [4.5. Tray(托盘)](#45-tray托盘) 25 | - [4.6. clipboard](#46-clipboard) 26 | - [4.7. screen](#47-screen) 27 | - [4.8. globalShortcut](#48-globalshortcut) 28 | - [4.9. desktopCapturer](#49-desktopcapturer) 29 | - [4.10. shell](#410-shell) 30 | - [4.11. powerMonitor 电源监视器](#411-powermonitor-电源监视器) 31 | - [4.12. nativeTheme 读取并响应Chromium本地色彩主题中的变化](#412-nativetheme-读取并响应chromium本地色彩主题中的变化) 32 | - [5. 开机自启动](#5-开机自启动) 33 | - [5.1. node-auto-launch](#51-node-auto-launch) 34 | - [5.2. app.getLoginItemSettings([options])](#52-appgetloginitemsettingsoptions) 35 | - [6. 监控—crashReporter](#6-监控crashreporter) 36 | - [7. 打包](#7-打包) 37 | - [7.1. electron-builder](#71-electron-builder) 38 | - [打包中遇到的问题](#打包中遇到的问题) 39 | - [命令行参数(CLI)](#命令行参数cli) 40 | - [8. 集成c++](#8-集成c) 41 | - [9. 测试和调试](#9-测试和调试) 42 | - [9.1. 调试debug](#91-调试debug) 43 | - [9.2. 自动化测试](#92-自动化测试) 44 | - [10. 更新](#10-更新) 45 | - [11. Electron客户端的安全:从xss到rce](#11-electron客户端的安全从xss到rce) 46 | - [12. 浏览器启动客户端](#12-浏览器启动客户端) 47 | - [12.1. windows平台](#121-windows平台) 48 | - [12.2. mac 平台](#122-mac-平台) 49 | - [12.2.1. info.plist](#1221-infoplist) 50 | - [12.3. 接收参数](#123-接收参数) 51 | - [12.3.1. Windows](#1231-windows) 52 | - [12.3.2. MacOS](#1232-macos) 53 | - [13. 性能优化](#13-性能优化) 54 | - [13.1. 减少包体积大小](#131-减少包体积大小) 55 | - [Electron 开发过程中可能会遇到的几个问题和场景。](#electron-开发过程中可能会遇到的几个问题和场景) 56 | - [NPM 下载的问题](#npm-下载的问题) 57 | - [热重载](#热重载) 58 | - [参考资料](#参考资料) 59 | 60 | ## 1. 本次项目功能点 61 | 62 | 模仿微信,做了一个单机版的聊天,因为只有mac,没有Windows机器,以下仅根据mac来开发。 63 | 64 | ![效果图](images/xiaoguo.gif) 65 | 66 | ### 目前支持的功能点 67 | 68 | 1. 聊天 69 | 2. 发送表情 70 | 3. 选择文件(只支持选择图片)发送 71 | 4. 截图发送 72 | 5. 粘贴图片(只支持剪切板上粘贴图片)发送 73 | 6. 根据当前设备主题自动切换深浅主题 74 | 7. 设置里,调整字体大小 75 | 8. 退出登录,含清除数据功能 76 | 9. 启动updater-server 自动更新 77 | 78 | ### 使用方式 79 | 80 | ```text 81 | git clone https://github.com/spiderT/electron-learn.git 82 | cd electron-learn 83 | npm install 84 | 85 | // (可选)消息存储服务端 koa+mongodb, 需要事先安装配置mongodb 86 | cd koa-mongodb 87 | npm install 88 | node index.js 89 | 90 | // 启动websocket模拟聊天 91 | cd ws-server 92 | node index.js 93 | // 以live server的方式打开client.html,就可以愉快的聊天了 94 | 95 | // 启动 96 | npm start 97 | 98 | // 如果需要启动crash报告收集,update服务 99 | cd updater-server 100 | node index.js 101 | ``` 102 | 103 | 104 | ## 2. electron相关软件安装 105 | 106 | ### nvm 安装 107 | 108 | ```text 109 | Mac/Linux: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash 110 | Windows: https://github.com/coreybutler/nvm-windows/releasesa 111 | 验证nvm: nvm --versiona 112 | ``` 113 | 114 | ### Node.js/NPM 安装 115 | 116 | ```text 117 | 安装 Node.js: nvm install 12.14.0 118 | 切换 Node.js 版本:nvm use 12.14.0 119 | 验证 npm -v 120 | 验证 node -v 121 | ``` 122 | 123 | ### node 安装加速机器 124 | 125 | ```text 126 | // mac 在 .bashrc 或者 .zshrc 加入 127 | export NVM_NODEJS_ORG_MIRROR=http://npm.taobao.org/mirrors/node 128 | 129 | // Windows 在 %userprofile%\AppData\Roaming\nvm\setting.txt 加入 130 | node_mirror: https://npm.taobao.org/mirrors/node/ npm_mirror: https://npm.taobao.org/mirrors/npm/ 131 | ``` 132 | 133 | ### Electron 安装 134 | 135 | ```text 136 | npm install electron --save-dev 137 | npm install --arch=ia32 --platform=win32 electron 138 | 139 | // 验证安装成功: 140 | npx electron -v (npm > 5.2) 141 | ./node_modules/.bin/electron -v 142 | ``` 143 | 144 | ### Electron 加速技巧 145 | 146 | ```text 147 | # 设置ELECTRON_MIRROR 148 | ELECTRON_MIRROR=https://cdn.npm.taobao.org/dist/electron/ npm install electron --save- dev 149 | ``` 150 | 151 | ### 常见问题 152 | 153 | > Electron failed to install correctly, please delete node_modules/electron and try installing again 154 | 155 | 1. 首先 npm instal或者yarn install 156 | 2. 执行 npm install electron-fix -g 157 | 3. 接着 electron-fix start 158 | 4. 最后再 npm start 159 | 160 | ## 3. electron 原理 161 | 162 | Node.js 和 Chromiums 整合 163 | 164 | - Chromium 集成到 Node.js: 用 libuv 实现 messagebump (nw) 165 | 166 | - 难点:Node.js 事件循环基于 libuv,但 Chromium 基于 message bump 167 | 168 | Node.js 集成到 Chromium 169 | 170 | ![chromium](images/chromium.png) 171 | ![electron](images/electron.png) 172 | 173 | 174 | ### 2.1. 使用 Electron 的 API 175 | 176 | Electron 在主进程和渲染进程中提供了大量 API 去帮助开发桌面应用程序, 在主进程和渲染进程中,你可以通过 require 的方式将其包含在模块中以此,获取 Electron 的 API 177 | 178 | ```js 179 | const electron = require('electron'); 180 | ``` 181 | 182 | 所有 Electron 的 API 都被指派给一种进程类型。 许多 API 只能被用于主进程或渲染进程中,但其中一些 API 可以同时在上述两种进程中使用。 每一个 API 的文档都将声明你可以在哪种进程中使用该 API。 183 | 184 | Electron 中的窗口是使用 BrowserWindow 类型创建的一个实例, 它只能在主进程中使用。 185 | 186 | ```js 187 | // 这样写在主进程会有用,但是在渲染进程中会提示'未定义' 188 | const { BrowserWindow } = require('electron'); 189 | 190 | const win = new BrowserWindow(); 191 | ``` 192 | 193 | 因为进程之间的通信是被允许的, 所以渲染进程可以调用主进程来执行任务。 Electron 通过 remote 模块暴露一些通常只能在主进程中获取到的 API。 为了在渲染进程中创建一个 BrowserWindow 的实例,我们通常使用 remote 模块为中间件: 194 | 195 | ```js 196 | //这样写在渲染进程中时行得通的,但是在主进程中是'未定义' 197 | const { remote } = require('electron'); 198 | const { BrowserWindow } = remote; 199 | 200 | const win = new BrowserWindow(); 201 | ``` 202 | 203 | ### 3.2. 使用 Node.js 的 API 204 | 205 | Electron 同时对主进程和渲染进程暴露了 Node.js 所有的接口。 这里有两个重要的定义: 206 | 207 | 1. 所有在 Node.js 可以使用的 API,在 Electron 中同样可以使用。 在 Electron 中调用如下代码是有用的: 208 | 209 | ```js 210 | const fs = require('fs'); 211 | 212 | const root = fs.readdirSync('/'); 213 | 214 | // 这会打印出磁盘根级别的所有文件 215 | // 同时包含'/'和'C:\'。 216 | console.log(root); 217 | ``` 218 | 219 | 正如您可能已经猜到的那样,如果您尝试加载远程内容, 这会带来重要的安全隐患。 您可以在我们的 安全文档 中找到更多有关加载远程内容的信息和指南。 220 | 221 | 2)你可以在你的应用程序中使用 Node.js 的模块。 选择您最喜欢的 npm 模块。 npm 提供了目前世界上最大的开源代码库,那里包含良好的维护、经过测试的代码,提供给服务器应用程序的特色功能也提供给 Electron。 222 | 223 | 例如,在你的应用程序中要使用官方的 AWS SDK,你需要首先安装它的依赖: 224 | 225 | npm install --save aws-sdk 226 | 然后在你的 Electron 应用中,通过 require 引入并使用该模块,就像构建 Node.js 应用程序那样: 227 | 228 | ```js 229 | // 准备好被使用的S3 client模块 230 | const S3 = require('aws-sdk/clients/s3'); 231 | ``` 232 | 233 | 有一个非常重要的提示: 原生 Node.js 模块 (即指,需要编译源码过后才能被使用的模块) 需要在编译后才能和 Electron 一起使用。 234 | 235 | 绝大多数的 Node.js 模块都不是原生的, 在 650000 个模块中只有 400 是原生的。 236 | 237 | ## 4. electron 常用 api 238 | 239 | ### 4.1. app 240 | 241 | 进程: Main 242 | 用于控制应用生命周期。 243 | 244 | ready: 当 Electron 完成初始化时被触发。 245 | 246 | will-finish-launching: 当应用程序完成基础的启动的时候被触发。常会在这里为 open-file 和 open-url 设置监听器,并启动崩溃报告和自动更新。 247 | 248 | activate: 当应用被激活时触发,常用于点击应用的 dock 图标的时候。 249 | 250 | window-all-closed: 当所有的窗口都被关闭时触发。如果没有监听此事件,当所有窗口都已关闭时,默认行为是退出应用程序。 251 | 252 | 253 | ```js 254 | const { app } = require('electron') 255 | 256 | app.on('second-instance', show); 257 | 258 | app.on('will-finish-launching', () => { 259 | // 自动更新 260 | if (!isDev) { 261 | require('./src/main/updater.js'); 262 | } 263 | require('./src/main/crash-reporter').init(); 264 | }); 265 | 266 | app.on('ready', () => { 267 | // 模拟crash 268 | // process.crash(); 269 | const win = createWindow(); 270 | setTray(); 271 | handleIPC(); 272 | handleDownload(win); 273 | }); 274 | 275 | app.on('activate', show); 276 | 277 | app.on('before-quit', close); 278 | 279 | app.on('will-quit', () => { 280 | // Unregister all shortcuts. 281 | globalShortcut.unregisterAll(); 282 | }); 283 | app.on('window-all-closed', () => { 284 | app.quit() 285 | }) 286 | 287 | ``` 288 | 289 | 禁止多开 290 | 291 | ```js 292 | const gotTheLock = app.requestSingleInstanceLock() 293 | if (!gotTheLock) { 294 | app.quit() 295 | } else { 296 | app.on('second-instance', (event, commandLine, workingDirectory) => { 297 | // 当运行第二个实例时,将会聚焦到myWindow这个窗口 298 | showMainWindow() 299 | }) 300 | app.on('ready', () => {...}) 301 | } 302 | ``` 303 | 304 | ### 4.2. BrowserWindow 305 | 306 | 进程: Main 307 | 创建和控制浏览器窗口。 308 | 309 | ```js 310 | win = new BrowserWindow({ 311 | width: 900, 312 | height: 700, 313 | webPreferences: { 314 | nodeIntegration: true 315 | }, 316 | minWidth: 800, 317 | minHeight: 600, 318 | titleBarStyle: 'hiddenInset', 319 | show: false, // 先隐藏 320 | icon: path.join(__dirname, '../../resources/images/zhizhuxia.png'), 321 | backgroundColor: '#f3f3f3', // 优化白屏,设置窗口底色 322 | }) 323 | ``` 324 | 325 | BrowserWindow——无边框 326 | 327 | 1、BrowserWindow 的 options 中将 frame 设置为 false。 328 | 329 | macOS 上的其他方案: 330 | 2、titleBarStyle设为hidden,返回一个隐藏标题栏的全尺寸内容窗口,在左上角仍然有标准的窗口控制按钮。 331 | 332 | 3、titleBarStyle设为hiddenInset,返回一个另一种隐藏了标题栏的窗口,其中控制按钮到窗口边框的距离更大。 333 | 334 | 4、customButtonsOnHover 335 | 使用自定义的关闭、缩小和全屏按钮,这些按钮会在划过窗口的左上角时显示。 336 | 337 | 5、透明窗口:通过将 transparent 选项设置为 true, 还可以使无框窗口透明: 338 | 339 | 默认情况下, 无边框窗口是不可拖拽的。 需要在 CSS 中指定 -webkit-app-region: drag 来告诉 Electron 哪些区域是可拖拽的。 340 | 341 | ### 4.3. ipcMain 和 ipcRenderer 342 | 343 | 1. 主进程和渲染进程之间的区别 344 | 345 | 主进程使用 BrowserWindow 实例创建页面。 每个 BrowserWindow 实例都在自己的渲染进程里运行页面。 当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。 346 | 347 | 主进程管理所有的 web 页面和它们对应的渲染进程。 每个渲染进程都是独立的,它只关心它所运行的 web 页面。 348 | 349 | 在页面中调用与 GUI 相关的原生 API 是不被允许的,因为在 web 页面里操作原生的 GUI 资源是非常危险的,而且容易造成资源泄露。 如果你想在 web 页面里使用 GUI 操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI 操作。 350 | 351 | Electron 为主进程( main process)和渲染器进程(renderer processes)通信提供了多种实现方式,如可以使用 ipcRenderer 和 ipcMain 模块发送消息,使用 remote 模块进行 RPC 方式通信 352 | 353 | ![渲染进程和主进程](images/ipcRenderer.png) 354 | 355 | 2. Electron 渲染进程 356 | 357 | ```js 358 | // 引入模块,各进程直接在electron模块引入即可。例子: 359 | const { app, BrowserWindow } = require(‘electron’) // 主进程引入app, BrowserWindow模块 360 | 361 | const { ipcRenderer } = require(‘electron’) // 渲染进程引入ipcRenderer 362 | ipcRenderer.invoke(channel, ...args).then(result => { handleResult }) // 渲染进程跟主进程发送请求 363 | ``` 364 | 365 | - 展示 Web 页面的进程称为渲染进程 366 | 367 | - 通过 Node.js、Electron 提供的 API 可以跟系统底层打交道 368 | 369 | - 一个 Electron 应用可以有多个渲染进程 370 | 371 | 3. Electron 主进程 372 | 373 | ipcMain.handle(channel, handler),处理理渲染进程的 channel 请求,在 handler 中 return 返回结果 374 | 375 | - Electron 运行 package.json 的 main 脚本的进程被称为主进程 376 | 377 | - 每个应用只有一个主进程 378 | 379 | - 管理原生 GUI,典型的窗口(BrowserWindow、Tray、Dock、Menu) 380 | 381 | - 创建渲染进程 382 | 383 | - 控制应用生命周期(app) 384 | 385 | ![渲染进程和主进程](images/ipcRenderer2.png) 386 | 387 | 4. 进程间通信 388 | 389 | 1) IPC 模块通信 390 | 391 | - Electron 提供了 IPC 通信模块,主进程的 ipcMain 和 渲染进程的 ipcRenderer 392 | - ipcMain、ipcRenderer 都是 EventEmitter 对象 393 | 394 | 2) 进程间通信:从渲染进程到主进程 395 | 396 | - Callback 写法: 397 | ipcRenderer.send 398 | ipcMain.on 399 | 400 | - Promise 写法 (Electron 7.0 之后,处理请求 + 响应模式) 401 | ipcRenderer.invoke 402 | ipcMain.handle 403 | 404 | 3) 进程间通信:从主进程到渲染进程 405 | 406 | - 主进程通知渲染进程: 407 | ipcRenderer.on 408 | webContents.send 409 | 410 | 4)页面间(渲染进程与渲染进程间)通信 411 | 412 | - 通知事件 413 | 414 | 通过主进程转发(Electron 5之前) 415 | ipcRenderer.sendTo (Electron 5之后) 数据共享 416 | 417 | 窗口A的渲染进程发消息给主进程 418 | 419 | ```js 420 | ipcRenderer.send('imgUploadMain', { 421 | id: dom.id, 422 | siteId: this.siteId, 423 | url: dom.src 424 | }); 425 | ``` 426 | 427 | 主进程收到消息后,再发消息给窗口B的渲染进程 428 | 429 | ```js 430 | ipcMain.on('imgUploadMain', (event, message) => { 431 | mainWindow.webContents.send('imgUploadMsgFromMain', message); 432 | }); 433 | ``` 434 | 435 | 窗口B渲染进程接收主进程消息的代码: 436 | 437 | ```js 438 | ipcRenderer.on('imgUploadMsgFromMain', (e, message) => this.imgUploadCb(message)); 439 | ``` 440 | 441 | - 数据共享 442 | 443 | Web 技术(localStorage、sessionStorage、indexedDB) 444 | 使用 remote 445 | 446 | **注意** 447 | 448 | - 少用 remote 模块 449 | - 不要用 sync 模式 450 | - 在请求 + 响应的通信模式下,需要自定义超时限制 451 | 452 | ### 4.4. Menu/MenuItem(菜单/菜单项) 453 | 454 | 1. 新建菜单 455 | 456 | ```js 457 | const menu = new Menu() 458 | ``` 459 | 460 | 2. 新建菜单项 461 | 462 | ```js 463 | const menuItem1 = new MenuItem({ label: '复制', role: 'copy' }) 464 | const menuItem2 = new MenuItem({ label: '菜单项名', click: handler, enabled, visible, 465 | type: normal | separator | submenu | checkbox | radio, 466 | role: copy | paste | cut | quit | ... 467 | }) 468 | ``` 469 | 470 | 3. 添加菜单项 471 | 472 | ```js 473 | menu.append(menuItem1) 474 | menu.append(new MenuItem({ type: 'separator' })) 475 | menu.append(menuItem2) 476 | ``` 477 | 478 | 4. 弹出右键菜单 479 | 480 | ```js 481 | menu.popup({ window: remote.getCurrentWindow() }) 482 | ``` 483 | 484 | 5. 设置应用菜单栏 485 | 486 | ```js 487 | app.applicationMenu = appMenu; 488 | ``` 489 | 490 | ### 4.5. Tray(托盘) 491 | 492 | 1. 方法 493 | 494 | - 创建托盘 495 | 496 | ```js 497 | tray = new Tray('/path/to/my/icon') 498 | ``` 499 | Mac图片建议保留 1倍图(32 * 32),2倍图@2x(64 * 64) 500 | Windows使用ico格式 501 | 大部分Mac托盘都是偏黑色的、Windows则是彩色的 Mac 502 | 503 | - 弹出托盘菜单 504 | 505 | ```js 506 | const contextMenu = Menu.buildFromTemplate([ 507 | { label: '显示', click: () => {showMainWindow()}}, 508 | { label: '退出', role: 'quit'}} 509 | ]) 510 | tray.popUpContextMenu(contextMenu) 511 | ``` 512 | 513 | 2. 事件 514 | 515 | 'click':点击托盘 516 | 'right-click':右击托盘 517 | 'drop-files':文件拖拽。类似的还有drop-text 518 | 'balloon-click':托盘气泡被点击(Windows特性) 519 | 520 | ### 4.6. clipboard 521 | 522 | 在系统剪贴板上执行复制和粘贴操作。 523 | 524 | ```js 525 | const { clipboard, nativeImage } = require('electron') 526 | 527 | // 将 image 写入剪贴板 528 | const dataUrl = this.selectRectMeta.base64Data 529 | const img = nativeImage.createFromDataURL(dataUrl) 530 | clipboard.writeImage(img) 531 | 532 | // 自动粘贴剪贴板上的图片 533 | function handlePaste(e) { 534 | const cbd = e.clipboardData 535 | if (!(e.clipboardData && e.clipboardData.items)) { 536 | return 537 | } 538 | for (let i = 0; i < cbd.items.length; i++) { 539 | const item = cbd.items[i] 540 | if (item.kind == 'file') { 541 | const blob = item.getAsFile() 542 | if (blob.size === 0) { 543 | return 544 | } 545 | const reader = new FileReader() 546 | const imgs = new Image() 547 | imgs.file = blob 548 | reader.onload = (e) => { 549 | const imgPath = e.target.result 550 | imgs.src = imgPath 551 | const eleHtml = `${html}` 552 | setHtml(eleHtml) 553 | } 554 | reader.readAsDataURL(blob) 555 | } 556 | } 557 | } 558 | 559 | ``` 560 | 561 | 562 | ### 4.7. screen 563 | 564 | 检索有关屏幕大小、显示器、光标位置等的信息。 565 | 566 | ```js 567 | // 创建填充整个屏幕的窗口的示例: 568 | const { app, BrowserWindow, screen } = require('electron') 569 | 570 | let win 571 | app.on('ready', () => { 572 | const { width, height } = screen.getPrimaryDisplay().workAreaSize 573 | win = new BrowserWindow({ width, height }) 574 | win.loadURL('https://github.com') 575 | }) 576 | 577 | ``` 578 | 579 | 580 | ### 4.8. globalShortcut 581 | 582 | 系统快捷键,监听键盘事件 583 | 584 | globalShortcut 模块可以在操作系统中注册/注销全局快捷键, 以便可以为操作定制各种快捷键。 585 | 586 | >注意: 快捷方式是全局的; 即使应用程序没有键盘焦点, 它也仍然在持续监听键盘事件。 在应用程序模块发出 ready 事件之前, 不应使用此模块。 587 | 588 | ```js 589 | const { app, globalShortcut } = require('electron') 590 | 591 | app.on('ready', () => { 592 | // 注册一个 'CommandOrControl+X' 的全局快捷键 593 | const ret = globalShortcut.register('CommandOrControl+X', () => { 594 | console.log('CommandOrControl+X is pressed') 595 | }) 596 | 597 | if (!ret) { 598 | console.log('registration failed') 599 | } 600 | 601 | // 检查快捷键是否注册成功 602 | console.log(globalShortcut.isRegistered('CommandOrControl+X')) 603 | }) 604 | 605 | app.on('will-quit', () => { 606 | // 注销快捷键 607 | globalShortcut.unregister('CommandOrControl+X') 608 | 609 | // 注销所有快捷键 610 | globalShortcut.unregisterAll() 611 | }) 612 | 613 | ``` 614 | 615 | ### 4.9. desktopCapturer 616 | 617 | 用于从桌面上捕获音频和视频的媒体源信息。 618 | 619 | ```js 620 | desktopCapturer.getSources({ 621 | types: ['screen', 'window'], 622 | thumbnailSize: { 623 | width, 624 | height 625 | } 626 | }).then( 627 | async (sources) => { 628 | const screenImgUrl = sources[0].thumbnail.toDataURL() 629 | 630 | const bg = document.querySelector('.bg') 631 | const rect = document.querySelector('.rect') 632 | const sizeInfo = document.querySelector('.size-info') 633 | const toolbar = document.querySelector('.toolbar') 634 | const draw = new Draw(screenImgUrl, bg, width, height, rect, sizeInfo, toolbar) 635 | document.addEventListener('mousedown', draw.startRect.bind(draw)) 636 | document.addEventListener('mousemove', draw.drawingRect.bind(draw)) 637 | document.addEventListener('mouseup', draw.endRect.bind(draw)) 638 | } 639 | ).catch(err => console.log('err', err)) 640 | ``` 641 | 642 | ### 4.10. shell 643 | 644 | 使用默认应用程序管理文件和 url。 645 | 646 | shell 模块提供与桌面集成相关的功能。 647 | 648 | 在用户的默认浏览器中打开 URL 的示例: 649 | 650 | ```js 651 | const { shell } = require('electron') 652 | 653 | shell.openExternal('https://github.com') 654 | 655 | ``` 656 | 657 | shell.showItemInFolder(fullPath) 658 | fullPath String 659 | 在文件管理器中显示给定的文件。如果可以, 选中该文件。 660 | 661 | shell.openItem(fullPath) 662 | fullPath String 663 | 返回 Boolean - 文件是否成功打开,以桌面的默认方式打开给定的文件。 664 | 665 | shell.beep() 666 | 播放哔哔的声音. 667 | 668 | ### 4.11. powerMonitor 电源监视器 669 | 670 | > 监视电源状态的改变。 671 | 672 | https://www.electronjs.org/docs/api/power-monitor 673 | 674 | ### 4.12. nativeTheme 读取并响应Chromium本地色彩主题中的变化 675 | 676 | 主进程监听主题变化,通知渲染进程增加dark-mode的class 677 | 678 | ```js 679 | nativeTheme.on('updated', function (e) { 680 | const darkMode = nativeTheme.shouldUseDarkColors; 681 | console.log('updateddarkMode', darkMode); 682 | send('change-mode', darkMode); 683 | }); 684 | 685 | function addDarkMode() { 686 | document.getElementsByTagName('body')[0].classList.add("dark-mode"); 687 | } 688 | 689 | function removeDarkMode() { 690 | document.getElementsByTagName('body')[0].classList.remove("dark-mode"); 691 | } 692 | 693 | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { 694 | addDarkMode(); 695 | } else { 696 | removeDarkMode(); 697 | } 698 | 699 | ipcRenderer.on('change-mode', (e, arg) => { 700 | console.log('change-mode') 701 | if (arg) { 702 | addDarkMode(); 703 | } else { 704 | removeDarkMode(); 705 | } 706 | }) 707 | ``` 708 | 709 | ## 5. 开机自启动 710 | 711 | ### 5.1. [node-auto-launch](https://github.com/Teamwork/node-auto-launch) 712 | 713 | 主进程main.js: 714 | 715 | ```js 716 | const AutoLaunch = require('auto-launch'); 717 | const demo = new AutoLaunch({ 718 | name: 'demo', 719 | //path: '/Applications/Minecraft.app', 720 | }); 721 | ``` 722 | 723 | 官方的范例里面写上了这个path。不写的话,是自动获取。写上的话,就是个固定的字符串。这个路径值很显然并不是固定的。 724 | 725 | 加入开机启动项 726 | 727 | ```js 728 | demo.enable(); 729 | ``` 730 | 731 | 移除开机启动项 732 | 733 | ```js 734 | demo.disable(); 735 | ``` 736 | 737 | 检测开机启动项状态 738 | 739 | ```js 740 | demo.isEnabled().then(function(isEnabled){ 741 | if(isEnabled){ 742 | return; 743 | } 744 | //demo.enable(); 745 | }) 746 | .catch(function(err){ 747 | // handle error 748 | }); 749 | ``` 750 | 751 | > 升级mac系统到catalina后报错 752 | 753 | ```text 754 | Error: 36:145: execution error: “System Events”遇到一个错误:应用程序没有运行。 (-600) 755 | 756 | at ChildProcess. (/Users/tangting/tt/github/electron-learn/node_modules/applescript/lib/applescript.js:49:13) 757 | at ChildProcess.emit (events.js:223:5) 758 | at Process.ChildProcess._handle.onexit (internal/child_process.js:272:12) { 759 | appleScript: 'tell application "System Events" to make login item at end with properties {path:"/Applications/spiderchat.app", hidden:false, name:"spiderchat"}', 760 | exitCode: 1 761 | } 762 | } 763 | ``` 764 | 765 | ### 5.2. app.getLoginItemSettings([options]) 766 | 767 | options 的值 768 | 769 | openAtLogin Boolean - true 如果应用程序设置为在登录时打开, 则设为true 770 | openAsHidden Boolean macOS - true 表示应用在登录时以隐藏的方式启动。 该配置在 MAS 构建 时不可用。 771 | wasOpenedAtLogin Boolean macOS - true 表示应用在自动登录后已经启动。 该配置在 MAS 构建 时不可用。 772 | wasOpenedAsHiddenBoolean macOS - 如果应用在登录时已经隐藏启动, 则为 true。 这表示应用程序在启动时不应打开任何窗口。 该配置在 MAS 构建 时不可用。 773 | restoreState Boolean macOS - true 表示应用作为登录启动项并且需要恢复之前的会话状态。 这表示程序应该还原上次关闭时打开的窗口。 该配置在 MAS 构建 时不可用。 774 | 775 | ## 6. 监控—crashReporter 776 | 777 | 可以用process.crash()模拟崩溃 778 | 779 | 崩溃报告发送 multipart/form-data POST 型的数据给 submitURL: 780 | 781 | ```js 782 | // 客户端 783 | crashReporter.start({ 784 | productName: 'spiderchat', 785 | companyName: 'spiderT', 786 | submitURL: 'http://127.0.0.1:9999/crash', 787 | }) 788 | 789 | // 服务端 790 | const multer = require('koa-multer') 791 | const uploadCrash = multer({ 792 | dest: 'crash/' 793 | }) 794 | router.post('/crash', uploadCrash.single('upload_file_minidump'), (ctx, next) => { 795 | console.log('crash', ctx.req.body) 796 | // todo 存DB 797 | }) 798 | ``` 799 | 800 | 崩溃报告解析 801 | 802 | 下载并解压 symbol https://github.com/electron/electron/releases 803 | 804 | • Mac electron-vX.X.X-darwin-x64-symbols.zip 805 | 806 | • Win electron-vX.X.X-win32-ia32-symbols.zip 807 | 808 | 解析 dmp 文件 809 | 810 | • node-minidump 811 | 812 | ```js 813 | const minidump = require('minidump'); 814 | const fs = require('fs'); 815 | 816 | // symbolpath 817 | minidump.addSymbolPath('/Users/tangting/tt/soft/electron-v9/breakpad_symbols/'); 818 | 819 | minidump.walkStack('./crash/aebd0e03e9f27f8e9d111f3aa8b67409',(err, res)=>{ 820 | fs.writeFileSync('./error.txt', res); 821 | }) 822 | ``` 823 | 824 | 825 | ## 7. 打包 826 | 827 | ### 7.1. [electron-builder](https://github.com/electron-userland/electron-builder) 828 | 829 | https://juejin.im/post/5bc53aade51d453df0447927 830 | 831 | 1. 在 package.json 应用程序中指定的标准字段 — name, description, version and author. 832 | 833 | 2. 在 package.json 添加 build 配置: 834 | 835 | ```json 836 | "build": { 837 | "appId": "your.id", 838 | "mac": { 839 | "category": "your.app.category.type" 840 | } 841 | } 842 | ``` 843 | 844 | See all options. Option files to indicate which files should be packed in the final application, including the entry file, maybe required. 845 | 846 | 3. 添加 icons. 847 | 848 | 制作 icns 图标 849 | 850 | - brew install makeicns 851 | 852 | - makeicns -in input.jpg -output out.icns 853 | 854 | 4. 在 package.json 添加 scripts 命令: 855 | 856 | ```json 857 | "scripts": { 858 | "pack": "electron-builder --dir", 859 | "dist": "electron-builder" 860 | } 861 | ``` 862 | 863 | #### 打包中遇到的问题 864 | 865 | 1. electron-builder打包不成功解决方法 866 | 867 | 参考:https://blog.csdn.net/weixin_41779718/article/details/106562736 868 | 869 | 2. 证书签名问题: Error signing Distribution iOS app “unable to build chain to self-signed root for signer” 870 | 871 | 参考:https://stackoverflow.com/questions/65996659/error-signing-distribution-ios-app-unable-to-build-chain-to-self-signed-root-fo 872 | 873 | #### 命令行参数(CLI) 874 | 875 | - Commands(命令): 876 | 877 | ```text 878 | electron-builder build 构建命名 [default] 879 | electron-builder install-app-deps 下载app依赖 880 | electron-builder node-gyp-rebuild 重建自己的本机代码 881 | electron-builder create-self-signed-cert 为Windows应用程序创建自签名代码签名证书 882 | electron-builder start 使用electronic-webpack在开发模式下运行应用程序(须臾要electron-webpack模块支持) 883 | 884 | ``` 885 | 886 | - Building(构建参数): 887 | 888 | ```text 889 | --mac, -m, -o, --macos Build for macOS, [array] 890 | --linux, -l Build for Linux [array] 891 | --win, -w, --windows Build for Windows [array] 892 | --x64 Build for x64 (64位安装包) [boolean] 893 | --ia32 Build for ia32(32位安装包) [boolean] 894 | --armv7l Build for armv7l [boolean] 895 | --arm64 Build for arm64 [boolean] 896 | --dir Build unpacked dir. Useful to test. [boolean] 897 | --prepackaged, --pd 预打包应用程序的路径(以可分发的格式打包) 898 | --projectDir, --project 项目目录的路径。 默认为当前工作目录。 899 | --config, -c 配置文件路径。 默认为`electron-builder.yml`(或`js`,或`js5`) 900 | 901 | ``` 902 | 903 | - Publishing(发布): 904 | 905 | ```text 906 | --publish, -p 发布到GitHub Releases [choices: "onTag", "onTagOrDraft", "always", "never", undefined] 907 | 908 | ``` 909 | 910 | - Other(其他): 911 | 912 | ```text 913 | --help Show help [boolean] 914 | --version Show version number [boolean] 915 | ``` 916 | 917 | - Examples(例子): 918 | 919 | ```text 920 | electron-builder -mwl 为macOS,Windows和Linux构建(同时构建) 921 | electron-builder --linux deb tar.xz 为Linux构建deb和tar.xz 922 | electron-builder -c.extraMetadata.foo=bar 将package.js属性`foo`设置为`bar` 923 | electron-builder --config.nsis.unicode=false 为NSIS配置unicode选项 924 | 925 | ``` 926 | 927 | - TargetConfiguration(构建目标配置): 928 | 929 | ```js 930 | target: String - 目标名称,例如snap. 931 | arch “x64” | “ia32” | “armv7l” | “arm64”> | “x64” | “ia32” | “armv7l” | “arm64” -arch支持列表 932 | 933 | ``` 934 | 935 | ## 8. 集成c++ 936 | 937 | 安装 938 | 939 | ```js 940 | 941 | npm install -g --production windows-build-tools 942 | 943 | npm install -g node-gyp 944 | 945 | ``` 946 | 947 | 写c++, 来自 node 官网文档 948 | 949 | ```c 950 | 951 | #include 952 | 953 | namespace demo { 954 | 955 | using v8::FunctionCallbackInfo; 956 | 957 | using v8::Isolate; 958 | 959 | using v8::Local; 960 | 961 | using v8::Object; 962 | 963 | using v8::String; 964 | 965 | using v8::Value; 966 | 967 | void Method(const FunctionCallbackInfo& args) { 968 | 969 | Isolate* isolate = args.GetIsolate(); 970 | 971 | args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world")); 972 | 973 | } 974 | 975 | void init(Localexports) { 976 | 977 | NODE_SET_METHOD(exports, "hello", Method); 978 | 979 | } 980 | 981 | NODE_MODULE(addon, init) 982 | 983 | } // namespace demo 984 | 985 | ``` 986 | 987 | 加个配置文件binding.gyp 988 | 989 | ```json 990 | { 991 | "targets": [{ 992 | "target_name": "addon", 993 | "sources": [ "hello.cc"] 994 | }] 995 | } 996 | ``` 997 | 998 | 然后 999 | 1000 | ```js 1001 | node-gyp configure 1002 | npm install 1003 | ``` 1004 | 1005 | 在js里使用 1006 | 1007 | ```js 1008 | const addon = require("./build/Release/addon"); 1009 | console.log(addon.hello()); 1010 | ``` 1011 | 1012 | ## 9. 测试和调试 1013 | 1014 | ### 9.1. 调试debug 1015 | 1016 | 1. 渲染进程调试 1017 | 1018 | win.webContents.openDevTools() 1019 | 1020 | 打开控制台,跟web页面调试一样 1021 | 1022 | 1023 | 2. 主进程调试 1024 | 1025 | ./node_modules/.bin/electron . --inspect=[port] //port 不设置,默认是5858 1026 | 1027 | 通过访问 chrome://inspect 来连接 Chrome 并在那里选择需要检查的Electron 应用程序 1028 | 1029 | 1030 | ### 9.2. 自动化测试 1031 | 1032 | 1. [spectron](https://www.electronjs.org/spectron) 1033 | 1034 | ```js 1035 | # Install Spectron 1036 | $ npm install --save-dev spectron 1037 | 1038 | // A simple test to verify a visible window is opened with a title 1039 | const Application = require('spectron').Application 1040 | const assert = require('assert') 1041 | 1042 | const app = new Application({ 1043 | path: '/Applications/MyApp.app/Contents/MacOS/MyApp' 1044 | }) 1045 | 1046 | app.start().then(function () { 1047 | // Check if the window is visible 1048 | return app.browserWindow.isVisible() 1049 | }).then(function (isVisible) { 1050 | // Verify the window is visible 1051 | assert.equal(isVisible, true) 1052 | }).then(function () { 1053 | // Get the window's title 1054 | return app.client.getTitle() 1055 | }).then(function (title) { 1056 | // Verify the window's title 1057 | assert.equal(title, 'My App') 1058 | }).then(function () { 1059 | // Stop the application 1060 | return app.stop() 1061 | }).catch(function (error) { 1062 | // Log any failures 1063 | console.error('Test failed', error.message) 1064 | }) 1065 | ``` 1066 | 1067 | 2. [WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) 1068 | 1069 | ```js 1070 | const webdriver = require('selenium-webdriver') 1071 | const driver = new webdriver.Builder() 1072 | // "9515" 是ChromeDriver使用的端口 1073 | .usingServer('http://localhost:9515') 1074 | .withCapabilities({ 1075 | chromeOptions: { 1076 | // 这里设置Electron的路径 1077 | binary: '/Path-to-Your-App.app/Contents/MacOS/Electron' 1078 | } 1079 | }) 1080 | .forBrowser('electron') 1081 | .build() 1082 | driver.get('http://www.google.com') 1083 | driver.findElement(webdriver.By.name('q')).sendKeys('webdriver') 1084 | driver.findElement(webdriver.By.name('btnG')).click() 1085 | driver.wait(() => { 1086 | return driver.getTitle().then((title) => { 1087 | return title === 'webdriver - Google Search' 1088 | }) 1089 | }, 1000) 1090 | driver.quit() 1091 | ``` 1092 | 1093 | ## 10. 更新 1094 | 1095 | 客户端使用autoUpdater 1096 | 1097 | ```js 1098 | const { 1099 | autoUpdater, 1100 | app, 1101 | dialog 1102 | } = require('electron'); 1103 | 1104 | if (process.platform == 'darwin') { 1105 | autoUpdater.setFeedURL('http://127.0.0.1:9999/darwin?version=' + app.getVersion()) 1106 | } else { 1107 | autoUpdater.setFeedURL('http://127.0.0.1:9999/win32?version=' + app.getVersion()) 1108 | } 1109 | 1110 | // 定时轮训、服务端推送 1111 | autoUpdater.checkForUpdates(); 1112 | autoUpdater.on('update-available', () => { 1113 | console.log('update-available') 1114 | }) 1115 | 1116 | autoUpdater.on('update-downloaded', (e, notes, version) => { 1117 | // 提醒用户更新 1118 | app.whenReady().then(() => { 1119 | const clickId = dialog.showMessageBoxSync({ 1120 | type: 'info', 1121 | title: '升级提示', 1122 | message: '已为你升级到最新版,是否立即体验', 1123 | buttons: ['马上升级', '手动重启'], 1124 | cancelId: 1, 1125 | }) 1126 | if (clickId === 0) { 1127 | autoUpdater.quitAndInstall() 1128 | app.quit() 1129 | } 1130 | }) 1131 | }) 1132 | 1133 | autoUpdater.on('error', (err) => { 1134 | console.log('error', err) 1135 | }) 1136 | ``` 1137 | 1138 | 服务端 1139 | 1140 | ```js 1141 | function getNewVersion(version) { 1142 | if (!version) return null; 1143 | const maxVersion = { 1144 | name: '1.0.1', 1145 | pub_date: '2020-06-09T12:26:53+1:00', 1146 | notes: '新增功能: 菜单栏改成红色', 1147 | url: `http://127.0.0.1:9999/public/spiderchat-1.0.1-mac.zip`, 1148 | }; 1149 | if (compareVersions.compare(maxVersion.name, version, '>')) { 1150 | return maxVersion; 1151 | } 1152 | return null; 1153 | } 1154 | 1155 | router.get('/darwin', (ctx, next) => { 1156 | // 处理Mac更新, ?version=1.0.0&uid=123 1157 | const { version } = ctx.query; 1158 | const newVersion = getNewVersion(version); 1159 | if (newVersion) { 1160 | ctx.body = newVersion; 1161 | } else { 1162 | ctx.status = 204; 1163 | } 1164 | }); 1165 | ``` 1166 | 1167 | ## 11. Electron客户端的安全:从xss到rce 1168 | 1169 | 1170 | 1171 | ## 12. 浏览器启动客户端 1172 | 1173 | > 原理 1174 | 浏览器在解析url的时候,会尝试从系统本地寻找url协议所关联的应用,如果有关联的应用,则尝试打开这个应用 1175 | 1176 | ### 12.1. windows平台 1177 | 1178 | 在windows下,注册一个协议比较简单,写注册表就可以了。参考 Registering an Application to a URI Scheme:https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85) 1179 | 1180 | ### 12.2. mac 平台 1181 | 1182 | 几个基本概念 1183 | 1184 | #### 12.2.1. info.plist 1185 | 1186 | iOS和MacOS的应用包中,都有一个info.plist文件,这个文件主要用来记录应用的一些meta信息,参考[Information Property List](https://developer.apple.com/documentation/bundleresources/information_property_list)。文件用键值对的形式来记录信息(xml),结构如下: 1187 | 1188 | **CFBundleURLTypes** 1189 | 1190 | A list of URL schemes (http, ftp, and so on) supported by the app. 1191 | 1192 | 其实就是info.plist里面的一个key,对应的value是一个数组。可以通过这个字段来为应用注册一个 or 多个 URL Schema。参考[CFBundleURLTypes](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleurltypes#details) 1193 | 1194 | > 修改info.plist文件 1195 | 1196 | 只需为App包中info.plist,设置CFBundleURLTypes的值即可. https://zhuanlan.zhihu.com/p/76172940 1197 | 1198 | 通过 extendInfo 中添加数组, 数组中的 值将会被写入 Info.plist 文件中。 1199 | 1200 | ```json 1201 | "build": { 1202 | "appId": "com.spider.chat", 1203 | "mac": { 1204 | "category": "spider", 1205 | "icon": "src/resources/icns/spider.icns", 1206 | "extendInfo": { 1207 | "CFBundleURLSchemes": [ 1208 | "spiderlink" 1209 | ] 1210 | } 1211 | }, 1212 | ``` 1213 | 1214 | ### 12.3. 接收参数 1215 | 1216 | 协议注册完毕之后,我们已经可以在浏览器中,通过访问自定义协议url来启动客户端了。 1217 | 1218 | 对于url中的不同参数,客户端的行为也是不一样的,例如vscode:extension/ms-python.python这个url,启动了VsCode的同时也告诉了VsCode:我要安装插件,插件名是ms-phthon.python。 1219 | 1220 | Vscode通过解析url中的参数来实现自定义行为,那么作为客户端如何拿到这个url呢? 1221 | 1222 | ### 12.3.1. Windows 1223 | 1224 | 参数会通过启动参数的形式传递给应用程序。因此,我们可以很方便的拿到这个参数 1225 | 1226 | ```js 1227 | // 通过自定义url启动客户端时 1228 | console.log(process.argv); 1229 | 1230 | // 打印出 1231 | [ 1232 | 'C://your-app.exe', // 启动路径 1233 | 'kujiale://111', // 启动的自定义url 1234 | ] 1235 | ``` 1236 | 1237 | ### 12.3.2. MacOS 1238 | 1239 | 在Mac下不会通过启动参数传递给应用,通过自定义协议打开应用,app会收到 open-url 事件 1240 | 1241 | ```js 1242 | // mac下通过kujiale协议启动应用 1243 | app.on('open-url', (e, url) => { // eslint-disable-line 1244 | parse(url); 解析url 1245 | }); 1246 | ``` 1247 | 1248 | 1249 | ## 13. 性能优化 1250 | 1251 | ### 13.1. 减少包体积大小 1252 | 1253 | yarn autoclean -I 1254 | 1255 | yarn autoclean -F 1256 | 1257 | 1258 | 1259 | ## Electron 开发过程中可能会遇到的几个问题和场景。 1260 | 1261 | - 启动时间优化 1262 | Electron 应用创建窗口之后,由于需要初始化窗口,加载 html,js 以及各种依赖,会出现一个短暂的白屏。除了传统的,比如说延迟 js 加载等 web 性能优化的方法,在 Electron 中还可以使用一种方式,就是在 close 窗口之前缓存 index 页面,下次再打开窗口的时候直接加载缓存好的页面,这样就会提前页面渲染的时间,缩短白屏时间。 1263 | 1264 | 但是,优化之后也还是会有白屏出现,对于这段时间可以用一个比较 tricky 的方法,就是让窗口监听 ready-to-show 事件,等到页面完成首次绘制后,再显示窗口。这样,虽然延迟了窗口显示时间,总归不会有白屏出现了。 1265 | 1266 | 1. 在 ready-to-show 时候再显示 1267 | 设置窗口底色 1268 | 1269 | 2. [实现占位图](https://github.com/dengyaolong/electron-loading-window-example) 1270 | 1271 | BrowserView、BrowserWindow、ChildWindow 1272 | 1273 | - CPU 密集型任务处理 1274 | 对于 cpu 密集型或者 long-running 的 task,我们肯定不希望它们阻塞主进程或者影响渲染进程页面的渲染,这时候就需要在其他进程中执行这些任务。通常有三种方式: 1275 | 1276 | 1. 使用 child_process 模块,spawn 或者 fork 一个子进程; 1277 | 2. WebWorker; 1278 | 3. Backgroundprocess。在 Electron 应用中,我们可以创建一个隐藏的 Browser Window 作为 background process,这种方法的优势就在于它本身就是一个渲染进程,所以可以使用 Electron 和 Node.js 提供的所有 api。 1279 | 1280 | - 数据持久化存储 1281 | 为了使应用在 offline 的情况下也可以正常运行,对于桌面应用,我们会将一些数据存储到本地,常见方式有: 1282 | 1283 | localStorage。对于渲染进程中的数据,可以存到 localStorage 中。需要注意的是主进程是无法获取的。 1284 | 嵌入式数据库。我们也可以直接打包一个嵌入式数据库到应用中,比如说 SQLite,nedb,这种方式比较适合大规模数据的存储以及增删改查。 1285 | 对于简易的配置或者用户数据,可以使用 electron-config 等模块,将数据以 JSON 格式保存到文件中。 1286 | 1287 | - 安全性考虑 1288 | 在 Electron 应用中,web 页面是可以直接调用 Node.js api 的,这样就可以做很多事情,比如说操作文件系统,但同时也会带来安全隐患,建议大家渲染进程中禁用 NodeJS 集成。 1289 | 1290 | 如果需要在页面中使用 node 或者 electron 的 api,可以通过提前加载一个 preload.js 作为 bridge,这个 js 会在所有页面 js 运行前被执行。我们可以在里面做很多事情,比如说把需要的 node 方法放到 global 或者 window 中,这样页面中就没办法直接使用 node 模块,但是又可以使用需要的某些功能,如下图所示。 1291 | 1292 | Electron在DevTools中的探索与实践 1293 | 1294 | 除此之外,还要注意,使用安全的协议,比如说 https 加载外部资源。在 Electron 应用中,可以通过监听新窗口创建和页面跳转事件,判断是否是安全跳转,加以限制。亦可以通过设置 CSP,对指定 URL 的访问进行约束。 1295 | 1296 | - 应用体积优化 1297 | 对于 Electron 应用打包,首先会使用 webpack 分别对主进程和渲染进程代码进行处理优化,和 web 应用一样。有点区别的地方是配置中主进程的 target 是 electron-main, 渲染进程的 target 是 electron-renderer。除此之外,还要对 node 做一些配置,我们是不需要 webpack 来 polyfill 或者 mocknode 的全局变量和模块的,所以设为 false。 1298 | 1299 | 之后,在基于 electron-builder 将应用 build 成不同平台的安装包,需要注意的是,对于 package.json,尽可能地把可以打包到 bundle 的依赖模块,从 dependencies 移到 devDependencies,因为所有 dependencies 中的模块都会被打到安装包中,会严重增大安装包体积。 1300 | 1301 | ##### NPM 下载的问题 1302 | 1303 | 因为 NPM 在国内比较慢。导致 electron-V.xxxx.zip 下载失败。这些东西如果是第一次打包的话是需要下载对应 electron 版本的支持文件。解决办法有两个 1304 | 1305 | 1. 设置镜像:在.npmrc 文件。然后加入下面这句代码 1306 | 1307 | ```text 1308 | electron_mirror=http://npm.taobao.org/mirrors/electron/ 1309 | ``` 1310 | 1311 | 2. 直接去淘宝镜像文件库找到对应的文件并下载,放到指定的目录下,electron 的淘宝镜像地址。下载完之后放到指定的文件。 1312 | 1313 | 1314 | ### 热重载 1315 | 1316 | webpack的electron配置 1317 | 1318 | ```js 1319 | 'use strict' 1320 | 1321 | const chalk = require('chalk') 1322 | const electron = require('electron') 1323 | const path = require('path') 1324 | const { say } = require('cfonts') 1325 | const { spawn } = require('child_process') 1326 | const webpack = require('webpack') 1327 | const WebpackDevServer = require('webpack-dev-server') 1328 | const webpackHotMiddleware = require('webpack-hot-middleware') 1329 | 1330 | const mainConfig = require('./webpack.main.config') 1331 | const rendererConfig = require('./webpack.renderer.config') 1332 | 1333 | let electronProcess = null 1334 | let manualRestart = false 1335 | let hotMiddleware 1336 | 1337 | function logStats(proc, data) { 1338 | let log = '' 1339 | 1340 | log += chalk.yellow.bold( 1341 | `┏ ${proc} Process ${new Array(19 - proc.length + 1).join('-')}` 1342 | ) 1343 | log += '\n\n' 1344 | 1345 | if (typeof data === 'object') { 1346 | data 1347 | .toString({ 1348 | colors: true, 1349 | chunks: false 1350 | }) 1351 | .split(/\r?\n/) 1352 | .forEach(line => { 1353 | log += ' ' + line + '\n' 1354 | }) 1355 | } else { 1356 | log += ` ${data}\n` 1357 | } 1358 | 1359 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' 1360 | 1361 | console.log(log) 1362 | } 1363 | 1364 | function startRenderer() { 1365 | return new Promise((resolve, reject) => { 1366 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat( 1367 | rendererConfig.entry.renderer 1368 | ) 1369 | rendererConfig.mode = 'development' 1370 | const compiler = webpack(rendererConfig) 1371 | hotMiddleware = webpackHotMiddleware(compiler, { 1372 | log: false, 1373 | heartbeat: 2500 1374 | }) 1375 | 1376 | compiler.hooks.compilation.tap('compilation', compilation => { 1377 | compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync( 1378 | 'html-webpack-plugin-after-emit', 1379 | (data, cb) => { 1380 | hotMiddleware.publish({ action: 'reload' }) 1381 | cb() 1382 | } 1383 | ) 1384 | }) 1385 | 1386 | compiler.hooks.done.tap('done', stats => { 1387 | logStats('Renderer', stats) 1388 | }) 1389 | 1390 | const server = new WebpackDevServer(compiler, { 1391 | hot: true, 1392 | contentBase: path.join(__dirname, '../'), 1393 | quiet: true, 1394 | before(app, ctx) { 1395 | app.use(hotMiddleware) 1396 | ctx.middleware.waitUntilValid(() => { 1397 | resolve() 1398 | }) 1399 | } 1400 | }) 1401 | 1402 | server.listen(9080) 1403 | }) 1404 | } 1405 | 1406 | function startMain() { 1407 | return new Promise((resolve, reject) => { 1408 | mainConfig.entry.main = [ 1409 | path.join(__dirname, '../src/main/index.dev.js') 1410 | ].concat(mainConfig.entry.main) 1411 | mainConfig.mode = 'development' 1412 | const compiler = webpack(mainConfig) 1413 | 1414 | compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => { 1415 | logStats('Main', chalk.white.bold('compiling...')) 1416 | hotMiddleware.publish({ action: 'compiling' }) 1417 | done() 1418 | }) 1419 | 1420 | compiler.watch({}, (err, stats) => { 1421 | if (err) { 1422 | console.log(err) 1423 | return 1424 | } 1425 | 1426 | logStats('Main', stats) 1427 | 1428 | if (electronProcess && electronProcess.kill) { 1429 | manualRestart = true 1430 | process.kill(electronProcess.pid) 1431 | electronProcess = null 1432 | startElectron() 1433 | 1434 | setTimeout(() => { 1435 | manualRestart = false 1436 | }, 5000) 1437 | } 1438 | 1439 | resolve() 1440 | }) 1441 | }) 1442 | } 1443 | 1444 | function startElectron() { 1445 | var args = [path.join(__dirname, '../dist/electron/main.js')] 1446 | 1447 | // detect yarn or npm and process commandline args accordingly 1448 | if (process.env.npm_execpath.endsWith('yarn.js')) { 1449 | args = args.concat(process.argv.slice(3)) 1450 | } else if (process.env.npm_execpath.endsWith('npm-cli.js')) { 1451 | args = args.concat(process.argv.slice(2)) 1452 | } 1453 | 1454 | electronProcess = spawn(electron, args) 1455 | 1456 | electronProcess.stdout.on('data', data => { 1457 | electronLog(data, 'blue') 1458 | }) 1459 | electronProcess.stderr.on('data', data => { 1460 | electronLog(data, 'red') 1461 | }) 1462 | 1463 | electronProcess.on('close', () => { 1464 | if (!manualRestart) process.exit() 1465 | }) 1466 | } 1467 | 1468 | function electronLog(data, color) { 1469 | let log = '' 1470 | data = data.toString().split(/\r?\n/) 1471 | data.forEach(line => { 1472 | log += ` ${line}\n` 1473 | }) 1474 | if (/[0-9A-z]+/.test(log)) { 1475 | console.log( 1476 | chalk[color].bold('┏ Electron -------------------') + 1477 | '\n\n' + 1478 | log + 1479 | chalk[color].bold('┗ ----------------------------') + 1480 | '\n' 1481 | ) 1482 | } 1483 | } 1484 | 1485 | function greeting() { 1486 | const cols = process.stdout.columns 1487 | let text = '' 1488 | 1489 | if (cols > 104) text = 'electron-react' 1490 | else if (cols > 76) text = 'electron-|react' 1491 | else text = false 1492 | 1493 | if (text) { 1494 | say(text, { 1495 | colors: ['yellow'], 1496 | font: 'simple3d', 1497 | space: false 1498 | }) 1499 | } else console.log(chalk.yellow.bold('\n electron-react')) 1500 | console.log(chalk.blue(' getting ready...') + '\n') 1501 | } 1502 | 1503 | function init() { 1504 | greeting() 1505 | 1506 | Promise.all([startRenderer(), startMain()]) 1507 | .then(() => { 1508 | startElectron() 1509 | }) 1510 | .catch(err => { 1511 | console.error(err) 1512 | }) 1513 | } 1514 | init() 1515 | 1516 | ``` 1517 | 1518 | 1519 | 1520 | ### 参考资料 1521 | 1522 | 1. electron 优化 https://juejin.im/post/5e0010866fb9a015fd69c645 1523 | 1524 | Electron的主进程阻塞导致UI卡顿的问题 https://zhuanlan.zhihu.com/p/37050595 1525 | 1526 | 2. 打包:mac 文件签名:https://www.cnblogs.com/lovesong/p/11782449.html 1527 | https://www.cnblogs.com/qirui/p/8327812.html 1528 | 1529 | 3. 集成c++ 1530 | https://www.jianshu.com/p/93ffa05f028f 1531 | https://blog.csdn.net/wang839305939/article/details/83780789 1532 | https://www.jianshu.com/p/5a4c7ce2be54 1533 | https://www.dazhuanlan.com/2019/09/23/5d88a0bc8ec13/ 1534 | https://stackoverflow.com/questions/32986826/calling-node-native-addons-c-in-electron 1535 | 1536 | 4. 崩溃报告上传 https://juejin.im/post/5c5ee47be51d457f95354c82 1537 | https://www.electronjs.org/docs/api/crash-reporter 1538 | 1539 | 5. debugger https://cloud.tencent.com/developer/section/1116142 1540 | 1541 | 6. 测试和调试 https://www.bookstack.cn/read/electron-v6.0-zh/dda8a7a000404b49.md 1542 | -------------------------------------------------------------------------------- /demos/child.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 16 | 17 |

Hello Electron

18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello World! 8 | 9 | 10 |

Hello World!

11 |
12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /demos/main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, ipcMain, screen } = require('electron'); 2 | const path = require('path'); 3 | 4 | function createWindow() { 5 | const { width, height } = screen.getPrimaryDisplay().workAreaSize; 6 | 7 | const mainWindow = new BrowserWindow({ 8 | // width, 9 | // height, 10 | webPreferences: { 11 | nodeIntegration: true, 12 | contextIsolation: false, 13 | enableRemoteModule: true, 14 | }, 15 | }); 16 | 17 | mainWindow.loadFile('index.html'); 18 | } 19 | 20 | app.whenReady().then(() => { 21 | createWindow(); 22 | 23 | app.on('activate', function () { 24 | if (BrowserWindow.getAllWindows().length === 0) createWindow(); 25 | }); 26 | }); 27 | 28 | app.on('window-all-closed', function () { 29 | if (process.platform !== 'darwin') app.quit(); 30 | }); 31 | 32 | function createFrameless(options) { 33 | const win = new BrowserWindow({ 34 | ...{ 35 | width: 300, 36 | height: 300, 37 | }, 38 | ...options, 39 | }); 40 | win.loadURL(path.join('file:', __dirname, './child.html')); 41 | } 42 | 43 | ipcMain.on('create-win-1', () => createFrameless({ frame: false })); 44 | 45 | ipcMain.on('create-win-2', () => createFrameless({ titleBarStyle: 'hidden' })); 46 | 47 | ipcMain.on('create-win-3', () => createFrameless({ titleBarStyle: 'hiddenInset' })); 48 | 49 | ipcMain.on('create-win-4', () => createFrameless({ titleBarStyle: 'customButtonsOnHover', frame: false })); 50 | 51 | ipcMain.on('create-win-5', () => createFrameless({ transparent: true, frame: false })); 52 | -------------------------------------------------------------------------------- /demos/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-quick-start", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@electron/get": { 8 | "version": "1.12.2", 9 | "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.2.tgz", 10 | "integrity": "sha512-vAuHUbfvBQpYTJ5wB7uVIDq5c/Ry0fiTBMs7lnEYAo/qXXppIVcWdfBr57u6eRnKdVso7KSiH6p/LbQAG6Izrg==", 11 | "dev": true, 12 | "requires": { 13 | "debug": "^4.1.1", 14 | "env-paths": "^2.2.0", 15 | "fs-extra": "^8.1.0", 16 | "global-agent": "^2.0.2", 17 | "global-tunnel-ng": "^2.7.1", 18 | "got": "^9.6.0", 19 | "progress": "^2.0.3", 20 | "sanitize-filename": "^1.6.2", 21 | "sumchecker": "^3.0.1" 22 | } 23 | }, 24 | "@sindresorhus/is": { 25 | "version": "0.14.0", 26 | "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", 27 | "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", 28 | "dev": true 29 | }, 30 | "@szmarczak/http-timer": { 31 | "version": "1.1.2", 32 | "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", 33 | "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", 34 | "dev": true, 35 | "requires": { 36 | "defer-to-connect": "^1.0.1" 37 | } 38 | }, 39 | "@types/node": { 40 | "version": "12.12.47", 41 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.47.tgz", 42 | "integrity": "sha512-yzBInQFhdY8kaZmqoL2+3U5dSTMrKaYcb561VU+lDzAYvqt+2lojvBEy+hmpSNuXnPTx7m9+04CzWYOUqWME2A==", 43 | "dev": true 44 | }, 45 | "boolean": { 46 | "version": "3.0.1", 47 | "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz", 48 | "integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==", 49 | "dev": true, 50 | "optional": true 51 | }, 52 | "buffer-crc32": { 53 | "version": "0.2.13", 54 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 55 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", 56 | "dev": true 57 | }, 58 | "buffer-from": { 59 | "version": "1.1.1", 60 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 61 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 62 | "dev": true 63 | }, 64 | "cacheable-request": { 65 | "version": "6.1.0", 66 | "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", 67 | "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", 68 | "dev": true, 69 | "requires": { 70 | "clone-response": "^1.0.2", 71 | "get-stream": "^5.1.0", 72 | "http-cache-semantics": "^4.0.0", 73 | "keyv": "^3.0.0", 74 | "lowercase-keys": "^2.0.0", 75 | "normalize-url": "^4.1.0", 76 | "responselike": "^1.0.2" 77 | }, 78 | "dependencies": { 79 | "get-stream": { 80 | "version": "5.1.0", 81 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", 82 | "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", 83 | "dev": true, 84 | "requires": { 85 | "pump": "^3.0.0" 86 | } 87 | }, 88 | "lowercase-keys": { 89 | "version": "2.0.0", 90 | "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", 91 | "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", 92 | "dev": true 93 | } 94 | } 95 | }, 96 | "clone-response": { 97 | "version": "1.0.2", 98 | "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", 99 | "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", 100 | "dev": true, 101 | "requires": { 102 | "mimic-response": "^1.0.0" 103 | } 104 | }, 105 | "concat-stream": { 106 | "version": "1.6.2", 107 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 108 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 109 | "dev": true, 110 | "requires": { 111 | "buffer-from": "^1.0.0", 112 | "inherits": "^2.0.3", 113 | "readable-stream": "^2.2.2", 114 | "typedarray": "^0.0.6" 115 | } 116 | }, 117 | "config-chain": { 118 | "version": "1.1.12", 119 | "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", 120 | "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", 121 | "dev": true, 122 | "optional": true, 123 | "requires": { 124 | "ini": "^1.3.4", 125 | "proto-list": "~1.2.1" 126 | } 127 | }, 128 | "core-js": { 129 | "version": "3.6.5", 130 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", 131 | "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", 132 | "dev": true, 133 | "optional": true 134 | }, 135 | "core-util-is": { 136 | "version": "1.0.2", 137 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 138 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 139 | "dev": true 140 | }, 141 | "debug": { 142 | "version": "4.1.1", 143 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 144 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 145 | "dev": true, 146 | "requires": { 147 | "ms": "^2.1.1" 148 | } 149 | }, 150 | "decompress-response": { 151 | "version": "3.3.0", 152 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", 153 | "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", 154 | "dev": true, 155 | "requires": { 156 | "mimic-response": "^1.0.0" 157 | } 158 | }, 159 | "defer-to-connect": { 160 | "version": "1.1.3", 161 | "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", 162 | "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", 163 | "dev": true 164 | }, 165 | "define-properties": { 166 | "version": "1.1.3", 167 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 168 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 169 | "dev": true, 170 | "optional": true, 171 | "requires": { 172 | "object-keys": "^1.0.12" 173 | } 174 | }, 175 | "detect-node": { 176 | "version": "2.0.4", 177 | "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", 178 | "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", 179 | "dev": true, 180 | "optional": true 181 | }, 182 | "duplexer3": { 183 | "version": "0.1.4", 184 | "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", 185 | "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", 186 | "dev": true 187 | }, 188 | "electron": { 189 | "version": "9.0.3", 190 | "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.3.tgz", 191 | "integrity": "sha512-rY59wy50z0oWp/q69zq0UIzvtcM5j2BJbLAwEoLfVNS3DLt9wDZqRqSIBvLEBl+xWbafCnRA9haEqi7ssM94GA==", 192 | "dev": true, 193 | "requires": { 194 | "@electron/get": "^1.0.1", 195 | "@types/node": "^12.0.12", 196 | "extract-zip": "^1.0.3" 197 | } 198 | }, 199 | "encodeurl": { 200 | "version": "1.0.2", 201 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 202 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 203 | "dev": true, 204 | "optional": true 205 | }, 206 | "end-of-stream": { 207 | "version": "1.4.4", 208 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 209 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 210 | "dev": true, 211 | "requires": { 212 | "once": "^1.4.0" 213 | } 214 | }, 215 | "env-paths": { 216 | "version": "2.2.0", 217 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", 218 | "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", 219 | "dev": true 220 | }, 221 | "es6-error": { 222 | "version": "4.1.1", 223 | "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", 224 | "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", 225 | "dev": true, 226 | "optional": true 227 | }, 228 | "escape-string-regexp": { 229 | "version": "4.0.0", 230 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 231 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 232 | "dev": true, 233 | "optional": true 234 | }, 235 | "extract-zip": { 236 | "version": "1.7.0", 237 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", 238 | "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", 239 | "dev": true, 240 | "requires": { 241 | "concat-stream": "^1.6.2", 242 | "debug": "^2.6.9", 243 | "mkdirp": "^0.5.4", 244 | "yauzl": "^2.10.0" 245 | }, 246 | "dependencies": { 247 | "debug": { 248 | "version": "2.6.9", 249 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 250 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 251 | "dev": true, 252 | "requires": { 253 | "ms": "2.0.0" 254 | } 255 | }, 256 | "ms": { 257 | "version": "2.0.0", 258 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 259 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 260 | "dev": true 261 | } 262 | } 263 | }, 264 | "fd-slicer": { 265 | "version": "1.1.0", 266 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 267 | "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", 268 | "dev": true, 269 | "requires": { 270 | "pend": "~1.2.0" 271 | } 272 | }, 273 | "fs-extra": { 274 | "version": "8.1.0", 275 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", 276 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", 277 | "dev": true, 278 | "requires": { 279 | "graceful-fs": "^4.2.0", 280 | "jsonfile": "^4.0.0", 281 | "universalify": "^0.1.0" 282 | } 283 | }, 284 | "get-stream": { 285 | "version": "4.1.0", 286 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", 287 | "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", 288 | "dev": true, 289 | "requires": { 290 | "pump": "^3.0.0" 291 | } 292 | }, 293 | "global-agent": { 294 | "version": "2.1.12", 295 | "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.12.tgz", 296 | "integrity": "sha512-caAljRMS/qcDo69X9BfkgrihGUgGx44Fb4QQToNQjsiWh+YlQ66uqYVAdA8Olqit+5Ng0nkz09je3ZzANMZcjg==", 297 | "dev": true, 298 | "optional": true, 299 | "requires": { 300 | "boolean": "^3.0.1", 301 | "core-js": "^3.6.5", 302 | "es6-error": "^4.1.1", 303 | "matcher": "^3.0.0", 304 | "roarr": "^2.15.3", 305 | "semver": "^7.3.2", 306 | "serialize-error": "^7.0.1" 307 | } 308 | }, 309 | "global-tunnel-ng": { 310 | "version": "2.7.1", 311 | "resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz", 312 | "integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==", 313 | "dev": true, 314 | "optional": true, 315 | "requires": { 316 | "encodeurl": "^1.0.2", 317 | "lodash": "^4.17.10", 318 | "npm-conf": "^1.1.3", 319 | "tunnel": "^0.0.6" 320 | } 321 | }, 322 | "globalthis": { 323 | "version": "1.0.1", 324 | "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.1.tgz", 325 | "integrity": "sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw==", 326 | "dev": true, 327 | "optional": true, 328 | "requires": { 329 | "define-properties": "^1.1.3" 330 | } 331 | }, 332 | "got": { 333 | "version": "9.6.0", 334 | "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", 335 | "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", 336 | "dev": true, 337 | "requires": { 338 | "@sindresorhus/is": "^0.14.0", 339 | "@szmarczak/http-timer": "^1.1.2", 340 | "cacheable-request": "^6.0.0", 341 | "decompress-response": "^3.3.0", 342 | "duplexer3": "^0.1.4", 343 | "get-stream": "^4.1.0", 344 | "lowercase-keys": "^1.0.1", 345 | "mimic-response": "^1.0.1", 346 | "p-cancelable": "^1.0.0", 347 | "to-readable-stream": "^1.0.0", 348 | "url-parse-lax": "^3.0.0" 349 | } 350 | }, 351 | "graceful-fs": { 352 | "version": "4.2.4", 353 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 354 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", 355 | "dev": true 356 | }, 357 | "http-cache-semantics": { 358 | "version": "4.1.0", 359 | "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", 360 | "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", 361 | "dev": true 362 | }, 363 | "inherits": { 364 | "version": "2.0.4", 365 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 366 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 367 | "dev": true 368 | }, 369 | "ini": { 370 | "version": "1.3.5", 371 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 372 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", 373 | "dev": true, 374 | "optional": true 375 | }, 376 | "isarray": { 377 | "version": "1.0.0", 378 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 379 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 380 | "dev": true 381 | }, 382 | "json-buffer": { 383 | "version": "3.0.0", 384 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", 385 | "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", 386 | "dev": true 387 | }, 388 | "json-stringify-safe": { 389 | "version": "5.0.1", 390 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 391 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", 392 | "dev": true, 393 | "optional": true 394 | }, 395 | "jsonfile": { 396 | "version": "4.0.0", 397 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 398 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 399 | "dev": true, 400 | "requires": { 401 | "graceful-fs": "^4.1.6" 402 | } 403 | }, 404 | "keyv": { 405 | "version": "3.1.0", 406 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", 407 | "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", 408 | "dev": true, 409 | "requires": { 410 | "json-buffer": "3.0.0" 411 | } 412 | }, 413 | "lodash": { 414 | "version": "4.17.15", 415 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 416 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", 417 | "dev": true, 418 | "optional": true 419 | }, 420 | "lowercase-keys": { 421 | "version": "1.0.1", 422 | "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", 423 | "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", 424 | "dev": true 425 | }, 426 | "matcher": { 427 | "version": "3.0.0", 428 | "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", 429 | "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", 430 | "dev": true, 431 | "optional": true, 432 | "requires": { 433 | "escape-string-regexp": "^4.0.0" 434 | } 435 | }, 436 | "mimic-response": { 437 | "version": "1.0.1", 438 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", 439 | "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", 440 | "dev": true 441 | }, 442 | "minimist": { 443 | "version": "1.2.5", 444 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 445 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 446 | "dev": true 447 | }, 448 | "mkdirp": { 449 | "version": "0.5.5", 450 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 451 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 452 | "dev": true, 453 | "requires": { 454 | "minimist": "^1.2.5" 455 | } 456 | }, 457 | "ms": { 458 | "version": "2.1.2", 459 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 460 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 461 | "dev": true 462 | }, 463 | "normalize-url": { 464 | "version": "4.5.0", 465 | "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", 466 | "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", 467 | "dev": true 468 | }, 469 | "npm-conf": { 470 | "version": "1.1.3", 471 | "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", 472 | "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", 473 | "dev": true, 474 | "optional": true, 475 | "requires": { 476 | "config-chain": "^1.1.11", 477 | "pify": "^3.0.0" 478 | } 479 | }, 480 | "object-keys": { 481 | "version": "1.1.1", 482 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 483 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 484 | "dev": true, 485 | "optional": true 486 | }, 487 | "once": { 488 | "version": "1.4.0", 489 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 490 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 491 | "dev": true, 492 | "requires": { 493 | "wrappy": "1" 494 | } 495 | }, 496 | "p-cancelable": { 497 | "version": "1.1.0", 498 | "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", 499 | "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", 500 | "dev": true 501 | }, 502 | "pend": { 503 | "version": "1.2.0", 504 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 505 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", 506 | "dev": true 507 | }, 508 | "pify": { 509 | "version": "3.0.0", 510 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 511 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", 512 | "dev": true, 513 | "optional": true 514 | }, 515 | "prepend-http": { 516 | "version": "2.0.0", 517 | "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", 518 | "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", 519 | "dev": true 520 | }, 521 | "process-nextick-args": { 522 | "version": "2.0.1", 523 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 524 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 525 | "dev": true 526 | }, 527 | "progress": { 528 | "version": "2.0.3", 529 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 530 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", 531 | "dev": true 532 | }, 533 | "proto-list": { 534 | "version": "1.2.4", 535 | "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", 536 | "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", 537 | "dev": true, 538 | "optional": true 539 | }, 540 | "pump": { 541 | "version": "3.0.0", 542 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 543 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 544 | "dev": true, 545 | "requires": { 546 | "end-of-stream": "^1.1.0", 547 | "once": "^1.3.1" 548 | } 549 | }, 550 | "readable-stream": { 551 | "version": "2.3.7", 552 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 553 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 554 | "dev": true, 555 | "requires": { 556 | "core-util-is": "~1.0.0", 557 | "inherits": "~2.0.3", 558 | "isarray": "~1.0.0", 559 | "process-nextick-args": "~2.0.0", 560 | "safe-buffer": "~5.1.1", 561 | "string_decoder": "~1.1.1", 562 | "util-deprecate": "~1.0.1" 563 | } 564 | }, 565 | "responselike": { 566 | "version": "1.0.2", 567 | "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", 568 | "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", 569 | "dev": true, 570 | "requires": { 571 | "lowercase-keys": "^1.0.0" 572 | } 573 | }, 574 | "roarr": { 575 | "version": "2.15.3", 576 | "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.3.tgz", 577 | "integrity": "sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA==", 578 | "dev": true, 579 | "optional": true, 580 | "requires": { 581 | "boolean": "^3.0.0", 582 | "detect-node": "^2.0.4", 583 | "globalthis": "^1.0.1", 584 | "json-stringify-safe": "^5.0.1", 585 | "semver-compare": "^1.0.0", 586 | "sprintf-js": "^1.1.2" 587 | } 588 | }, 589 | "safe-buffer": { 590 | "version": "5.1.2", 591 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 592 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 593 | "dev": true 594 | }, 595 | "sanitize-filename": { 596 | "version": "1.6.3", 597 | "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", 598 | "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", 599 | "dev": true, 600 | "requires": { 601 | "truncate-utf8-bytes": "^1.0.0" 602 | } 603 | }, 604 | "semver": { 605 | "version": "7.3.2", 606 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", 607 | "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", 608 | "dev": true, 609 | "optional": true 610 | }, 611 | "semver-compare": { 612 | "version": "1.0.0", 613 | "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", 614 | "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", 615 | "dev": true, 616 | "optional": true 617 | }, 618 | "serialize-error": { 619 | "version": "7.0.1", 620 | "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", 621 | "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", 622 | "dev": true, 623 | "optional": true, 624 | "requires": { 625 | "type-fest": "^0.13.1" 626 | } 627 | }, 628 | "sprintf-js": { 629 | "version": "1.1.2", 630 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", 631 | "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", 632 | "dev": true, 633 | "optional": true 634 | }, 635 | "string_decoder": { 636 | "version": "1.1.1", 637 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 638 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 639 | "dev": true, 640 | "requires": { 641 | "safe-buffer": "~5.1.0" 642 | } 643 | }, 644 | "sumchecker": { 645 | "version": "3.0.1", 646 | "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", 647 | "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", 648 | "dev": true, 649 | "requires": { 650 | "debug": "^4.1.0" 651 | } 652 | }, 653 | "to-readable-stream": { 654 | "version": "1.0.0", 655 | "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", 656 | "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", 657 | "dev": true 658 | }, 659 | "truncate-utf8-bytes": { 660 | "version": "1.0.2", 661 | "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", 662 | "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", 663 | "dev": true, 664 | "requires": { 665 | "utf8-byte-length": "^1.0.1" 666 | } 667 | }, 668 | "tunnel": { 669 | "version": "0.0.6", 670 | "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", 671 | "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", 672 | "dev": true, 673 | "optional": true 674 | }, 675 | "type-fest": { 676 | "version": "0.13.1", 677 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", 678 | "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", 679 | "dev": true, 680 | "optional": true 681 | }, 682 | "typedarray": { 683 | "version": "0.0.6", 684 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 685 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 686 | "dev": true 687 | }, 688 | "universalify": { 689 | "version": "0.1.2", 690 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 691 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", 692 | "dev": true 693 | }, 694 | "url-parse-lax": { 695 | "version": "3.0.0", 696 | "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", 697 | "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", 698 | "dev": true, 699 | "requires": { 700 | "prepend-http": "^2.0.0" 701 | } 702 | }, 703 | "utf8-byte-length": { 704 | "version": "1.0.4", 705 | "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", 706 | "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", 707 | "dev": true 708 | }, 709 | "util-deprecate": { 710 | "version": "1.0.2", 711 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 712 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 713 | "dev": true 714 | }, 715 | "wrappy": { 716 | "version": "1.0.2", 717 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 718 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 719 | "dev": true 720 | }, 721 | "yauzl": { 722 | "version": "2.10.0", 723 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 724 | "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", 725 | "dev": true, 726 | "requires": { 727 | "buffer-crc32": "~0.2.3", 728 | "fd-slicer": "~1.1.0" 729 | } 730 | } 731 | } 732 | } 733 | -------------------------------------------------------------------------------- /demos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-quick-start", 3 | "version": "1.0.0", 4 | "description": "A minimal Electron application", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron ." 8 | }, 9 | "repository": "https://github.com/electron/electron-quick-start", 10 | "keywords": [ 11 | "Electron", 12 | "quick", 13 | "start", 14 | "tutorial", 15 | "demo" 16 | ], 17 | "author": "GitHub", 18 | "license": "CC0-1.0", 19 | "devDependencies": { 20 | "electron": "^9.0.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demos/renderer.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron'); 2 | 3 | const btns = [1, 2, 3, 4, 5]; 4 | 5 | btns.map((i) => 6 | document.getElementById(`btn_${i}`).addEventListener('click', () => ipcRenderer.send(`create-win-${i}`)) 7 | ); 8 | -------------------------------------------------------------------------------- /demos/spider.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/demos/spider.gif -------------------------------------------------------------------------------- /images/ability.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/images/ability.png -------------------------------------------------------------------------------- /images/chromium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/images/chromium.png -------------------------------------------------------------------------------- /images/electron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/images/electron.png -------------------------------------------------------------------------------- /images/ipcRenderer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/images/ipcRenderer.png -------------------------------------------------------------------------------- /images/ipcRenderer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/images/ipcRenderer2.png -------------------------------------------------------------------------------- /images/xiaoguo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/images/xiaoguo.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | spiderChat 9 | 10 | 31 | 32 |
33 | 34 | -------------------------------------------------------------------------------- /koa-mongodb/README.md: -------------------------------------------------------------------------------- 1 | # koa-mongodb-learn 2 | 3 | ## 1. mongodb 安装配置 4 | 5 | ### 1. 安装 6 | 7 | 1. 手动安装 8 | 9 | 官网下载包 https://www.mongodb.com/try/download/community 10 | 11 | 下载解压安装完后,可以把 MongoDB 的二进制命令文件目录(安装目录/bin)添加到 PATH 路径中。 12 | 13 | export PATH=/usr/local/mongodb/bin:$PATH 14 | 15 | 2. brew 安装 16 | 17 | sudo brew install mongodb 18 | 19 | 使用命令mongod -v来查看mongo DB是否安装成功。 20 | 21 | 22 | ### 2. 运行 23 | 24 | 创建一个数据库存储目录 /data/db 25 | 26 | sudo mkdir -p /data/db 这一步,在mac的catalina系统上会报错,mkdir: /data/db: Read-only file system 27 | 28 | 解决方法:不要在根目录创建,随便选择一个目录,然后运行 mongod --dbpath ~/data/db 绑定目录 29 | 30 | 31 | ## 2. CRUD 32 | 33 | ### 2.1. Inserting Documents 34 | 35 | ```js 36 | const insertDocuments = function (db, data) { 37 | const collection = db.collection("msgs"); 38 | // Insert some documents 39 | collection.insertOne(data, function (err, result) { 40 | console.log("Inserted success"); 41 | }); 42 | }; 43 | 44 | 45 | 46 | router.post("/addmsg", (ctx, next) => { 47 | const body = ctx.request.body; 48 | console.log('ctx.request.body', ctx.request.body); 49 | 50 | insertDocuments(db, body); 51 | }); 52 | 53 | 54 | ``` 55 | 56 | curl post 请求 57 | 58 | 用 -X POST 来申明我们的请求方法,用 -d 参数,来传送我们的参数。 59 | 60 | ```text 61 | curl localhost:1234/addmsg -X POST -d "title=comewords&content=articleContent" 62 | ``` 63 | 64 | 一般我们的接口都是 json 格式的 -H 参数来申明请求的 header 65 | 66 | ```text 67 | curl localhost:1234/addmsg -X POST -H "Content-Type:application/json" -d '{"type": 1,"content": "你好","fromId": "me","toId": "zhizhuxia","id": 1234}' 68 | ``` 69 | -------------------------------------------------------------------------------- /koa-mongodb/index.js: -------------------------------------------------------------------------------- 1 | const MongoClient = require("mongodb").MongoClient; 2 | const assert = require("assert"); 3 | const Koa = require("koa"); 4 | const app = new Koa(); 5 | const Router = require("koa-router"); 6 | const router = new Router(); 7 | const koaBody = require("koa-body"); 8 | const cors = require("@koa/cors"); 9 | 10 | app.use(koaBody()); 11 | app.use(cors()); 12 | app.use(router.routes()); 13 | 14 | // Connection URL 15 | const url = "mongodb://localhost:27017"; 16 | 17 | // Database Name 18 | const dbName = "chats"; 19 | const client = new MongoClient(url, { 20 | useNewUrlParser: true, 21 | useUnifiedTopology: true, 22 | }); 23 | 24 | const insertDocuments = (db, data) => { 25 | const collection = db.collection("msgs"); 26 | // Insert some documents 27 | collection.insertOne(data, (err, result) => { 28 | console.log("Inserted success"); 29 | }); 30 | }; 31 | 32 | const findDocuments = (db) => { 33 | const collection = db.collection("msgs"); 34 | // Find some documents 35 | return new Promise(function (resolve, reject) { 36 | collection.find({}).toArray((err, docs) => { 37 | if (err) throw err; 38 | resolve(docs); 39 | }); 40 | }); 41 | }; 42 | 43 | // 删除所有数据 44 | const removeAllData = (db) => { 45 | const collection = db.collection("msgs"); 46 | collection.remove({}, {}, (err, r) => { 47 | console.log("remove success"); 48 | }); 49 | }; 50 | 51 | // Use connect method to connect to the server 52 | client.connect((err) => { 53 | console.log("Connected successfully to server"); 54 | 55 | const db = client.db(dbName); 56 | 57 | router.get("/allmsgs", async (ctx, next) => { 58 | const data = await findDocuments(db); 59 | ctx.body = data; 60 | console.log("get"); 61 | }); 62 | 63 | router.post("/addmsg", async (ctx, next) => { 64 | const body = ctx.request.body; 65 | console.log("body", body); 66 | await insertDocuments(db, body); 67 | ctx.body = "success"; 68 | }); 69 | 70 | router.delete("/clearmsgs", async (ctx, next) => { 71 | const data = await removeAllData(db); 72 | ctx.body = "success"; 73 | console.log("clearmsgs"); 74 | }); 75 | }); 76 | 77 | app.listen(1234); 78 | -------------------------------------------------------------------------------- /koa-mongodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-mongodb-learn", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@koa/cors": "^3.1.0", 14 | "koa": "^2.12.1", 15 | "koa-body": "^4.2.0", 16 | "koa-compose": "^4.1.0", 17 | "koa-router": "^9.0.1", 18 | "mongodb": "^3.5.9" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spiderchat", 3 | "version": "1.0.0", 4 | "description": "spiderChat baseon electron", 5 | "main": "src/main/main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "concurrently \"npm run start:render\" \"wait-on http://localhost:9200&& npm run start:main\" ", 9 | "start:render": "webpack-dev-server --open", 10 | "start:main": "electron .", 11 | "pack:mac": "electron-builder --mac", 12 | "build:render": "webpack --env prod", 13 | "build": "npm run build:render && npm run pack:mac", 14 | "fix": "electron-fix start" 15 | }, 16 | "author": "Ivy", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "babel-core": "^6.26.3", 20 | "babel-loader": "^7.1.2", 21 | "babel-plugin-import": "^1.13.0", 22 | "babel-plugin-transform-decorators-legacy": "^1.3.5", 23 | "babel-plugin-transform-runtime": "^6.23.0", 24 | "babel-preset-env": "^1.7.0", 25 | "babel-preset-es2015": "^6.24.1", 26 | "babel-preset-react": "^6.24.1", 27 | "babel-preset-stage-0": "^6.24.1", 28 | "babel-preset-stage-1": "^6.24.1", 29 | "clean-webpack-plugin": "^3.0.0", 30 | "concurrently": "^5.2.0", 31 | "css-loader": "^3.5.3", 32 | "electron": "^13.1.4", 33 | "electron-builder": "^22.10.5", 34 | "electron-fix": "^1.1.3", 35 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 36 | "file-loader": "^6.0.0", 37 | "html-loader": "^1.1.0", 38 | "html-webpack-plugin": "^4.3.0", 39 | "less": "^3.11.1", 40 | "less-loader": "^6.1.0", 41 | "mini-css-extract-plugin": "^0.9.0", 42 | "node-sass": "^4.14.1", 43 | "postcss-import": "^12.0.1", 44 | "postcss-loader": "^3.0.0", 45 | "precss": "^4.0.0", 46 | "prettier": "^2.0.5", 47 | "react": "^16.13.1", 48 | "react-contenteditable": "^3.3.4", 49 | "react-dom": "^16.13.1", 50 | "react-router-dom": "^5.2.0", 51 | "sass-resources-loader": "^2.0.3", 52 | "style-loader": "^1.2.1", 53 | "uglifyjs-webpack-plugin": "^2.2.0", 54 | "url-loader": "^4.1.0", 55 | "wait-on": "^5.0.1", 56 | "webpack": "^4.43.0", 57 | "webpack-bundle-analyzer": "^3.8.0", 58 | "webpack-cli": "^3.3.11", 59 | "webpack-dev-server": "^3.11.0", 60 | "webpack-merge": "^4.2.2", 61 | "ws": "^7.3.0" 62 | }, 63 | "dependencies": { 64 | "about-window": "^1.13.2", 65 | "electron-is-dev": "^1.2.0", 66 | "mime-types": "^2.1.27" 67 | }, 68 | "build": { 69 | "appId": "com.spider.chat", 70 | "productName": "spiderchat", 71 | "electronDownload": { 72 | "mirror": "https://npm.taobao.org/mirrors/electron/" 73 | }, 74 | "files": [ 75 | "build/**/*", 76 | "src" 77 | ], 78 | "directories": { 79 | "output": "dist" 80 | }, 81 | "copyright": "Copyright © 2021 spider", 82 | "mac": { 83 | "target": [ 84 | "dmg", 85 | "zip" 86 | ], 87 | "icon": "src/resources/icns/spider.icns", 88 | "extendInfo": { 89 | "URL types": [ 90 | { 91 | "Document Role": "Viewer", 92 | "URL identifier": "spiderchat", 93 | "URL Schemes": [ 94 | "spiderchat" 95 | ] 96 | } 97 | ], 98 | "CFBundleURLSchemes": [ 99 | "spiderchat" 100 | ] 101 | } 102 | }, 103 | "dmg": { 104 | "contents": [ 105 | { 106 | "x": 410, 107 | "y": 150, 108 | "type": "link", 109 | "path": "/Applications" 110 | }, 111 | { 112 | "x": 130, 113 | "y": 150, 114 | "type": "file" 115 | } 116 | ], 117 | "icon": "src/resources/icns/spider.icns", 118 | "iconSize": 150 119 | }, 120 | "nsis": { 121 | "createDesktopShortcut": true, 122 | "createStartMenuShortcut": true 123 | } 124 | }, 125 | "prettier": { 126 | "printWidth": 120, 127 | "bracketSpacing": true, 128 | "singleQuote": true, 129 | "trailingComma": "es5", 130 | "tabWidth": 2 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require( 'postcss-import' ), 4 | require( 'precss' ) 5 | ] 6 | } -------------------------------------------------------------------------------- /ppt/桌面端开发之Electron200628.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/ppt/桌面端开发之Electron200628.pdf -------------------------------------------------------------------------------- /src/lib/capture/iconfont/demo.css: -------------------------------------------------------------------------------- 1 | *{margin: 0;padding: 0;list-style: none;} 2 | /* 3 | KISSY CSS Reset 4 | 理念:1. reset 的目的不是清除浏览器的默认样式,这仅是部分工作。清除和重置是紧密不可分的。 5 | 2. reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。 6 | 3. reset 期望提供一套普适通用的基础样式。但没有银弹,推荐根据具体需求,裁剪和修改后再使用。 7 | 特色:1. 适应中文;2. 基于最新主流浏览器。 8 | 维护:玉伯, 正淳 9 | */ 10 | 11 | /** 清除内外边距 **/ 12 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */ 13 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */ 14 | pre, /* text formatting elements 文本格式元素 */ 15 | form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */ 16 | th, td /* table elements 表格元素 */ { 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | /** 设置默认字体 **/ 22 | body, 23 | button, input, select, textarea /* for ie */ { 24 | font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif; 25 | } 26 | h1, h2, h3, h4, h5, h6 { font-size: 100%; } 27 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 28 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */ 29 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 30 | 31 | /** 重置列表元素 **/ 32 | ul, ol { list-style: none; } 33 | 34 | /** 重置文本格式元素 **/ 35 | a { text-decoration: none; } 36 | a:hover { text-decoration: underline; } 37 | 38 | 39 | /** 重置表单元素 **/ 40 | legend { color: #000; } /* for ie6 */ 41 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */ 42 | button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */ 43 | /* 注:optgroup 无法扶正 */ 44 | 45 | /** 重置表格元素 **/ 46 | table { border-collapse: collapse; border-spacing: 0; } 47 | 48 | /* 清除浮动 */ 49 | .ks-clear:after, .clear:after { 50 | content: '\20'; 51 | display: block; 52 | height: 0; 53 | clear: both; 54 | } 55 | .ks-clear, .clear { 56 | *zoom: 1; 57 | } 58 | 59 | .main { 60 | padding: 30px 100px; 61 | width: 960px; 62 | margin: 0 auto; 63 | } 64 | .main h1{font-size:36px; color:#333; text-align:left;margin-bottom:30px; border-bottom: 1px solid #eee;} 65 | 66 | .helps{margin-top:40px;} 67 | .helps pre{ 68 | padding:20px; 69 | margin:10px 0; 70 | border:solid 1px #e7e1cd; 71 | background-color: #fffdef; 72 | overflow: auto; 73 | } 74 | 75 | .icon_lists{ 76 | width: 100% !important; 77 | 78 | } 79 | 80 | .icon_lists li{ 81 | float:left; 82 | width: 100px; 83 | height:180px; 84 | text-align: center; 85 | list-style: none !important; 86 | } 87 | .icon_lists .icon{ 88 | font-size: 42px; 89 | line-height: 100px; 90 | margin: 10px 0; 91 | color:#333; 92 | -webkit-transition: font-size 0.25s ease-out 0s; 93 | -moz-transition: font-size 0.25s ease-out 0s; 94 | transition: font-size 0.25s ease-out 0s; 95 | 96 | } 97 | .icon_lists .icon:hover{ 98 | font-size: 100px; 99 | } 100 | 101 | 102 | 103 | .markdown { 104 | color: #666; 105 | font-size: 14px; 106 | line-height: 1.8; 107 | } 108 | 109 | .highlight { 110 | line-height: 1.5; 111 | } 112 | 113 | .markdown img { 114 | vertical-align: middle; 115 | max-width: 100%; 116 | } 117 | 118 | .markdown h1 { 119 | color: #404040; 120 | font-weight: 500; 121 | line-height: 40px; 122 | margin-bottom: 24px; 123 | } 124 | 125 | .markdown h2, 126 | .markdown h3, 127 | .markdown h4, 128 | .markdown h5, 129 | .markdown h6 { 130 | color: #404040; 131 | margin: 1.6em 0 0.6em 0; 132 | font-weight: 500; 133 | clear: both; 134 | } 135 | 136 | .markdown h1 { 137 | font-size: 28px; 138 | } 139 | 140 | .markdown h2 { 141 | font-size: 22px; 142 | } 143 | 144 | .markdown h3 { 145 | font-size: 16px; 146 | } 147 | 148 | .markdown h4 { 149 | font-size: 14px; 150 | } 151 | 152 | .markdown h5 { 153 | font-size: 12px; 154 | } 155 | 156 | .markdown h6 { 157 | font-size: 12px; 158 | } 159 | 160 | .markdown hr { 161 | height: 1px; 162 | border: 0; 163 | background: #e9e9e9; 164 | margin: 16px 0; 165 | clear: both; 166 | } 167 | 168 | .markdown p, 169 | .markdown pre { 170 | margin: 1em 0; 171 | } 172 | 173 | .markdown > p, 174 | .markdown > blockquote, 175 | .markdown > .highlight, 176 | .markdown > ol, 177 | .markdown > ul { 178 | width: 80%; 179 | } 180 | 181 | .markdown ul > li { 182 | list-style: circle; 183 | } 184 | 185 | .markdown > ul li, 186 | .markdown blockquote ul > li { 187 | margin-left: 20px; 188 | padding-left: 4px; 189 | } 190 | 191 | .markdown > ul li p, 192 | .markdown > ol li p { 193 | margin: 0.6em 0; 194 | } 195 | 196 | .markdown ol > li { 197 | list-style: decimal; 198 | } 199 | 200 | .markdown > ol li, 201 | .markdown blockquote ol > li { 202 | margin-left: 20px; 203 | padding-left: 4px; 204 | } 205 | 206 | .markdown code { 207 | margin: 0 3px; 208 | padding: 0 5px; 209 | background: #eee; 210 | border-radius: 3px; 211 | } 212 | 213 | .markdown pre { 214 | border-radius: 6px; 215 | background: #f7f7f7; 216 | padding: 20px; 217 | } 218 | 219 | .markdown pre code { 220 | border: none; 221 | background: #f7f7f7; 222 | margin: 0; 223 | } 224 | 225 | .markdown strong, 226 | .markdown b { 227 | font-weight: 600; 228 | } 229 | 230 | .markdown > table { 231 | border-collapse: collapse; 232 | border-spacing: 0px; 233 | empty-cells: show; 234 | border: 1px solid #e9e9e9; 235 | width: 95%; 236 | margin-bottom: 24px; 237 | } 238 | 239 | .markdown > table th { 240 | white-space: nowrap; 241 | color: #333; 242 | font-weight: 600; 243 | 244 | } 245 | 246 | .markdown > table th, 247 | .markdown > table td { 248 | border: 1px solid #e9e9e9; 249 | padding: 8px 16px; 250 | text-align: left; 251 | } 252 | 253 | .markdown > table th { 254 | background: #F7F7F7; 255 | } 256 | 257 | .markdown blockquote { 258 | font-size: 90%; 259 | color: #999; 260 | border-left: 4px solid #e9e9e9; 261 | padding-left: 0.8em; 262 | margin: 1em 0; 263 | font-style: italic; 264 | } 265 | 266 | .markdown blockquote p { 267 | margin: 0; 268 | } 269 | 270 | .markdown .anchor { 271 | opacity: 0; 272 | transition: opacity 0.3s ease; 273 | margin-left: 8px; 274 | } 275 | 276 | .markdown .waiting { 277 | color: #ccc; 278 | } 279 | 280 | .markdown h1:hover .anchor, 281 | .markdown h2:hover .anchor, 282 | .markdown h3:hover .anchor, 283 | .markdown h4:hover .anchor, 284 | .markdown h5:hover .anchor, 285 | .markdown h6:hover .anchor { 286 | opacity: 1; 287 | display: inline-block; 288 | } 289 | 290 | .markdown > br, 291 | .markdown > p > br { 292 | clear: both; 293 | } 294 | 295 | 296 | .hljs { 297 | display: block; 298 | background: white; 299 | padding: 0.5em; 300 | color: #333333; 301 | overflow-x: auto; 302 | } 303 | 304 | .hljs-comment, 305 | .hljs-meta { 306 | color: #969896; 307 | } 308 | 309 | .hljs-string, 310 | .hljs-variable, 311 | .hljs-template-variable, 312 | .hljs-strong, 313 | .hljs-emphasis, 314 | .hljs-quote { 315 | color: #df5000; 316 | } 317 | 318 | .hljs-keyword, 319 | .hljs-selector-tag, 320 | .hljs-type { 321 | color: #a71d5d; 322 | } 323 | 324 | .hljs-literal, 325 | .hljs-symbol, 326 | .hljs-bullet, 327 | .hljs-attribute { 328 | color: #0086b3; 329 | } 330 | 331 | .hljs-section, 332 | .hljs-name { 333 | color: #63a35c; 334 | } 335 | 336 | .hljs-tag { 337 | color: #333333; 338 | } 339 | 340 | .hljs-title, 341 | .hljs-attr, 342 | .hljs-selector-id, 343 | .hljs-selector-class, 344 | .hljs-selector-attr, 345 | .hljs-selector-pseudo { 346 | color: #795da3; 347 | } 348 | 349 | .hljs-addition { 350 | color: #55a532; 351 | background-color: #eaffea; 352 | } 353 | 354 | .hljs-deletion { 355 | color: #bd2c00; 356 | background-color: #ffecec; 357 | } 358 | 359 | .hljs-link { 360 | text-decoration: underline; 361 | } 362 | 363 | pre{ 364 | background: #fff; 365 | } 366 | 367 | 368 | 369 | 370 | 371 | -------------------------------------------------------------------------------- /src/lib/capture/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1538684099545'); /* IE9*/ 4 | src: url('iconfont.eot?t=1538684099545#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAWQAAsAAAAACCQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8nVDMY21hcAAAAYAAAABrAAABsu3anlxnbHlmAAAB7AAAAZYAAAG869Cas2hlYWQAAAOEAAAALgAAADYS2G+AaGhlYQAAA7QAAAAcAAAAJAfeA4ZobXR4AAAD0AAAAA4AAAAUFAAAAGxvY2EAAAPgAAAADAAAAAwBEAFgbWF4cAAAA+wAAAAfAAAAIAESAENuYW1lAAAEDAAAAUUAAAJtPlT+fXBvc3QAAAVUAAAAPAAAAE0pDHeHeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeqb+7xdzwv4EhhrmBoQEozAiSAwDxJgzseJztkbsNgDAMRF8+IBSxBw0dC2SgVIzLAC5ZIdgxBUNw0bPsU+LiAkxAUnYlQzgJmJq6YfiJMvxM1bmwEImyySHtvnqHb/8q6D0/1id9Ybtnfq2j1nfKlp5jacvh2I9IczQ77sshP1MOG7MAeJwdj0FrE1EUhe95z8zLvOnM9GVC0pSZmEybmSKYTdsZRIhKXRgQLfgTpIiLoJtCdZFqf4HQnRsLhazabNxamD/gyoIb/QkK3ehqUu/4Npdzz8c795AkfvKSR0gRZUSIrQgtS/WtJN/eSirVRbs/Qr49RO0eMt75MJWjTJrEymreZEJ+9hq4vVV+vLOBRrgkHmXiJBujZhbj1XM76Oi3JjJ444SW1ufMbxp/kuZC3t2Y+Mb4x8xinB375p2a6U5gT1BU+8X9iW3rjp5Jj0j8v/WpmJNNAVGA1ggpEg8DWF3kyIb4IR7qSOnywgnr4kn5RddDR+xoFYnD8oKHZr8errLBlNhhir8E0XUpL8Uv6rEYQnlQa+18jTtzdRZxknJKq80hI8jTzW/T6VnjtCg+NWf7R3Mp50fZ81t6ZVnovb8vXzzA9f6BH5xNi+L1h8pkZD1Worli71qPd39XedzjvTihZaJBnA6SEdo1ZF0oWApftb347tZ6HgwOdezqxav60jN15fRd5yf+uDd6bnmlBf0D1bBUzQAAeJxjYGRgYADi+Y6Xrsbz23xl4GZhAIHrd6YeRtD//7IwMIO4HAxMIAoAaF8MWwAAeJxjYGRgYG7438AQw8IAAkCSkQEVsAIARwsCbnicY2FgYGBBwwABBAAVAAAAAAAAAFYAggC6AN54nGNgZGBgYGUwZ2BmAAEmIOYCQgaG/2A+AwAOdQFWAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nGNgYoAALgbsgJWRiZGZkYWRlZGNga0iM7EqMZMtvTQxLymToyojPy+9KiOTLaU0MyMxn4EBAMN8Cyw=') format('woff'), 6 | url('iconfont.ttf?t=1538684099545') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1538684099545#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-xiazai:before { content: "\e627"; } 19 | 20 | .icon-guanbi:before { content: "\e66c"; } 21 | 22 | .icon-zhongzhi:before { content: "\e633"; } 23 | 24 | .icon-duihao:before { content: "\eeda"; } 25 | 26 | -------------------------------------------------------------------------------- /src/lib/capture/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/lib/capture/iconfont/iconfont.eot -------------------------------------------------------------------------------- /src/lib/capture/iconfont/iconfont.js: -------------------------------------------------------------------------------- 1 | (function(window){var svgSprite='';var script=function(){var scripts=document.getElementsByTagName("script");return scripts[scripts.length-1]}();var shouldInjectCss=script.getAttribute("data-injectcss");var ready=function(fn){if(document.addEventListener){if(~["complete","loaded","interactive"].indexOf(document.readyState)){setTimeout(fn,0)}else{var loadFn=function(){document.removeEventListener("DOMContentLoaded",loadFn,false);fn()};document.addEventListener("DOMContentLoaded",loadFn,false)}}else if(document.attachEvent){IEContentLoaded(window,fn)}function IEContentLoaded(w,fn){var d=w.document,done=false,init=function(){if(!done){done=true;fn()}};var polling=function(){try{d.documentElement.doScroll("left")}catch(e){setTimeout(polling,50);return}init()};polling();d.onreadystatechange=function(){if(d.readyState=="complete"){d.onreadystatechange=null;init()}}}};var before=function(el,target){target.parentNode.insertBefore(el,target)};var prepend=function(el,target){if(target.firstChild){before(el,target.firstChild)}else{target.appendChild(el)}};function appendSvg(){var div,svg;div=document.createElement("div");div.innerHTML=svgSprite;svgSprite=null;svg=div.getElementsByTagName("svg")[0];if(svg){svg.setAttribute("aria-hidden","true");svg.style.position="absolute";svg.style.width=0;svg.style.height=0;svg.style.overflow="hidden";prepend(svg,document.body)}}if(shouldInjectCss&&!window.__iconfont__svg__cssinject__){window.__iconfont__svg__cssinject__=true;try{document.write("")}catch(e){console&&console.log(e)}}ready(appendSvg)})(window) -------------------------------------------------------------------------------- /src/lib/capture/iconfont/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/lib/capture/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/lib/capture/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/lib/capture/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/lib/capture/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/lib/capture/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 截屏 6 | 7 | 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 |
200*200
83 | 84 |
85 |
86 |
87 |
88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/lib/capture/js/capture.js: -------------------------------------------------------------------------------- 1 | const { desktopCapturer, remote } = require('electron'); 2 | const screen = remote.screen; 3 | const { width, height } = screen.getPrimaryDisplay().workAreaSize; 4 | 5 | const { Draw } = require(`${__dirname}/js/draw.js`); 6 | 7 | desktopCapturer 8 | .getSources({ 9 | types: ['screen'], 10 | thumbnailSize: { 11 | width, 12 | height, 13 | }, 14 | }) 15 | .then((sources) => { 16 | const screenImgUrl = sources[0].thumbnail.toDataURL(); 17 | 18 | const bg = document.querySelector('#bg'); 19 | const rect = document.querySelector('.rect'); 20 | const sizeInfo = document.querySelector('.size-info'); 21 | const toolbar = document.querySelector('.toolbar'); 22 | const draw = new Draw(screenImgUrl, bg, width, height, rect, sizeInfo, toolbar); 23 | document.addEventListener('mousedown', draw.startRect.bind(draw)); 24 | document.addEventListener('mousemove', draw.drawingRect.bind(draw)); 25 | document.addEventListener('mouseup', draw.endRect.bind(draw)); 26 | }) 27 | .catch((err) => console.log('err', err)); 28 | -------------------------------------------------------------------------------- /src/lib/capture/js/draw.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer, clipboard, nativeImage, remote } = require('electron'); 2 | 3 | // 进程间转发 4 | // ipcRenderer.on('paste-from-clipboard', (e, arg)=>{ 5 | // ipcRenderer.send('paste-from-clipboard-capwin', arg) 6 | // }) 7 | 8 | class Draw { 9 | constructor(screenImgUrl, bg, screenWidth, screenHeight, rect, sizeInfo, toolbar) { 10 | this.screenImgUrl = screenImgUrl; 11 | this.screenWidth = screenWidth; 12 | this.screenHeight = screenHeight; 13 | 14 | this.$bgDOM = bg; 15 | //背景图数据存在canvas里面 16 | this.$bgCanvas; 17 | this.$bgCtx; 18 | this.initFullScreenCanvas(); 19 | 20 | this.$rectDOM = rect; 21 | this.$rectCtx = this.$rectDOM.getContext('2d'); 22 | 23 | this.$sizeInfoDom = sizeInfo; 24 | this.$toolbarDom = toolbar; 25 | 26 | //存储位置,矩形宽高,是否可画等meta信息 27 | this.selectRectMeta = { 28 | x: 0, //canvas最终的left 29 | y: 0, //canvas最终的top 30 | startX: 0, //鼠标一开始点那个点,e.pageX 31 | startY: 0, //鼠标一开始点那个点,e.pageY 32 | w: 0, //向量,宽,为负说明在startX的左边 33 | h: 0, //向量,高,为负说明在startY的左边 34 | drawing: false, //是否可画,mousedown为true,mouseup为false 35 | dragging: false, //是否可拖拽 36 | RGBAData: null, //矩阵图信息 37 | base64Data: null, //base64编码的二进制图片数据 38 | }; 39 | 40 | //绑定this到原型链上,方便使用 41 | this.getMouseMeta = this.getMouseMeta.bind(this); 42 | this.setSizeInfo = this.setSizeInfo.bind(this); 43 | this.setToolBar = this.setToolBar.bind(this); 44 | this.destroy = this.destroy.bind(this); 45 | this.done = this.done.bind(this); 46 | this.sendMsg = this.sendMsg.bind(this); 47 | this.handleClose = this.handleClose.bind(this); 48 | this.handlePaste = this.handlePaste.bind(this); 49 | 50 | //初始化toolbar事件 51 | this.initToolBarEvent(); 52 | } 53 | 54 | //记录屏幕快照,并赋值给背景 55 | async initFullScreenCanvas() { 56 | //用backgroundSize限定,否则append图片会伸缩 57 | this.$bgDOM.style.backgroundImage = `url(${this.screenImgUrl})`; 58 | this.$bgDOM.style.backgroundSize = `${this.screenWidth}px ${this.screenHeight}px`; 59 | 60 | //创建新的canvas上下文作为存储,方便取出里面的rgba信息 61 | this.$bgCanvas = document.createElement('canvas'); 62 | this.$bgCtx = this.$bgCanvas.getContext('2d'); 63 | // 拿到原图后,再加截屏遮罩 64 | const mask = document.querySelector('#mask'); 65 | mask.style.backgroundColor = 'rgba(0, 0, 0, 0.3)'; 66 | 67 | //新建一个图片,用来放进canvas存图片数据 68 | const imgCanvas = await new Promise((resolve) => { 69 | const img = new Image(); 70 | img.src = this.screenImgUrl; 71 | if (img.complete) { 72 | resolve(img); 73 | } else { 74 | img.onload = () => resolve(img); 75 | } 76 | }).catch((err) => console.log(err)); 77 | 78 | this.$bgCanvas.width = width; 79 | this.$bgCanvas.height = height; 80 | this.$bgCtx.drawImage(imgCanvas, 0, 0); 81 | } 82 | 83 | //开始按下,对应mousedown事件 84 | startRect(e) { 85 | console.log('startRect'); 86 | this.drawing = true; 87 | 88 | //鼠标按下的定点坐标 89 | this.selectRectMeta.startX = e.pageX; 90 | this.selectRectMeta.startY = e.pageY; 91 | } 92 | 93 | //正在画矩形选区,对应mousemove 94 | drawingRect(e) { 95 | if (!this.drawing) return; 96 | 97 | this.getMouseMeta(e); 98 | 99 | //宽高需赋值绝对值为正 100 | this.$rectDOM.width = Math.abs(this.selectRectMeta.w); 101 | this.$rectDOM.height = Math.abs(this.selectRectMeta.h); 102 | 103 | this.$rectDOM.style.left = `${this.selectRectMeta.x}px`; 104 | this.$rectDOM.style.top = `${this.selectRectMeta.y}px`; 105 | 106 | //没有拉伸距离会报错 107 | if (!this.selectRectMeta.w || !this.selectRectMeta.h) return; 108 | //获取矩形坐标在整个fullscreen的位置,生成RGBAData传入回矩形选区 109 | this.selectRectMeta.RGBAData = this.$bgCtx.getImageData( 110 | this.selectRectMeta.x, 111 | this.selectRectMeta.y, 112 | Math.abs(this.selectRectMeta.w), 113 | Math.abs(this.selectRectMeta.h) 114 | ); 115 | this.$rectCtx.putImageData(this.selectRectMeta.RGBAData, 0, 0); 116 | 117 | this.$rectCtx.fillStyle = 'white'; 118 | this.$rectCtx.strokeStyle = 'black'; 119 | this.$rectCtx.lineWidth = 2; 120 | 121 | this.$rectCtx.strokeRect(0, 0, Math.abs(this.selectRectMeta.w), Math.abs(this.selectRectMeta.h)); 122 | 123 | this.$rectDOM.style.display = 'block'; 124 | 125 | //尺寸信息 126 | this.setSizeInfo(); 127 | } 128 | 129 | getMouseMeta(e) { 130 | // 计算坐标差值(宽高) 131 | this.selectRectMeta.w = e.pageX - this.selectRectMeta.startX; 132 | this.selectRectMeta.h = e.pageY - this.selectRectMeta.startY; 133 | 134 | //计算真正的x,y坐标,根据距离在鼠标定点的左右来判断,即大于0 135 | if (this.selectRectMeta.w > 0) { 136 | this.selectRectMeta.x = this.selectRectMeta.startX; 137 | } else { 138 | this.selectRectMeta.x = e.pageX; 139 | } 140 | 141 | if (this.selectRectMeta.h > 0) { 142 | this.selectRectMeta.y = this.selectRectMeta.startY; 143 | } else { 144 | this.selectRectMeta.y = e.pageY; 145 | } 146 | } 147 | 148 | //画完,对应mouseup事件 149 | endRect(e) { 150 | this.drawing = false; 151 | 152 | //转成base64 153 | this.selectRectMeta.base64Data = this.RGBA2ImageData(this.selectRectMeta.RGBAData); 154 | 155 | //画完显示工具条 156 | this.setToolBar(); 157 | } 158 | 159 | //设置size-info,就是取宽高 160 | setSizeInfo() { 161 | this.$sizeInfoDom.style.display = 'block'; 162 | this.$sizeInfoDom.style.left = `${this.selectRectMeta.x}px`; 163 | this.$sizeInfoDom.style.top = `${this.selectRectMeta.y - 25}px`; 164 | this.$sizeInfoDom.innerHTML = `${Math.abs(this.selectRectMeta.w)}*${Math.abs(this.selectRectMeta.h)}`; 165 | } 166 | 167 | //设置工具栏 168 | setToolBar() { 169 | this.$toolbarDom.style.display = 'block'; 170 | this.$toolbarDom.style.left = `${this.selectRectMeta.x + Math.abs(this.selectRectMeta.w) - 100}px`; 171 | this.$toolbarDom.style.top = `${this.selectRectMeta.y + Math.abs(this.selectRectMeta.h)}px`; 172 | } 173 | 174 | //初始化工具栏事件 175 | initToolBarEvent() { 176 | this.$toolbarDom.querySelector('#js-tool-close').addEventListener('click', (e) => { 177 | console.log('close'); 178 | this.destroy(); 179 | }); 180 | 181 | this.$toolbarDom.querySelector('#js-tool-ok').addEventListener('click', (e) => { 182 | console.log('ok'); 183 | this.done(); 184 | }); 185 | } 186 | 187 | //关闭按钮 188 | destroy(data) { 189 | // this.sendMsg('close', data) 190 | this.handleClose(data); 191 | } 192 | 193 | //check按钮的动作 194 | done(e) { 195 | //写入图片到剪贴板 196 | const dataUrl = this.selectRectMeta.base64Data; 197 | const img = nativeImage.createFromDataURL(dataUrl); 198 | clipboard.writeImage(img); 199 | 200 | // 进程间转发 成功后,粘贴到输入框 201 | // this.sendMsg('paste', dataUrl) 202 | 203 | this.handlePaste(dataUrl); 204 | console.log('paste11'); 205 | 206 | this.destroy({ 207 | base64: dataUrl, 208 | }); 209 | } 210 | 211 | handleClose(msg) { 212 | ipcRenderer.send('clip-page', { 213 | type: 'close', 214 | msg, 215 | }); 216 | } 217 | 218 | handlePaste(msg) { 219 | const id = remote.getGlobal('sharedObject').mainId; 220 | ipcRenderer.sendTo(id, 'paste-pic-from-clipboard', msg); 221 | } 222 | 223 | sendMsg(type, msg) { 224 | ipcRenderer.send('clip-page', { 225 | type, 226 | msg, 227 | }); 228 | } 229 | 230 | // 矩阵图转base64格式,原理是插入canvas里面,通过canvas转成图片 231 | RGBA2ImageData(RGBAImg) { 232 | const width = RGBAImg.width; 233 | const height = RGBAImg.height; 234 | const canvas = document.createElement('canvas'); 235 | canvas.width = width; 236 | canvas.height = height; 237 | const ctx = canvas.getContext('2d'); 238 | const imgData = ctx.createImageData(width, height); 239 | imgData.data.set(RGBAImg.data); 240 | ctx.putImageData(imgData, 0, 0); 241 | return canvas.toDataURL(); 242 | } 243 | } 244 | 245 | exports.Draw = Draw; 246 | -------------------------------------------------------------------------------- /src/lib/capture/main.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow, globalShortcut, ipcMain, app } = require('electron'); 2 | 3 | const path = require('path'); 4 | let capWin; 5 | 6 | //注册快捷键 7 | function createShortcut() { 8 | globalShortcut.register('CmdOrCtrl+Shift+A', captureScreen); 9 | // Esc 与 其他应用的快捷键冲突 10 | globalShortcut.register('CmdOrCtrl+Esc', () => { 11 | if (capWin) { 12 | capWin.close(); 13 | capWin = null; 14 | } 15 | }); 16 | ipcMain.on('capture-screen', captureScreen); 17 | } 18 | 19 | function createCaptureWindow() { 20 | // 创建浏览器窗口,只允许创建一个 21 | if (capWin) return console.log('只能有一个CaptureWindow'); 22 | const { screen } = require('electron'); 23 | const { width, height } = screen.getPrimaryDisplay().workAreaSize; 24 | 25 | capWin = new BrowserWindow({ 26 | // window 使用 fullscreen, mac 设置为 undefined, 不可为 false 27 | fullscreen: process.platform !== 'darwin' || undefined, 28 | width, 29 | height, 30 | x: 0, 31 | y: 0, 32 | transparent: true, 33 | frame: false, 34 | movable: false, 35 | skipTaskbar: true, 36 | autoHideMenuBar: true, 37 | resizable: false, 38 | enableLargerThanScreen: true, // mac 39 | hasShadow: false, 40 | webPreferences: { 41 | nodeIntegration: true, 42 | contextIsolation: false, 43 | enableRemoteModule: true, 44 | }, 45 | }); 46 | 47 | app.dock.hide(); 48 | capWin.setAlwaysOnTop(true, 'screen-saver'); 49 | capWin.setVisibleOnAllWorkspaces(true); 50 | capWin.setSimpleFullScreen(true); 51 | // capWin.setFullScreenable(false) // mac 52 | capWin.loadFile(path.join(__dirname, './index.html')); 53 | 54 | // 打开开发者工具 55 | // capWin.webContents.openDevTools(); 56 | 57 | capWin.on('closed', () => { 58 | capWin = null; 59 | }); 60 | } 61 | 62 | function captureScreen() { 63 | createCaptureWindow(); 64 | } 65 | 66 | ipcMain.on('clip-page', (event, { type, msg }) => { 67 | if (type === 'close') { 68 | if (capWin) { 69 | // todo 为了解决全屏问题 70 | capWin.setSimpleFullScreen(false); 71 | // 解决图片自动粘贴问题 72 | setTimeout(function () { 73 | capWin.close(); 74 | capWin = null; 75 | }, 0); 76 | } 77 | 78 | // 进程间转发 79 | // }else if(type === 'paste'){ 80 | // console.log('paste22') 81 | // event.sender.send('paste-from-clipboard', msg); 82 | } 83 | }); 84 | 85 | exports.createShortcut = createShortcut; 86 | -------------------------------------------------------------------------------- /src/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 84 | 85 |
x
86 |
87 | 88 |

账号:

89 |

密码:密码错误

90 |
登录
91 |
92 | 93 | 131 | 132 | -------------------------------------------------------------------------------- /src/main/crash-reporter.js: -------------------------------------------------------------------------------- 1 | const { crashReporter } = require('electron'); 2 | 3 | function init() { 4 | crashReporter.start({ 5 | productName: 'spiderchat', 6 | globalExtra: { 7 | companyName: 'spiderT', 8 | }, 9 | submitURL: 'http://127.0.0.1:9999/crash', 10 | }); 11 | } 12 | module.exports = { 13 | init, 14 | }; 15 | -------------------------------------------------------------------------------- /src/main/download.js: -------------------------------------------------------------------------------- 1 | const { session, app } = require('electron'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | module.exports = (win) => 6 | session.defaultSession.on('will-download', async (event, item) => { 7 | const fileName = item.getFilename(); 8 | const url = item.getURL(); 9 | const startTime = item.getStartTime(); 10 | const initialState = item.getState(); 11 | const downloadPath = app.getPath('downloads'); 12 | 13 | let fileNum = 0; 14 | let savePath = path.join(downloadPath, fileName); 15 | 16 | // savePath基础信息 17 | const ext = path.extname(savePath); 18 | const name = path.basename(savePath, ext); 19 | const dir = path.dirname(savePath); 20 | 21 | // 文件名自增逻辑 22 | while (fs.existsSync(savePath)) { 23 | fileNum += 1; 24 | savePath = path.format({ 25 | dir, 26 | ext, 27 | name: `${name}(${fileNum})`, 28 | }); 29 | } 30 | 31 | // 设置下载目录,阻止系统dialog的出现 32 | item.setSavePath(savePath); 33 | 34 | // 通知渲染进程,有一个新的下载任务 35 | win.webContents.send('new-download-item', { 36 | savePath, 37 | url, 38 | startTime, 39 | state: initialState, 40 | paused: item.isPaused(), 41 | totalBytes: item.getTotalBytes(), 42 | receivedBytes: item.getReceivedBytes(), 43 | }); 44 | 45 | // 下载任务更新 46 | item.on('updated', (e, state) => { 47 | win.webContents.send('download-item-updated', { 48 | startTime, 49 | state, 50 | totalBytes: item.getTotalBytes(), 51 | receivedBytes: item.getReceivedBytes(), 52 | paused: item.isPaused(), 53 | }); 54 | }); 55 | 56 | // 下载任务完成 57 | item.on('done', (e, state) => { 58 | win.webContents.send('download-item-done', { 59 | startTime, 60 | state, 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/main/ipc.js: -------------------------------------------------------------------------------- 1 | const { ipcMain, dialog, BrowserWindow, app, Notification } = require('electron'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const mineType = require('mime-types'); 5 | let unread = 0; 6 | 7 | const { createLoginWindow, close, send } = require('./windows'); 8 | let picWin, settingWin; 9 | 10 | module.exports = function () { 11 | // 打开对话框事件dialog 12 | ipcMain.handle('open-directory-dialog', async (event) => { 13 | const res = new Promise((resolve, reject) => 14 | resolve( 15 | dialog 16 | .showOpenDialog({ 17 | // properties String -包含对话框应用的功能。支持以下值: 18 | // openFile - 允许选择文件 19 | // openDirectory - 允许选择文件夹 20 | // multiSelections-允许多选。 21 | // showHiddenFiles-显示对话框中的隐藏文件。 22 | // createDirectory macOS -允许你通过对话框的形式创建新的目录。 23 | // noResolveAliases macOS -禁用自动别名 (symlink) 路径解析。 选定的别名现在将返回别名路径而不是其目标路径。 24 | // treatPackageAsDirectory macOS -将包 (如 .app 文件夹) 视为目录而不是文件。 25 | properties: ['openFile', 'openDirectory'], 26 | filters: [ 27 | { 28 | name: 'Images', 29 | extensions: ['jpg', 'jpeg', 'png', 'gif'], 30 | }, 31 | // { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] }, 32 | // { name: 'Custom File Type', extensions: ['as'] }, 33 | // { name: 'All Files', extensions: ['*'] } 34 | ], 35 | }) 36 | .then((result) => { 37 | // console.log('result', result) 38 | // result { canceled: false, filePaths: [ '/Users/Desktop/11.jpg' ] } 39 | const { canceled, filePaths } = result; 40 | if (canceled) { 41 | return { event: 'canceled' }; 42 | } 43 | const filePath = filePaths[0]; 44 | let data = fs.readFileSync(filePath); 45 | data = new Buffer.from(data).toString('base64'); 46 | const base64 = 'data:' + mineType.lookup(filePath) + ';base64,' + data; 47 | return { event: 'send', data: base64 }; 48 | }) 49 | .catch((err) => console.log(err)) 50 | ) 51 | ); 52 | return res; 53 | }); 54 | 55 | // 图片预览 56 | ipcMain.on('create-pic-window', (event, arg) => { 57 | picWin = new BrowserWindow({ 58 | width: 800, 59 | height: 600, 60 | resizable: false, 61 | webPreferences: { 62 | nodeIntegration: true, 63 | contextIsolation: false, 64 | enableRemoteModule: true, 65 | }, 66 | }); 67 | global.sharedObject.picId = picWin.webContents.id; 68 | 69 | picWin.loadURL(path.join('file:', __dirname, '../pic.html')); 70 | 71 | picWin.webContents.on('did-finish-load', function () { 72 | picWin.webContents.send('pic-url', arg); 73 | }); 74 | 75 | picWin.show(); 76 | 77 | // picWin.webContents.openDevTools(); 78 | 79 | picWin.on('closed', () => { 80 | picWin.destroy(); 81 | }); 82 | }); 83 | 84 | // 收到消息 msg-receive 85 | ipcMain.handle('msg-receive', async (event, arg) => { 86 | console.log('handle msg-receive'); 87 | unread += 1; 88 | app.setBadgeCount(unread); 89 | const res = new Promise((resolve, reject) => { 90 | const notification = new Notification({ 91 | title: arg.title, 92 | body: arg.body, 93 | hasReply: true, 94 | }); 95 | notification.show(); 96 | notification.on('reply', (e, reply) => { 97 | resolve({ event: 'reply', text: reply }); 98 | }); 99 | notification.on('close', (e) => { 100 | resolve({ event: 'close' }); 101 | }); 102 | }); 103 | return res; 104 | }); 105 | 106 | // 打开设置页 107 | ipcMain.on('open-settings', () => { 108 | settingWin = new BrowserWindow({ 109 | width: 400, 110 | height: 300, 111 | resizable: false, 112 | titleBarStyle: 'hiddenInset', 113 | webPreferences: { 114 | nodeIntegration: true, 115 | contextIsolation: false, 116 | enableRemoteModule: true, 117 | }, 118 | }); 119 | 120 | settingWin.loadURL(path.join('file:', __dirname, '../setting.html')); 121 | 122 | settingWin.show(); 123 | 124 | settingWin.on('closed', () => { 125 | settingWin.destroy(); 126 | }); 127 | }); 128 | 129 | // 退出登录 130 | ipcMain.on('login-out', () => { 131 | close(); 132 | settingWin.destroy(); 133 | createLoginWindow(); 134 | }); 135 | 136 | // 清除未读数 137 | app.on('browser-window-focus', () => { 138 | app.setBadgeCount(0); 139 | unread = 0; 140 | send('browser-window-focus'); 141 | }); 142 | }; 143 | -------------------------------------------------------------------------------- /src/main/main.js: -------------------------------------------------------------------------------- 1 | const { app, globalShortcut } = require('electron'); 2 | const setAppMenu = require('./menus'); 3 | const setTray = require('./tray'); 4 | const { createLoginWindow, createWindow, show, close } = require('./windows'); 5 | const path = require('path'); 6 | const handleIPC = require('./ipc'); 7 | const handleDownload = require('./download'); 8 | const isDev = require('electron-is-dev'); 9 | 10 | // 开机自启动 11 | app.setLoginItemSettings({ 12 | openAtLogin: true, 13 | }); 14 | 15 | app.whenReady().then(() => { 16 | setAppMenu(); 17 | if (process.platform === 'darwin') { 18 | app.dock.setIcon(path.join(__dirname, '../resources/images/zhizhuxia_big.png')); 19 | } 20 | }); 21 | 22 | const gotTheLock = app.requestSingleInstanceLock(); 23 | 24 | if (!gotTheLock) { 25 | app.quit(); 26 | } else { 27 | app.on('second-instance', show); 28 | 29 | app.on('will-finish-launching', () => { 30 | // 自动更新 31 | if (!isDev) { 32 | require('./updater.js'); 33 | } 34 | require('./crash-reporter').init(); 35 | }); 36 | 37 | app.on('ready', () => { 38 | createLoginWindow(); 39 | const win = createWindow(); 40 | setTray(); 41 | handleIPC(); 42 | handleDownload(win); 43 | // 模拟crash 44 | // process.crash(); 45 | }); 46 | 47 | app.on('activate', show); 48 | 49 | app.on('before-quit', close); 50 | 51 | app.on('will-quit', () => { 52 | // Unregister all shortcuts. 53 | globalShortcut.unregisterAll(); 54 | }); 55 | 56 | app.on('open-url', (event, url) => { 57 | event.preventDefault(); 58 | console.log('open-url'); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/menus/about.js: -------------------------------------------------------------------------------- 1 | const openAboutWindow = require('about-window').default; 2 | const path = require('path'); 3 | 4 | const aboutWindow = () => openAboutWindow({ 5 | icon_path: path.join(__dirname, '../../resources/images/zhizhuxia_big.png'), 6 | package_json_dir: path.resolve(__dirname + '../../../../'), 7 | copyright: 'Copyright (c) 2020 TT', 8 | }) 9 | 10 | module.exports = aboutWindow; -------------------------------------------------------------------------------- /src/main/menus/file-menu.js: -------------------------------------------------------------------------------- 1 | const submenu = [{ 2 | label: '发起会话...', 3 | accelerator: 'CommandOrControl+N', 4 | }, 5 | { 6 | label: '置顶/取消置顶', 7 | accelerator: 'up+CommandOrControl+T', 8 | }, 9 | { 10 | label: '静音/取消静音', 11 | accelerator: 'up+CommandOrControl+M', 12 | }, 13 | { 14 | type: 'separator' 15 | }, 16 | { 17 | label: '备份与恢复', 18 | }, 19 | { 20 | label: '关闭', 21 | accelerator: 'CommandOrControl+W', 22 | }, 23 | ] 24 | 25 | const fileMenu = { 26 | label: '&文件', 27 | submenu, 28 | }; 29 | 30 | module.exports = fileMenu; 31 | -------------------------------------------------------------------------------- /src/main/menus/index.js: -------------------------------------------------------------------------------- 1 | const { Menu, app } = require('electron'); 2 | const fileMenu = require('./file-menu'); 3 | const macAppMenu = require('./mac-app-menu'); 4 | const isMac = process.platform === 'darwin'; 5 | function createMenuTemplate() { 6 | const windowMenu = { 7 | label: '窗口', 8 | submenu: [ 9 | { role: 'minimize' }, 10 | { role: 'zoom' }, 11 | { type: 'separator' }, 12 | { role: 'front' }, 13 | { type: 'separator' }, 14 | { role: 'window' } 15 | ] 16 | }; 17 | 18 | const editMenu = { 19 | label: '编辑', 20 | submenu: [ 21 | { role: 'undo' }, 22 | { role: 'redo' }, 23 | { type: 'separator' }, 24 | { role: 'cut' }, 25 | { role: 'copy' }, 26 | { role: 'paste' }, 27 | ...(isMac ? [ 28 | { role: 'pasteAndMatchStyle' }, 29 | { role: 'delete' }, 30 | { role: 'selectAll' }, 31 | { type: 'separator' }, 32 | { 33 | label: 'Speech', 34 | submenu: [ 35 | { role: 'startspeaking' }, 36 | { role: 'stopspeaking' } 37 | ] 38 | } 39 | ] : [ 40 | { role: 'delete' }, 41 | { type: 'separator' }, 42 | { role: 'selectAll' } 43 | ]) 44 | ] 45 | }; 46 | 47 | const viewMenu = { 48 | label: '显示', 49 | submenu: [ 50 | { role: 'resetzoom' }, 51 | { role: 'zoomin' }, 52 | { role: 'zoomout' }, 53 | { type: 'separator' }, 54 | { role: 'togglefullscreen' } 55 | ] 56 | } 57 | 58 | const helpMenu = { 59 | role: 'help', 60 | submenu: [ 61 | { 62 | label: 'Learn More', 63 | click: async () => { 64 | const { shell } = require('electron') 65 | await shell.openExternal('https://electronjs.org') 66 | } 67 | }, 68 | { 69 | role: 'toggledevtools' 70 | } 71 | ] 72 | } 73 | 74 | return [ 75 | macAppMenu, 76 | fileMenu, 77 | editMenu, 78 | viewMenu, 79 | windowMenu, 80 | helpMenu 81 | ].filter(menu => menu !== null) 82 | 83 | } 84 | 85 | function setAppMenu() { 86 | const menuTemplate = createMenuTemplate(); 87 | const appMenu = Menu.buildFromTemplate(menuTemplate); 88 | app.applicationMenu = appMenu; 89 | } 90 | 91 | module.exports = setAppMenu; -------------------------------------------------------------------------------- /src/main/menus/mac-app-menu.js: -------------------------------------------------------------------------------- 1 | const { app } = require('electron'); 2 | const aboutWindow = require('./about'); 3 | 4 | const name = app.getName(); 5 | const macAppMenu = { 6 | label: name, 7 | submenu: [ 8 | { 9 | label: '关于' + name, 10 | click: aboutWindow 11 | }, 12 | { type: 'separator' }, 13 | { role: 'hide' }, 14 | { role: 'hideothers' }, 15 | { role: 'unhide' }, 16 | { type: 'separator' }, 17 | { role: 'quit' }, 18 | ] 19 | } 20 | 21 | 22 | module.exports = macAppMenu; -------------------------------------------------------------------------------- /src/main/tray/index.js: -------------------------------------------------------------------------------- 1 | const { 2 | app, 3 | Menu, 4 | Tray 5 | } = require('electron'); 6 | const { 7 | show 8 | } = require('../windows'); 9 | const path = require('path'); 10 | 11 | let tray; 12 | 13 | function setTray() { 14 | tray = new Tray(path.resolve(__dirname, '../../resources/images/zhizhuxia_small.png')); 15 | const contextMenu = Menu.buildFromTemplate([{ 16 | label: '显示', 17 | click: show 18 | }, 19 | { 20 | label: '退出', 21 | click: app.quit 22 | } 23 | ]) 24 | tray.setContextMenu(contextMenu) 25 | tray.setToolTip('spiderChat') 26 | } 27 | 28 | module.exports = setTray; -------------------------------------------------------------------------------- /src/main/updater.js: -------------------------------------------------------------------------------- 1 | const { autoUpdater, app, dialog } = require('electron'); 2 | 3 | if (process.platform == 'darwin') { 4 | autoUpdater.setFeedURL('http://127.0.0.1:9999/darwin?version=' + app.getVersion()); 5 | } else { 6 | autoUpdater.setFeedURL('http://127.0.0.1:9999/win32?version=' + app.getVersion()); 7 | } 8 | 9 | autoUpdater.checkForUpdates(); 10 | // 定时轮训( 30s), 或者服务端推送 11 | setInterval(() => autoUpdater.checkForUpdates(), 30000); 12 | 13 | autoUpdater.on('update-available', () => { 14 | console.log('update-available'); 15 | }); 16 | 17 | autoUpdater.on('update-downloaded', (e, notes, version) => { 18 | // 提醒用户更新 19 | app.whenReady().then(() => { 20 | const clickId = dialog.showMessageBoxSync({ 21 | type: 'info', 22 | title: '升级提示', 23 | message: '新版本:主题升级,自动匹配系统深色主题', 24 | buttons: ['马上升级', '手动重启'], 25 | cancelId: 1, 26 | }); 27 | if (clickId === 0) { 28 | autoUpdater.quitAndInstall(); 29 | app.quit(); 30 | } 31 | }); 32 | }); 33 | 34 | autoUpdater.on('error', (err) => { 35 | console.log('error', err); 36 | }); 37 | -------------------------------------------------------------------------------- /src/main/windows/index.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow, ipcMain, nativeTheme } = require('electron'); 2 | const isDev = require('electron-is-dev'); 3 | const path = require('path'); 4 | 5 | let win, 6 | loginWin, 7 | willQuitApp = false; 8 | const { createShortcut } = require('../../lib/capture/main'); 9 | 10 | process.on('unhandledRejection', (reason, p) => { 11 | console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); 12 | }); 13 | 14 | function createLoginWindow() { 15 | loginWin = new BrowserWindow({ 16 | width: 300, 17 | height: 400, 18 | frame: false, 19 | webPreferences: { 20 | nodeIntegration: true, 21 | contextIsolation: false, 22 | enableRemoteModule: true, 23 | }, 24 | resizable: false, 25 | }); 26 | 27 | willQuitApp = false; 28 | loginWin.loadFile(path.join(__dirname, '../../login.html')); 29 | } 30 | 31 | // 登录 32 | ipcMain.on('login-error', (event, arg) => { 33 | console.log('login-error'); 34 | // // todo 未生效??? 35 | // loginWin.flashFrame(true); 36 | }); 37 | 38 | ipcMain.on('login-success', (event, arg) => { 39 | console.log('login-success'); 40 | createWindow(); 41 | loginWin.close(); 42 | win.setSize(900, 700); 43 | win.center(); 44 | }); 45 | 46 | ipcMain.on('close-login', (event, arg) => { 47 | console.log('close-login'); 48 | loginWin && loginWin.close(); 49 | close(); 50 | }); 51 | 52 | function createWindow() { 53 | // 创建浏览器窗口 54 | win = new BrowserWindow({ 55 | width: 0, 56 | height: 0, 57 | webPreferences: { 58 | nodeIntegration: true, 59 | contextIsolation: false, 60 | enableRemoteModule: true, 61 | }, 62 | minWidth: 800, 63 | minHeight: 600, 64 | titleBarStyle: 'hiddenInset', 65 | show: false, // 先隐藏 66 | icon: path.join(__dirname, '../../resources/images/zhizhuxia.png'), 67 | backgroundColor: '#f3f3f3', // 优化白屏,设置窗口底色 68 | }); 69 | 70 | global.sharedObject = { 71 | mainId: win.webContents.id, 72 | }; 73 | 74 | // 初始化截图 75 | createShortcut(); 76 | 77 | win.on('ready-to-show', () => win.show()); // 初始化后显示 78 | 79 | win.on('close', (e) => { 80 | console.log('close', willQuitApp); 81 | if (willQuitApp) { 82 | win = null; 83 | } else { 84 | e.preventDefault(); 85 | win.hide(); 86 | } 87 | }); 88 | 89 | if (isDev) { 90 | win.loadURL('http://localhost:9200'); 91 | } else { 92 | win.loadFile(path.join(__dirname, '../../../build/index.html')); 93 | } 94 | 95 | // // 打开开发者工具 96 | // win.webContents.openDevTools() 97 | 98 | // 进程间转发 99 | // ipcMain.on('paste-from-clipboard-capwin', (event, arg) => { 100 | // console.log('paste-from-clipboard-capwin') 101 | // win.webContents.send('paste-from-clipboard-mainwin', arg) 102 | // }) 103 | 104 | nativeTheme.on('updated', function (e) { 105 | const darkMode = nativeTheme.shouldUseDarkColors; 106 | console.log('updateddarkMode', darkMode); 107 | send('change-mode', darkMode); 108 | }); 109 | 110 | return win; 111 | } 112 | 113 | function show() { 114 | win && win.show(); 115 | } 116 | 117 | function close() { 118 | willQuitApp = true; 119 | win && win.close(); 120 | } 121 | 122 | function send(channel, ...args) { 123 | win && win.webContents.send(channel, ...args); 124 | } 125 | 126 | module.exports = { 127 | createLoginWindow, 128 | createWindow, 129 | show, 130 | close, 131 | send, 132 | }; 133 | -------------------------------------------------------------------------------- /src/pic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | spiderChat 8 | 9 | 89 | 90 | 91 |
92 |
93 |
94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 |
保存成功
102 |
103 | 104 | 105 | 185 | 186 | -------------------------------------------------------------------------------- /src/renderer/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { ipcRenderer, remote } from 'electron'; 3 | 4 | import User from './container/User/index.jsx'; 5 | import Chat from './container/Chat/index.jsx'; 6 | 7 | export default function App() { 8 | useEffect(() => { 9 | ipcRenderer.on('download-item-done', (e) => { 10 | console.log('下载完成✅'); 11 | 12 | const id = remote.getGlobal('sharedObject').picId; 13 | ipcRenderer.sendTo(id, 'download-item-done'); 14 | }); 15 | }, []); 16 | 17 | return ( 18 |
19 | 20 | 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/components/EmojiPackage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.scss'; 3 | 4 | export default function EmojiPackage(props) { 5 | const emojiArr = [ 6 | '😊', 7 | '😢', 8 | '😄', 9 | '🔥', 10 | '👌', 11 | '👀', 12 | '🐦', 13 | '😯', 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 | '🧙‍', 46 | '🌧️', 47 | '🌞', 48 | ]; 49 | 50 | function sendEmoji(e, item) { 51 | e.stopPropagation(); 52 | props.sendEmoji(item); 53 | } 54 | 55 | document.body.addEventListener( 56 | 'click', 57 | (e) => { 58 | if (e.target.matches('.emoji-wrap *')) { 59 | return; 60 | } 61 | props.handleLeave(); 62 | }, 63 | false 64 | ); 65 | 66 | return ( 67 |
68 | {emojiArr.map((item, index) => ( 69 | sendEmoji(e, item)}> 70 | {item} 71 | 72 | ))} 73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/renderer/components/EmojiPackage/index.scss: -------------------------------------------------------------------------------- 1 | .emoji-wrap{ 2 | position: absolute; 3 | background-color: #fff; 4 | top: -260px; 5 | left: -82px; 6 | width: 224px; 7 | height: 256px; 8 | border-radius: 6px; 9 | box-shadow: 2px 2px 8px #ccc; 10 | padding: 10px; 11 | box-sizing: border-box; 12 | &::before{ 13 | position: absolute; 14 | display: inline-block; 15 | top: 246px; 16 | left: 92px; 17 | width: 0; 18 | height: 0px; 19 | content: ''; 20 | border-style: solid; 21 | border-width: 8px; 22 | border-color: #fff #fff transparent transparent; 23 | transform: rotate(135deg); 24 | box-shadow: 2px -2px 2px #ccc; 25 | } 26 | .emoji-item{ 27 | display: inline-block; 28 | width: 34px; 29 | height: 34px; 30 | line-height: 34px; 31 | text-align: center; 32 | font-size: 14px; 33 | &:hover{ 34 | font-size: 22px; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/renderer/components/Message/Image.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Image.scss'; 3 | import { ipcRenderer } from 'electron'; 4 | 5 | function handleZoom(src) { 6 | ipcRenderer.send('create-pic-window', src); 7 | } 8 | 9 | const Image = (props) => { 10 | const content = props.content; 11 | return ( 12 | 13 | handleZoom(content)} /> 14 | 15 | ); 16 | }; 17 | 18 | export default Image; 19 | -------------------------------------------------------------------------------- /src/renderer/components/Message/Image.scss: -------------------------------------------------------------------------------- 1 | .msg-text{ 2 | img{ 3 | max-height: 70px; 4 | width: auto; 5 | } 6 | } -------------------------------------------------------------------------------- /src/renderer/components/Message/Text.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Text.scss'; 3 | 4 | const Text = (props) => { 5 | return ( 6 | 7 | {props.content} 8 | 9 | ); 10 | }; 11 | 12 | export default Text; -------------------------------------------------------------------------------- /src/renderer/components/Message/Text.scss: -------------------------------------------------------------------------------- 1 | .msg-text { 2 | background: #fff; 3 | border-radius: 5px; 4 | padding: 5px 10px; 5 | } -------------------------------------------------------------------------------- /src/renderer/components/Message/Tip.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Tip.scss'; 3 | 4 | const Tip = (props) => { 5 | return ( 6 |
7 | {props.content} 8 |
9 | ); 10 | }; 11 | 12 | export default Tip; -------------------------------------------------------------------------------- /src/renderer/components/Message/Tip.scss: -------------------------------------------------------------------------------- 1 | .msg-tip{ 2 | text-align: center; 3 | color: #333; 4 | } -------------------------------------------------------------------------------- /src/renderer/components/Message/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Text from './Text'; 3 | import Tip from './Tip'; 4 | import Image from './Image.jsx'; 5 | import { USER_NAME } from '../../constants'; 6 | import './index.scss'; 7 | 8 | const AvatarRender = (msg) => { 9 | return ( 10 | 14 | ); 15 | }; 16 | 17 | const MsgRender = (msg) => { 18 | let content; 19 | switch (msg.type) { 20 | // 文本 21 | case 1: 22 | content = ; 23 | break; 24 | // 系统 25 | case 2: 26 | content = ; 27 | break; 28 | // 图片 29 | case 3: 30 | content = ; 31 | break; 32 | } 33 | 34 | return ( 35 |
36 | {msg.type !== 2 ? AvatarRender(msg) : null} 37 | {content} 38 |
39 | ); 40 | }; 41 | 42 | export default MsgRender; 43 | -------------------------------------------------------------------------------- /src/renderer/components/Message/index.scss: -------------------------------------------------------------------------------- 1 | .chat-avatar{ 2 | display: inline-block; 3 | background-color: #fff; 4 | width: 32px; 5 | height: 32px; 6 | border-radius: 2px; 7 | } 8 | 9 | .msg-item{ 10 | margin: 18px 0; 11 | .msg-text{ 12 | vertical-align: top; 13 | padding: 8px; 14 | display: inline-block; 15 | max-width: 300px; 16 | } 17 | &.left .msg-text{ 18 | position: relative; 19 | margin-left: 10px; 20 | &::before{ 21 | display: block; 22 | content: ''; 23 | width: 0; 24 | height: 0; 25 | border-style:solid; 26 | border-width: 6px; 27 | border-color: transparent #fff transparent transparent; 28 | position: absolute; 29 | left: -12px; 30 | top: 8px; 31 | } 32 | } 33 | 34 | &.right{ 35 | img, .msg-text{ 36 | float:right; 37 | margin-right: 10px; 38 | position: relative; 39 | } 40 | .msg-text{ 41 | background-color: rgb(158, 231, 101); 42 | &::before{ 43 | display: block; 44 | content: ''; 45 | width: 0; 46 | height: 0; 47 | border-style:solid; 48 | border-width: 6px; 49 | border-color: transparent transparent transparent rgb(158, 231, 101); 50 | position: absolute; 51 | right: -12px; 52 | top: 6px; 53 | } 54 | } 55 | } 56 | } 57 | 58 | .clearfix:after{ 59 | content: "020"; 60 | display: block; 61 | height: 0; 62 | clear: both; 63 | visibility: hidden; 64 | } 65 | 66 | .clearfix { 67 | zoom: 1; 68 | } -------------------------------------------------------------------------------- /src/renderer/constants.js: -------------------------------------------------------------------------------- 1 | const USER_NAME = 'ivy'; 2 | const SPIDER_MAN = 'zhizhuxia'; 3 | 4 | export { USER_NAME, SPIDER_MAN }; 5 | -------------------------------------------------------------------------------- /src/renderer/container/Chat/Chatlist.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useContext } from 'react'; 2 | import { MyContext } from '../../context-manager'; 3 | import { SPIDER_MAN } from '../../constants'; 4 | 5 | import './index.scss'; 6 | 7 | const users = [ 8 | { 9 | name: '蜘蛛侠', 10 | avatar: 'zhizhuxia', 11 | }, 12 | { 13 | name: '小丑', 14 | avatar: 'xiaochou', 15 | }, 16 | { 17 | name: '蚁人', 18 | avatar: 'yiren', 19 | }, 20 | { 21 | name: '超人', 22 | avatar: 'chaoren', 23 | }, 24 | { 25 | name: '蝙蝠侠', 26 | avatar: 'bianfuxia', 27 | }, 28 | { 29 | name: '钢铁侠', 30 | avatar: 'gangtiexia', 31 | }, 32 | { 33 | name: '金刚狼', 34 | avatar: 'jinganglang', 35 | }, 36 | { 37 | name: '雷神', 38 | avatar: 'leishen', 39 | }, 40 | { 41 | name: '绿灯侠', 42 | avatar: 'lvdengxia', 43 | }, 44 | { 45 | name: '绿巨人', 46 | avatar: 'lvjuren', 47 | }, 48 | { 49 | name: '美国队长', 50 | avatar: 'meiguoduichang', 51 | }, 52 | { 53 | name: '圣诞老人', 54 | avatar: 'ren', 55 | }, 56 | { 57 | name: '闪电侠', 58 | avatar: 'shandianxia', 59 | }, 60 | { 61 | name: '死侍', 62 | avatar: 'sishi', 63 | }, 64 | ]; 65 | 66 | export default function Chatlist(props) { 67 | console.log('props', props); 68 | 69 | useEffect(() => { 70 | fetchData(); 71 | }, []); 72 | 73 | const { setMsgData } = useContext(MyContext); 74 | function renderCurMsg(user) { 75 | const msgData = props.msgData; 76 | if (msgData && msgData.length) { 77 | const last = msgData[msgData.length - 1]; 78 | return last.type === 3 ? '[图片]' : last.content; 79 | } else { 80 | return `您已添加了${user.name},现在可以开始聊天了。`; 81 | } 82 | } 83 | 84 | function ListItem(user) { 85 | return ( 86 |
87 |
88 | 89 |
90 |
91 |

{user.name}

92 | {user.avatar === SPIDER_MAN ? ( 93 |

{renderCurMsg(user)}

94 | ) : ( 95 |

{`您已添加了${user.name},现在可以开始聊天了。`}

96 | )} 97 |
98 |
2021/06/25
99 |
100 | ); 101 | } 102 | 103 | function fetchData() { 104 | fetch('http://127.0.0.1:1234/allmsgs') 105 | .then((response) => response.json()) 106 | .then((response) => { 107 | setMsgData(response || []); 108 | }) 109 | .catch((error) => console.log(error)); 110 | } 111 | 112 | function getMsg(user) { 113 | if (user.avatar !== SPIDER_MAN) { 114 | return; 115 | } 116 | fetchData(); 117 | } 118 | return
{users.map((item) => ListItem(item))}
; 119 | } 120 | -------------------------------------------------------------------------------- /src/renderer/container/Chat/Messages.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from 'react'; 2 | import { ipcRenderer, remote } from 'electron'; 3 | import MsgRender from '../../components/Message'; 4 | import EmojiPackage from '../../components/EmojiPackage'; 5 | import ContentEditable from 'react-contenteditable'; 6 | import { msgBody } from '../../utils'; 7 | import { USER_NAME, SPIDER_MAN } from '../../constants'; 8 | import { MyContext } from '../../context-manager'; 9 | import './index.scss'; 10 | 11 | let unread = 0; 12 | const app = remote.app; 13 | 14 | const TO_ID = SPIDER_MAN; 15 | const FROM_ID = USER_NAME; 16 | ipcRenderer.setMaxListeners(100); 17 | 18 | const socket = new WebSocket('ws://localhost:8080/ws'); 19 | socket.onopen = (event) => { 20 | console.log('onopen'); 21 | }; 22 | socket.onclose = (event) => { 23 | console.log('onclose'); 24 | }; 25 | 26 | export default function Messages(props) { 27 | const { msgData = [] } = props; 28 | const { setMsgData } = useContext(MyContext); 29 | const [isShowEmoji, toggleShowEmoji] = useState(false); 30 | const msgBox = React.createRef(); 31 | const contentEditable = React.createRef(); 32 | const [editHtml, setHtml] = useState(''); 33 | 34 | useEffect(() => { 35 | setTimeout(() => scrollToView(), 300); 36 | // 'paste-pic-from-clipboard' 37 | ipcRenderer.on('paste-pic-from-clipboard', (e, arg) => { 38 | console.log('paste-pic-from-clipboard'); 39 | handlePasteFromIpc(arg); 40 | }); 41 | 42 | // 清除未读数 43 | ipcRenderer.on('browser-window-focus', () => { 44 | unread = 0; 45 | }) 46 | }, []) 47 | 48 | socket.onmessage = (event) => { 49 | const msg = JSON.parse(event.data); 50 | if (msg.fromId !== TO_ID) { 51 | return 52 | } 53 | 54 | handleMsg(msg.type, msg.content, 'receive'); 55 | 56 | if (document.hidden) { 57 | createNativeNotification(msg); 58 | // createHtmlNotification(msg) 59 | 60 | unread += 1; 61 | app.setBadgeCount(unread); 62 | // handleNativeNoti(msg); 63 | } 64 | }; 65 | 66 | function createHtmlNotification(msg) { 67 | // h5通知 68 | const option = { 69 | title: '蜘蛛侠', 70 | body: msg.content, 71 | }; 72 | 73 | // 渲染进程的Notification 74 | const chatNotication = new Notification(option.title, option); 75 | chatNotication.onclick = () => { 76 | console.log('clickchatNotication') 77 | } 78 | } 79 | 80 | async function createNativeNotification(msg) { 81 | // 发给主进程,展示Notification 82 | const res = await ipcRenderer.invoke('msg-receive', { title: '蜘蛛侠', body: msg.content }); 83 | console.log('res', res); 84 | res.event === 'reply' ? onSend(res.text) : onClose(); 85 | } 86 | 87 | function handleNativeNoti(msg) { 88 | const notification = new remote.Notification({ 89 | title: '蜘蛛侠', 90 | body: msg.content, 91 | hasReply: true, 92 | }); 93 | notification.show(); 94 | notification.on('reply', (e, reply) => { 95 | onSend(reply) 96 | }); 97 | notification.on('close', (e) => { 98 | onClose() 99 | }); 100 | } 101 | 102 | function onClose() { } 103 | 104 | function onSend(data) { 105 | handleMsg(1, data); 106 | // todo 解决 收到消息,聊天区域没有及时渲染 107 | document.getElementsByClassName('list-item')[0].click(); 108 | } 109 | 110 | function handleMsg(msgType, data, type) { 111 | console.log('handleMsg', msgType, data, type) 112 | if (!data) { 113 | return 114 | } 115 | let msg; 116 | if (type === 'receive') { 117 | msg = msgBody(msgType, data, TO_ID, FROM_ID); 118 | } else { 119 | msg = msgBody(msgType, data, FROM_ID, TO_ID); 120 | socket.send(JSON.stringify(msg)); 121 | addMsg(msg) 122 | } 123 | setMsgData(pre => [...pre, msg]); 124 | // todo 解决 收到消息,聊天区域没有及时渲染 125 | setHtml(editHtml + ' '); 126 | scrollToView(); 127 | } 128 | 129 | function addMsg(data) { 130 | fetch('http://127.0.0.1:1234/addmsg', { 131 | body: JSON.stringify(data), 132 | method: 'POST', 133 | headers: { 134 | 'content-type': 'application/json' 135 | }, 136 | }) 137 | .then((response) => console.log(response)) 138 | .catch((error) => console.log(error)) 139 | } 140 | 141 | function captureScreen() { 142 | ipcRenderer.send('capture-screen'); 143 | } 144 | 145 | function getSrcFromImg(str) { 146 | const imgReg = /|\/>)/gi; //匹配图片中的img标签 147 | const srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i; // 匹配图片中的src 148 | const arr = str.match(imgReg); //筛选出所有的img 149 | const src = arr[0].match(srcReg); 150 | return src[1]; 151 | } 152 | 153 | // 聊天内容向上滚动到可见区域 154 | function scrollToView() { 155 | const msgBoxEle = document.getElementById('msg-box'); 156 | const msgChildNodes = msgBoxEle.childNodes; 157 | const len = msgChildNodes.length; 158 | // 因为加了一个‘’dom 159 | const lastChildEle = msgChildNodes[len - 2]; 160 | lastChildEle && setTimeout(function () { 161 | lastChildEle.scrollIntoView(); 162 | }, 100); 163 | } 164 | 165 | function handleKeyDown(e) { 166 | if (e.keyCode === 13 || e.which === 13) { 167 | e.preventDefault(); 168 | 169 | if (!editHtml || !editHtml.trim() || !window.WebSocket) { 170 | return; 171 | } 172 | if (socket.readyState === WebSocket.OPEN) { 173 | // 判断发送文本,还是图片 174 | if (editHtml.includes('`; 217 | setHtml(eleHtml); 218 | } 219 | 220 | function handlePaste(e) { 221 | const cbd = e.clipboardData; 222 | if (!(e.clipboardData && e.clipboardData.items)) { 223 | return; 224 | } 225 | for (let i = 0; i < cbd.items.length; i++) { 226 | const item = cbd.items[i]; 227 | if (item.kind == 'file') { 228 | const blob = item.getAsFile(); 229 | if (blob.size === 0) { 230 | return; 231 | } 232 | const reader = new FileReader(); 233 | const imgs = new Image(); 234 | imgs.file = blob; 235 | reader.onload = (e) => { 236 | const imgPath = e.target.result; 237 | imgs.src = imgPath; 238 | const eleHtml = `${editHtml}`; 239 | setHtml(eleHtml); 240 | }; 241 | reader.readAsDataURL(blob); 242 | } 243 | } 244 | } 245 | 246 | // 进程间转发 247 | // ipcRenderer.on('paste-from-clipboard-mainwin', (e, arg) => { 248 | // console.log('paste-from-clipboard-mainwin'); 249 | // handlePasteFromIpc(arg); 250 | // }); 251 | 252 | 253 | 254 | return ( 255 |
256 |
蜘蛛侠
257 |
258 |
259 | {msgData.length ? msgData.map((item) => MsgRender(item)) : null} 260 |
261 |
262 |
263 | {isShowEmoji && } 264 |
265 | 266 | 267 | 268 | {/* // 以下功能暂时未开发 */} 269 | {/* 270 | 271 | */} 272 |
273 | 283 |
284 |
285 | ); 286 | } 287 | -------------------------------------------------------------------------------- /src/renderer/container/Chat/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './index.scss'; 3 | import Chatlist from './Chatlist'; 4 | import Messages from './Messages.jsx'; 5 | import { MyContext } from '../../context-manager'; 6 | import { ipcRenderer } from 'electron'; 7 | 8 | function addDarkMode() { 9 | document.getElementsByTagName('body')[0].classList.add("dark-mode"); 10 | } 11 | 12 | function removeDarkMode() { 13 | document.getElementsByTagName('body')[0].classList.remove("dark-mode"); 14 | } 15 | 16 | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { 17 | addDarkMode(); 18 | } else { 19 | removeDarkMode(); 20 | } 21 | 22 | ipcRenderer.on('change-mode', (e, arg) => { 23 | console.log('change-mode') 24 | if (arg) { 25 | addDarkMode(); 26 | } else { 27 | removeDarkMode(); 28 | } 29 | }) 30 | 31 | function Chat() { 32 | const [msgData, setMsgData] = useState([]); 33 | return ( 34 | 35 |
36 |
37 |
38 | 39 | 40 | + 41 |
42 | 43 |
44 | 45 |
46 |
47 | ); 48 | } 49 | 50 | export default Chat; -------------------------------------------------------------------------------- /src/renderer/container/Chat/index.scss: -------------------------------------------------------------------------------- 1 | .right-wrap { 2 | float: left; 3 | width: calc(100% - 70px); 4 | height: 100%; 5 | } 6 | .list-wrap { 7 | margin-top: 50px; 8 | border-right: 1px solid #eee; 9 | } 10 | .chat-wrap { 11 | width: 260px; 12 | float: left; 13 | height: 100%; 14 | overflow: scroll; 15 | box-sizing: border-box; 16 | .search { 17 | box-sizing: border-box; 18 | padding: 14px 10px; 19 | width: 260px; 20 | height: 50px; 21 | position: fixed; 22 | background-color: #fff; 23 | z-index: 99; 24 | input { 25 | width: calc(100% - 84px); 26 | background-color: #eee; 27 | border: none; 28 | height: 22px; 29 | line-height: 22px; 30 | font-size: 12px; 31 | border-radius: 4px; 32 | padding-left: 26px; 33 | } 34 | .icon { 35 | display: block; 36 | width: 20px; 37 | height: 20px; 38 | background: url('../../../resources/images/icons/sousuo.png') no-repeat; 39 | position: absolute; 40 | top: 18px; 41 | left: 14px; 42 | } 43 | .plus { 44 | display: inline-block; 45 | margin-left: 10px; 46 | vertical-align: middle; 47 | border-radius: 2px; 48 | width: 24px; 49 | height: 20px; 50 | border: 1px solid #ddd; 51 | color: #999; 52 | font-size: 20px; 53 | line-height: 16px; 54 | text-align: center; 55 | } 56 | } 57 | .list-item { 58 | -webkit-user-select: none; 59 | height: 66px; 60 | padding: 8px 0; 61 | box-sizing: border-box; 62 | border-bottom: 1px solid #ddd; 63 | position: relative; 64 | &:first-child { 65 | background-color: #ddd; 66 | } 67 | padding-left: 10px; 68 | } 69 | .avatar { 70 | float: left; 71 | border: 1px solid #ddd; 72 | width: 44px; 73 | height: 44px; 74 | overflow: hidden; 75 | background: #fff; 76 | img { 77 | width: 44px; 78 | height: 44px; 79 | } 80 | } 81 | .info { 82 | float: left; 83 | margin-left: 10px; 84 | line-height: 22px; 85 | .name { 86 | font-weight: 500; 87 | font-size: 14px; 88 | margin: 0; 89 | width: 160px; 90 | overflow: hidden; 91 | text-overflow: ellipsis; 92 | white-space: nowrap; 93 | } 94 | .message { 95 | font-size: 12px; 96 | color: #888; 97 | margin: 0; 98 | width: 180px; 99 | overflow: hidden; 100 | text-overflow: ellipsis; 101 | white-space: nowrap; 102 | } 103 | .clearfix:after { 104 | content: '020'; 105 | display: block; 106 | height: 0; 107 | clear: both; 108 | visibility: hidden; 109 | } 110 | 111 | .clearfix { 112 | zoom: 1; 113 | } 114 | } 115 | .time { 116 | position: absolute; 117 | right: 10px; 118 | top: 8px; 119 | color: #888; 120 | font-size: 12px; 121 | } 122 | } 123 | 124 | .chat-container { 125 | background-color: rgb(243, 243, 243); 126 | float: left; 127 | width: calc(100% - 260px); 128 | height: 100%; 129 | .head { 130 | height: 50px; 131 | margin-left: 20px; 132 | line-height: 50px; 133 | -webkit-app-region: drag; 134 | -webkit-user-select: none; 135 | } 136 | .message-wrap { 137 | height: 400px; 138 | border-top: 1px solid #ddd; 139 | border-bottom: 1px solid #ddd; 140 | padding: 20px; 141 | box-sizing: border-box; 142 | overflow-y: scroll; 143 | overflow-x: hidden; 144 | } 145 | .edit-wrap { 146 | padding: 6px 8px; 147 | box-sizing: border-box; 148 | position: relative; 149 | .edit-tool { 150 | height: 40px; 151 | .face, 152 | .file, 153 | .screenshot, 154 | .messages, 155 | .phone, 156 | .video { 157 | display: inline-block; 158 | width: 24px; 159 | height: 24px; 160 | background-image: url('../../../resources/images/icons/xiaolian.png'); 161 | background-size: 100% 100%; 162 | margin-right: 6px; 163 | } 164 | .file { 165 | background-image: url('../../../resources/images/icons/wenjian.png'); 166 | } 167 | .screenshot { 168 | background-image: url('../../../resources/images/icons/jianqie.png'); 169 | } 170 | .messages { 171 | background-image: url('../../../resources/images/icons/xiaoxi.png'); 172 | } 173 | .phone { 174 | background-image: url('../../../resources/images/icons/dianhua.png'); 175 | float: right; 176 | } 177 | .video { 178 | background-image: url('../../../resources/images/icons/shipin.png'); 179 | float: right; 180 | } 181 | } 182 | .edit-box { 183 | resize: none; 184 | border: none; 185 | display: inline-block; 186 | background-color: transparent; 187 | width: 100%; 188 | height: 160px; 189 | background-color: rgb(243, 243, 243); 190 | } 191 | } 192 | } 193 | 194 | .edit-div { 195 | display: inline-block; 196 | background-color: rgb(243, 243, 243); 197 | width: 100%; 198 | height: 160px; 199 | text-align: left; 200 | resize: none; 201 | outline: none; 202 | overflow-y: scroll; 203 | padding: 10px; 204 | font-size: 14px; 205 | img { 206 | max-width: 100px; 207 | } 208 | } 209 | 210 | .dark-mode { 211 | .right-wrap, 212 | .chat-wrap .search, 213 | .chat-container, 214 | .user-wrap, 215 | .edit-div { 216 | background-color: rgba(0, 0, 0, 0.8); 217 | } 218 | .chat-wrap .search input { 219 | background-color: rgb(44, 44, 44); 220 | } 221 | .list-item { 222 | background-color: rgba(0, 0, 0, 0.9); 223 | border-bottom: none; 224 | &:first-child { 225 | background-color: rgba(0, 0, 0, 0.1); 226 | } 227 | } 228 | .edit-div, 229 | .chat-container .head, 230 | .list-item .name { 231 | color: #fff; 232 | } 233 | .chat-container, 234 | .msg-text { 235 | color: #000; 236 | } 237 | .avatar, 238 | .message-wrap, 239 | .list-wrap, 240 | .search { 241 | border: none; 242 | } 243 | } 244 | 245 | .font-size-10 { 246 | .chat-wrap .info { 247 | .name, 248 | .message, 249 | .time { 250 | font-size: 10px; 251 | } 252 | } 253 | .chat-container { 254 | .head { 255 | font-size: 12px; 256 | } 257 | .msg-text, 258 | .edit-div { 259 | font-size: 10px; 260 | } 261 | } 262 | } 263 | 264 | .font-size-12 { 265 | .chat-wrap .info { 266 | .name, 267 | .message, 268 | .time { 269 | font-size: 12px; 270 | } 271 | } 272 | .chat-container { 273 | .head { 274 | font-size: 14px; 275 | } 276 | .msg-text, 277 | .edit-div { 278 | font-size: 12px; 279 | } 280 | } 281 | } 282 | 283 | .font-size-14 { 284 | .chat-wrap .info { 285 | .name, 286 | .message, 287 | .time { 288 | font-size: 14px; 289 | } 290 | } 291 | .chat-container { 292 | .head { 293 | font-size: 16px; 294 | } 295 | .msg-text, 296 | .edit-div { 297 | font-size: 14px; 298 | } 299 | } 300 | } 301 | 302 | .font-size-16 { 303 | .chat-wrap .info { 304 | .name, 305 | .message, 306 | .time { 307 | font-size: 16px; 308 | } 309 | } 310 | .chat-container { 311 | .head { 312 | font-size: 18px; 313 | } 314 | .msg-text, 315 | .edit-div { 316 | font-size: 16px; 317 | } 318 | } 319 | } 320 | 321 | .font-size-18 { 322 | .chat-wrap .info { 323 | .name, 324 | .message, 325 | .time { 326 | font-size: 18px; 327 | } 328 | } 329 | .chat-container { 330 | .head { 331 | font-size: 20px; 332 | } 333 | .msg-text, 334 | .edit-div { 335 | font-size: 18px; 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/renderer/container/User/Setting.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.scss'; 3 | import { ipcRenderer } from 'electron'; 4 | 5 | const Setting = (props) => { 6 | function openSettings() { 7 | props.handleSetting(); 8 | ipcRenderer.send('open-settings'); 9 | } 10 | 11 | return ( 12 |
13 |
备份与恢复
14 |
设置
15 |
16 | ); 17 | }; 18 | 19 | export default Setting; 20 | -------------------------------------------------------------------------------- /src/renderer/container/User/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Setting from './Setting.jsx'; 3 | import './index.scss'; 4 | 5 | function User() { 6 | const [visible, setVisible] = useState(false); 7 | function handleSetting() { 8 | setVisible(!visible); 9 | } 10 | return ( 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {visible && } 19 |
20 | ); 21 | } 22 | 23 | export default User; 24 | -------------------------------------------------------------------------------- /src/renderer/container/User/index.scss: -------------------------------------------------------------------------------- 1 | .wrap { 2 | height: 100%; 3 | } 4 | 5 | .user-wrap { 6 | -webkit-app-region: drag; 7 | background-color: #000; 8 | width: 70px; 9 | height: 100%; 10 | text-align: center; 11 | float: left; 12 | position: relative; 13 | .avatar { 14 | display: block; 15 | width: 44px; 16 | margin-left: 10px; 17 | height: 44px; 18 | margin-top: 60px; 19 | background-image: url('../../../resources/images/users/user.png'); 20 | background-size: contain; 21 | } 22 | .chat, 23 | .contacts, 24 | .collect, 25 | .phone, 26 | .setting { 27 | display: block; 28 | width: 26px; 29 | height: 26px; 30 | margin-left: 18px; 31 | background-image: url('../../../resources/images/icons/bubble.png'); 32 | background-size: 100% 100%; 33 | margin-top: 30px; 34 | &.active { 35 | background-image: url('../../../resources/images/icons/bubble_green.png'); 36 | } 37 | } 38 | .contacts { 39 | background-image: url('../../../resources/images/icons/lianxiren.png'); 40 | &.active { 41 | background-image: url('../../../resources/images/icons/lianxiren_green.png'); 42 | } 43 | } 44 | .collect { 45 | background-image: url('../../../resources/images/icons/lifangti.png'); 46 | &.active { 47 | background-image: url('../../../resources/images/icons/lifangti_green.png'); 48 | } 49 | } 50 | .phone { 51 | background-image: url('../../../resources/images/icons/shouji.png'); 52 | position: absolute; 53 | bottom: 90px; 54 | left: 50%; 55 | margin-left: -18px; 56 | } 57 | .setting { 58 | width: 20px; 59 | height: 20px; 60 | background-image: url('../../../resources/images/icons/santiaogang.png'); 61 | position: absolute; 62 | bottom: 30px; 63 | left: 50%; 64 | margin-left: -14px; 65 | } 66 | } 67 | 68 | .setting-wrap { 69 | width: 120px; 70 | box-sizing: border-box; 71 | height: 80px; 72 | background-color: #333; 73 | color: #555; 74 | position: fixed; 75 | left: 70px; 76 | bottom: 10px; 77 | z-index: 999; 78 | text-align: left; 79 | .setting-item { 80 | line-height: 40px; 81 | height: 40px; 82 | padding-left: 10px; 83 | &:hover { 84 | background-color: #aaa; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/renderer/context-manager.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const MyContext = React.createContext(); 4 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | spider 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /src/renderer/index.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './App.jsx'; 4 | 5 | import { ipcRenderer } from 'electron'; 6 | 7 | ipcRenderer.on('change-fontsize', (e, arg) => { 8 | const fonsize = 10 + arg * 0.08; 9 | const cls = ['font-size-10', 'font-size-12', 'font-size-14', 'font-size-16', 'font-size-18']; 10 | console.log('change-fontsize', arg, fonsize); 11 | document.getElementsByTagName('body')[0].classList.remove(...cls); 12 | document.getElementsByTagName('body')[0].classList.add(`font-size-${fonsize}`); 13 | }) 14 | 15 | ReactDOM.render(, document.getElementById('appRoot')); -------------------------------------------------------------------------------- /src/renderer/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * type是消息类型,1文本,2系统话术,3图片 3 | * content 是消息内容 4 | * fromId 消息发送方 5 | * toId 消息接收方 6 | */ 7 | export function msgBody(type, content, fromId, toId, id) { 8 | return { 9 | type, 10 | content, 11 | fromId, 12 | toId, 13 | id: id || new Date().getTime(), 14 | } 15 | } -------------------------------------------------------------------------------- /src/resources/icns/spider.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/icns/spider.icns -------------------------------------------------------------------------------- /src/resources/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/favicon.ico -------------------------------------------------------------------------------- /src/resources/images/icons/bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/bubble.png -------------------------------------------------------------------------------- /src/resources/images/icons/bubble_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/bubble_green.png -------------------------------------------------------------------------------- /src/resources/images/icons/dianhua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/dianhua.png -------------------------------------------------------------------------------- /src/resources/images/icons/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/download.png -------------------------------------------------------------------------------- /src/resources/images/icons/jianqie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/jianqie.png -------------------------------------------------------------------------------- /src/resources/images/icons/lianxiren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/lianxiren.png -------------------------------------------------------------------------------- /src/resources/images/icons/lianxiren_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/lianxiren_green.png -------------------------------------------------------------------------------- /src/resources/images/icons/lifangti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/lifangti.png -------------------------------------------------------------------------------- /src/resources/images/icons/lifangti_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/lifangti_green.png -------------------------------------------------------------------------------- /src/resources/images/icons/refresh-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/refresh-line.png -------------------------------------------------------------------------------- /src/resources/images/icons/ren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/ren.png -------------------------------------------------------------------------------- /src/resources/images/icons/rotateleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/rotateleft.png -------------------------------------------------------------------------------- /src/resources/images/icons/rotateright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/rotateright.png -------------------------------------------------------------------------------- /src/resources/images/icons/santiaogang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/santiaogang.png -------------------------------------------------------------------------------- /src/resources/images/icons/shipin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/shipin.png -------------------------------------------------------------------------------- /src/resources/images/icons/shouji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/shouji.png -------------------------------------------------------------------------------- /src/resources/images/icons/sousuo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/sousuo.png -------------------------------------------------------------------------------- /src/resources/images/icons/wenjian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/wenjian.png -------------------------------------------------------------------------------- /src/resources/images/icons/xiaolian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/xiaolian.png -------------------------------------------------------------------------------- /src/resources/images/icons/xiaoxi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/xiaoxi.png -------------------------------------------------------------------------------- /src/resources/images/icons/zoomin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/zoomin.png -------------------------------------------------------------------------------- /src/resources/images/icons/zoomout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/icons/zoomout.png -------------------------------------------------------------------------------- /src/resources/images/users/bianfuxia-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/bianfuxia-big.png -------------------------------------------------------------------------------- /src/resources/images/users/bianfuxia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/bianfuxia.png -------------------------------------------------------------------------------- /src/resources/images/users/chaoren-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/chaoren-big.png -------------------------------------------------------------------------------- /src/resources/images/users/chaoren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/chaoren.png -------------------------------------------------------------------------------- /src/resources/images/users/gangtiexia-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/gangtiexia-big.png -------------------------------------------------------------------------------- /src/resources/images/users/gangtiexia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/gangtiexia.png -------------------------------------------------------------------------------- /src/resources/images/users/jinganglang-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/jinganglang-big.png -------------------------------------------------------------------------------- /src/resources/images/users/jinganglang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/jinganglang.png -------------------------------------------------------------------------------- /src/resources/images/users/leishen-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/leishen-big.png -------------------------------------------------------------------------------- /src/resources/images/users/leishen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/leishen.png -------------------------------------------------------------------------------- /src/resources/images/users/lvdengxia-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/lvdengxia-big.png -------------------------------------------------------------------------------- /src/resources/images/users/lvdengxia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/lvdengxia.png -------------------------------------------------------------------------------- /src/resources/images/users/lvjuren-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/lvjuren-big.png -------------------------------------------------------------------------------- /src/resources/images/users/lvjuren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/lvjuren.png -------------------------------------------------------------------------------- /src/resources/images/users/meiguoduichang-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/meiguoduichang-big.png -------------------------------------------------------------------------------- /src/resources/images/users/meiguoduichang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/meiguoduichang.png -------------------------------------------------------------------------------- /src/resources/images/users/ren-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/ren-big.png -------------------------------------------------------------------------------- /src/resources/images/users/ren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/ren.png -------------------------------------------------------------------------------- /src/resources/images/users/shandianxia-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/shandianxia-big.png -------------------------------------------------------------------------------- /src/resources/images/users/shandianxia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/shandianxia.png -------------------------------------------------------------------------------- /src/resources/images/users/sishi-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/sishi-big.png -------------------------------------------------------------------------------- /src/resources/images/users/sishi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/sishi.png -------------------------------------------------------------------------------- /src/resources/images/users/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/user.png -------------------------------------------------------------------------------- /src/resources/images/users/xiaochou-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/xiaochou-big.png -------------------------------------------------------------------------------- /src/resources/images/users/xiaochou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/xiaochou.png -------------------------------------------------------------------------------- /src/resources/images/users/yiren-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/yiren-big.png -------------------------------------------------------------------------------- /src/resources/images/users/yiren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/yiren.png -------------------------------------------------------------------------------- /src/resources/images/users/zhizhuxia-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/zhizhuxia-big.png -------------------------------------------------------------------------------- /src/resources/images/users/zhizhuxia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/zhizhuxia.png -------------------------------------------------------------------------------- /src/resources/images/users/zhizhuxia_1-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/zhizhuxia_1-big.png -------------------------------------------------------------------------------- /src/resources/images/users/zhizhuxia_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/users/zhizhuxia_1.png -------------------------------------------------------------------------------- /src/resources/images/zhizhuxia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/zhizhuxia.png -------------------------------------------------------------------------------- /src/resources/images/zhizhuxia_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/zhizhuxia_big.png -------------------------------------------------------------------------------- /src/resources/images/zhizhuxia_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/src/resources/images/zhizhuxia_small.png -------------------------------------------------------------------------------- /src/setting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 92 | 93 |
94 |
账号信息
95 |
96 | 97 | Ivy 98 | 99 |
100 |
101 | 102 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |

121 |
122 |
123 |
124 | 125 |
126 |

退出登录

127 | 128 |
129 | 130 | 131 |
132 |
133 |
134 | 135 | 170 | 171 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | # todo list 2 | 3 | 1. 文件拖放 发送图片 4 | 2. 优化截屏——页面变得模糊?? 5 | 3. API升级 6 | 4. 从9升级到13后,crashreport收集不到数据(9.0.3 版本可用) 7 | 5. remote废弃 8 | -------------------------------------------------------------------------------- /updater-server/index.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa'); 2 | const app = new Koa(); 3 | const Router = require('koa-router'); 4 | const serve = require('koa-static-server'); 5 | const router = new Router(); 6 | const compareVersions = require('compare-versions'); 7 | const multer = require('koa-multer'); 8 | 9 | const upload = multer({ dest: 'crash/' }); 10 | 11 | router.post('/crash', upload.single('upload_file_minidump'), (ctx) => { 12 | console.log('file', ctx.req.file); 13 | console.log('body', ctx.req.body); 14 | ctx.body = '上传成功'; 15 | // todo 存DB 16 | }); 17 | 18 | function getNewVersion(version) { 19 | if (!version) return null; 20 | const maxVersion = { 21 | name: '1.0.1', 22 | pub_date: '2021-06-24T12:26:53+1:00', 23 | notes: '新增功能: 自定义下载路径', 24 | url: `http://127.0.0.1:9999/public/spiderchat-1.0.1-mac.zip`, 25 | }; 26 | if (compareVersions.compare(maxVersion.name, version, '>')) { 27 | return maxVersion; 28 | } 29 | return null; 30 | } 31 | 32 | router.get('/darwin', (ctx, next) => { 33 | const { version } = ctx.query; 34 | const newVersion = getNewVersion(version); 35 | if (newVersion) { 36 | ctx.body = newVersion; 37 | } else { 38 | ctx.status = 204; 39 | } 40 | }); 41 | app.use( 42 | serve({ 43 | rootDir: 'public', 44 | rootPath: '/public', 45 | }) 46 | ); 47 | app.use(router.routes()).use(router.allowedMethods()); 48 | 49 | app.listen(9999); 50 | -------------------------------------------------------------------------------- /updater-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "updater-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@koa/multer": "^3.0.0", 14 | "compare-versions": "^3.5.1", 15 | "koa": "^2.11.0", 16 | "koa-multer": "^1.0.2", 17 | "koa-router": "^8.0.7", 18 | "koa-static-server": "^1.4.0", 19 | "minidump": "^0.19.0", 20 | "multer": "^1.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /updater-server/parsedump.js: -------------------------------------------------------------------------------- 1 | const minidump = require('minidump'); 2 | const fs = require('fs'); 3 | 4 | // symbolpath 5 | minidump.addSymbolPath('/Users/tangting/tt/soft/electron-v9/breakpad_symbols/'); 6 | 7 | minidump.walkStack('./crash/c643b202bb5f8203b23d34037cc8690e',(err, res)=>{ 8 | fs.writeFileSync('./error/error.txt', res); 9 | }) -------------------------------------------------------------------------------- /updater-server/public/spiderchat-1.0.1-mac.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/updater-server/public/spiderchat-1.0.1-mac.zip -------------------------------------------------------------------------------- /upload/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttang1024/electron-learn/8b56576c32ec91ceb21eefda4e61b1caeb7d485e/upload/default.png -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const allEnvConfigs = require( './webpack_configs/index.js' ); 2 | 3 | const DEFAULT_ENV = 'dev'; 4 | 5 | module.exports = function( cliEnv ) { 6 | let cliEnvConfig = allEnvConfigs[ cliEnv ]; 7 | 8 | if( !cliEnvConfig ) { 9 | let avaibleEnvs = Object.keys( allEnvConfigs ).join(' '); 10 | console.warn(` 11 | Provided environment "${cliEnv}" was not found. 12 | Please use one of the following ones: ${avaibleEnvs} 13 | `); 14 | } 15 | 16 | return cliEnvConfig || allEnvConfigs[ DEFAULT_ENV ]; 17 | } 18 | -------------------------------------------------------------------------------- /webpack_configs/absulotePath.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | 3 | module.exports = { 4 | root: path.resolve(__dirname, '../'), 5 | dist: path.resolve(__dirname, '../dist'), 6 | build: path.resolve(__dirname, '../build'), 7 | src: path.resolve(__dirname, '../src') 8 | }; -------------------------------------------------------------------------------- /webpack_configs/index.js: -------------------------------------------------------------------------------- 1 | const devConfig = require( './webpack.dev.js' ); 2 | const prodConfig = require( './webpack.prod.js' ); 3 | 4 | module.exports = { 5 | dev: devConfig, 6 | prod: prodConfig 7 | }; -------------------------------------------------------------------------------- /webpack_configs/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | const AbsulotePath = require('./absulotePath'); 6 | 7 | var appConfig = { 8 | entry: { 9 | // app: ['babel-polyfill', './src/renderer/index.jsx'], 10 | app: ['./src/renderer/index.jsx'], 11 | }, 12 | plugins: [ 13 | new CleanWebpackPlugin(), 14 | new HtmlWebpackPlugin({ 15 | template: 'index.html', 16 | }), 17 | new MiniCssExtractPlugin({ 18 | filename: 'styles/[name].[hash:8].css', 19 | }), 20 | ], 21 | optimization: { 22 | splitChunks: { 23 | cacheGroups: { 24 | commons: { 25 | test: /[\\/]node_modules[\\/]/, 26 | name: 'vendors', 27 | chunks: 'all', 28 | reuseExistingChunk: true, 29 | }, 30 | }, 31 | }, 32 | }, 33 | target: 'electron-renderer', 34 | output: { 35 | filename: 'scripts/[name].[hash:8].js', 36 | path: AbsulotePath.build, 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /.js$/, 42 | exclude: /node_modules/, 43 | loader: 'babel-loader', 44 | options: { 45 | presets: [['env', { modules: false }], 'stage-2', 'react'] 46 | }, 47 | }, 48 | { 49 | test: /.jsx$/, 50 | exclude: /node_modules/, 51 | loader: 'babel-loader', 52 | options: { 53 | presets: ['react'], 54 | }, 55 | }, 56 | { 57 | test: /\.html$/, 58 | loader: 'html-loader', 59 | }, 60 | { 61 | test: /.css$/, 62 | use: [ 63 | { 64 | loader: MiniCssExtractPlugin.loader, 65 | options: { 66 | publicPath: '../' 67 | }, 68 | }, 69 | 'css-loader', 70 | 'postcss-loader', 71 | ], 72 | }, 73 | { 74 | test: /.(png|svg|jpg|jpeg|gif)$/, 75 | loader: 'file-loader', 76 | options: { 77 | esModule: false, // 这里设置为false 78 | name: '[name].[ext]', 79 | outputPath: 'images/', 80 | }, 81 | }, 82 | { 83 | test: /\.(woff|woff2|eot|ttf|otf|svg)$/, 84 | loader: 'file-loader', 85 | options: { 86 | name: '[name].[ext]', 87 | publicPath: '../iconfont/', 88 | outputPath: 'iconfont/', 89 | }, 90 | }, 91 | ], 92 | }, 93 | }; 94 | 95 | module.exports = appConfig; 96 | -------------------------------------------------------------------------------- /webpack_configs/webpack.dev.js: -------------------------------------------------------------------------------- 1 | // utils 2 | const merge = require( 'webpack-merge' ); 3 | // configs 4 | const commons = require( './webpack.common.js' ); 5 | 6 | /* --------------------------- main ---------------------------- */ 7 | module.exports = [ merge( commons, { 8 | mode: 'development', 9 | devtool: 'inline-source-map', 10 | devServer: { 11 | contentBase: './build', 12 | disableHostCheck: true, 13 | port: 9200 14 | } 15 | } ) 16 | ]; -------------------------------------------------------------------------------- /webpack_configs/webpack.prod.js: -------------------------------------------------------------------------------- 1 | // utils 2 | const webpack = require( 'webpack' ); 3 | const merge = require( 'webpack-merge' ); 4 | // plugins 5 | const UglifyJSPlugin = require( 'uglifyjs-webpack-plugin' ); 6 | // configs 7 | const commons = require( './webpack.common.js' ); 8 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 9 | 10 | /* --------------------------- main ---------------------------- */ 11 | module.exports = [ merge( commons, { 12 | mode: 'production', 13 | devtool: 'cheap-module-source-map', 14 | plugins: [ 15 | new UglifyJSPlugin( { 16 | sourceMap: true 17 | } ), 18 | new webpack.DefinePlugin( { 19 | 'process.env.NODE_ENV': JSON.stringify( 'production' ) 20 | } ) 21 | ] 22 | } ) 23 | ]; -------------------------------------------------------------------------------- /ws-server/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebSocket Chat 6 | 7 | 47 | 48 | 126 |
127 |

WebSocket:

128 |
129 |
130 | 131 | 132 |
133 |
134 |
135 | 136 | 137 | -------------------------------------------------------------------------------- /ws-server/index.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require('ws'); 2 | 3 | const server = new WebSocket.Server({ port: 8080 }); 4 | 5 | server.on('open', () => { 6 | console.log('connected'); 7 | }); 8 | 9 | server.on('close', () => { 10 | console.log('disconnected'); 11 | }); 12 | 13 | server.on('connection', (ws, req, client) => { 14 | ws.on('message', (message) => { 15 | console.log('received: %s', message); 16 | 17 | // 广播消息给所有客户端 18 | server.clients.forEach((client) => { 19 | if (client.readyState === WebSocket.OPEN) { 20 | client.send(message); 21 | } 22 | }); 23 | }); 24 | }); 25 | --------------------------------------------------------------------------------