Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80c05a173e | ||
|
|
46f54e12b7 | ||
|
|
32eb17660d | ||
|
|
dfbc96f1c0 | ||
|
|
d8b89da1ad | ||
|
|
063962d1ae | ||
|
|
7363be798c | ||
|
|
b7d2edbe23 | ||
|
|
f75514d05d | ||
|
|
34657ae1fa | ||
|
|
455cb4937e | ||
|
|
4f392f3628 | ||
|
|
6801334216 | ||
|
|
520d6d3fa8 | ||
|
|
911e34e98d | ||
|
|
66d8d714db | ||
|
|
624e5738d1 | ||
|
|
c011654a57 | ||
|
|
fc897cea04 | ||
|
|
cbafe66a75 | ||
|
|
5969daedac | ||
|
|
f0787d3ec2 | ||
|
|
95556498f7 | ||
|
|
44f5fbbb91 | ||
|
|
05998bba0d | ||
|
|
b483092c68 | ||
|
|
bf75abc2f1 | ||
|
|
167e424568 | ||
|
|
64e54d0b0b | ||
|
|
33bfaa5959 | ||
|
|
9317871fdb | ||
|
|
a355bc07f4 | ||
|
|
ecd462fbd4 | ||
|
|
4de7a1d5f2 | ||
|
|
404d7667e0 | ||
|
|
012a01d5d9 | ||
|
|
9791ff24d8 | ||
|
|
7677f1ea2a | ||
|
|
3c01861122 | ||
|
|
ae87a512a1 | ||
|
|
531bc13cca | ||
|
|
72371de40b | ||
|
|
d438f829e5 | ||
|
|
0ed7755508 | ||
|
|
8cfbc696a4 | ||
|
|
389bd7096d | ||
|
|
e8489182d7 | ||
|
|
7b50ba7ddb | ||
|
|
20e38f4eb9 | ||
|
|
b6b2eeae6b | ||
|
|
5f1fb08368 | ||
|
|
d50ad3a592 | ||
|
|
428ffc7ef1 | ||
|
|
86012c0c7b |
@@ -8,7 +8,7 @@ module.exports = {
|
||||
},
|
||||
parser: 'vue-eslint-parser',
|
||||
extends: [
|
||||
'plugin:vue/essential',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'@vue/standard',
|
||||
'@vue/typescript'
|
||||
],
|
||||
|
||||
@@ -44,6 +44,7 @@ body:
|
||||
- Mac
|
||||
- Mac(arm64)
|
||||
- Linux
|
||||
- All
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -44,6 +44,7 @@ body:
|
||||
- Mac
|
||||
- Mac(arm64)
|
||||
- Linux
|
||||
- All
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -56,3 +56,6 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
PICGO_ENV_COS_SECRET_ID: ${{ secrets.PICGO_ENV_COS_SECRET_ID }}
|
||||
PICGO_ENV_COS_SECRET_KEY: ${{ secrets.PICGO_ENV_COS_SECRET_KEY }}
|
||||
PICGO_ENV_S3_SECRET_ID: ${{ secrets.PICGO_ENV_S3_SECRET_ID }}
|
||||
PICGO_ENV_S3_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_SECRET_KEY }}
|
||||
PICGO_ENV_S3_ACCOUNT_ID: ${{ secrets.PICGO_ENV_S3_ACCOUNT_ID }}
|
||||
|
||||
@@ -53,3 +53,6 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
PICGO_ENV_COS_SECRET_ID: ${{ secrets.PICGO_ENV_COS_SECRET_ID }}
|
||||
PICGO_ENV_COS_SECRET_KEY: ${{ secrets.PICGO_ENV_COS_SECRET_KEY }}
|
||||
PICGO_ENV_S3_SECRET_ID: ${{ secrets.PICGO_ENV_S3_SECRET_ID }}
|
||||
PICGO_ENV_S3_SECRET_KEY: ${{ secrets.PICGO_ENV_S3_SECRET_KEY }}
|
||||
PICGO_ENV_S3_ACCOUNT_ID: ${{ secrets.PICGO_ENV_S3_ACCOUNT_ID }}
|
||||
|
||||
@@ -19,3 +19,6 @@ dist_electron/
|
||||
test.js
|
||||
.env
|
||||
scripts/*.yml
|
||||
|
||||
#Electron-builder output
|
||||
/dist_electron
|
||||
@@ -1,3 +1,138 @@
|
||||
# :tada: 2.4.0-beta.1 (2023-05-03)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add picgo toolbox for auto detect & fix problems ([dfbc96f](https://github.com/Molunerfinn/PicGo/commit/dfbc96f))
|
||||
* add settings.encodeOutputURL options ([f75514d](https://github.com/Molunerfinn/PicGo/commit/f75514d)), closes [#731](https://github.com/Molunerfinn/PicGo/issues/731)
|
||||
* add showDockIcon option ([46f54e1](https://github.com/Molunerfinn/PicGo/commit/46f54e1)), closes [#1045](https://github.com/Molunerfinn/PicGo/issues/1045)
|
||||
* add showDockIcon option ([32eb176](https://github.com/Molunerfinn/PicGo/commit/32eb176)), closes [#1045](https://github.com/Molunerfinn/PicGo/issues/1045)
|
||||
* support dragging any type of file to upload ([520d6d3](https://github.com/Molunerfinn/PicGo/commit/520d6d3)), closes [#1052](https://github.com/Molunerfinn/PicGo/issues/1052)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* console.log EPIPE error ([7363be7](https://github.com/Molunerfinn/PicGo/commit/7363be7)), closes [#1101](https://github.com/Molunerfinn/PicGo/issues/1101)
|
||||
* custom url template encode bug ([063962d](https://github.com/Molunerfinn/PicGo/commit/063962d)), closes [#1112](https://github.com/Molunerfinn/PicGo/issues/1112)
|
||||
* fix copy link encoding bug ([34657ae](https://github.com/Molunerfinn/PicGo/commit/34657ae)), closes [#731](https://github.com/Molunerfinn/PicGo/issues/731)
|
||||
* i18n bug ([911e34e](https://github.com/Molunerfinn/PicGo/commit/911e34e))
|
||||
* isDarkMode() error when dragging file to tray icon ([b7d2edb](https://github.com/Molunerfinn/PicGo/commit/b7d2edb)), closes [#1107](https://github.com/Molunerfinn/PicGo/issues/1107)
|
||||
* typescript nightly build bug ([455cb49](https://github.com/Molunerfinn/PicGo/commit/455cb49)), closes [#1082](https://github.com/Molunerfinn/PicGo/issues/1082)
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* change version files' upload dest & dist files' upload dest ([4f392f3](https://github.com/Molunerfinn/PicGo/commit/4f392f3))
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* update FAQ ([6801334](https://github.com/Molunerfinn/PicGo/commit/6801334)), closes [#1067](https://github.com/Molunerfinn/PicGo/issues/1067)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.0 (2023-01-05)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add file-name in gallery & add unknown-file-type placholder image ([f0787d3](https://github.com/Molunerfinn/PicGo/commit/f0787d3)), closes [#1050](https://github.com/Molunerfinn/PicGo/issues/1050)
|
||||
* add file-name in tray-page ([c011654](https://github.com/Molunerfinn/PicGo/commit/c011654)), closes [#1054](https://github.com/Molunerfinn/PicGo/issues/1054)
|
||||
* support multiple config ([#1016](https://github.com/Molunerfinn/PicGo/issues/1016)) ([9555649](https://github.com/Molunerfinn/PicGo/commit/9555649))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* handleUploaderConfig should be placed in main/utils ([fc897ce](https://github.com/Molunerfinn/PicGo/commit/fc897ce))
|
||||
* nsis script ([44f5fbb](https://github.com/Molunerfinn/PicGo/commit/44f5fbb)), closes [#1019](https://github.com/Molunerfinn/PicGo/issues/1019)
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* update FAQ.md ([#1011](https://github.com/Molunerfinn/PicGo/issues/1011)) ([05998bb](https://github.com/Molunerfinn/PicGo/commit/05998bb))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.3.1 (2022-11-13)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add $extName for paste template ([64e54d0](https://github.com/Molunerfinn/PicGo/commit/64e54d0)), closes [#1000](https://github.com/Molunerfinn/PicGo/issues/1000)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* upyun options is not required ([167e424](https://github.com/Molunerfinn/PicGo/commit/167e424)), closes [#1002](https://github.com/Molunerfinn/PicGo/issues/1002)
|
||||
|
||||
|
||||
|
||||
## :tada: 2.3.1-beta.8 (2022-10-30)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add remoteNotice ([9317871](https://github.com/Molunerfinn/PicGo/commit/9317871))
|
||||
* macOS tray icon more clearer ([ecd462f](https://github.com/Molunerfinn/PicGo/commit/ecd462f))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* url encode bug && copy-paste url encode bug ([4de7a1d](https://github.com/Molunerfinn/PicGo/commit/4de7a1d)), closes [#996](https://github.com/Molunerfinn/PicGo/issues/996)
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* add PicHoro ([a355bc0](https://github.com/Molunerfinn/PicGo/commit/a355bc0))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.3.1-beta.7 (2022-10-23)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add zh-TW.yml ([#976](https://github.com/Molunerfinn/PicGo/issues/976)) ([72371de](https://github.com/Molunerfinn/PicGo/commit/72371de))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* change decodeURI -> decodeURIComponent & encode also ([7677f1e](https://github.com/Molunerfinn/PicGo/commit/7677f1e))
|
||||
* macOS tray icon background color bug ([9791ff2](https://github.com/Molunerfinn/PicGo/commit/9791ff2)), closes [#970](https://github.com/Molunerfinn/PicGo/issues/970)
|
||||
* some bugs will case picgo-gui-local.log too large ([012a01d](https://github.com/Molunerfinn/PicGo/commit/012a01d)), closes [#995](https://github.com/Molunerfinn/PicGo/issues/995)
|
||||
* some case will cause picgo-gui-local.log too large ([3c01861](https://github.com/Molunerfinn/PicGo/commit/3c01861))
|
||||
* some texts in zh-TW ([#978](https://github.com/Molunerfinn/PicGo/issues/978)) ([531bc13](https://github.com/Molunerfinn/PicGo/commit/531bc13))
|
||||
|
||||
|
||||
### :pencil: Documentation
|
||||
|
||||
* update readme & FAQ ([d438f82](https://github.com/Molunerfinn/PicGo/commit/d438f82))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.3.1-beta.6 (2022-09-04)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* cli support uploading image with url ([e848918](https://github.com/Molunerfinn/PicGo/commit/e848918))
|
||||
* finish i18n system ([428ffc7](https://github.com/Molunerfinn/PicGo/commit/428ffc7))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* macos clipboard image can't show on tray page ([20e38f4](https://github.com/Molunerfinn/PicGo/commit/20e38f4)), closes [#961](https://github.com/Molunerfinn/PicGo/issues/961)
|
||||
* showFileExplorer result bug ([b6b2eea](https://github.com/Molunerfinn/PicGo/commit/b6b2eea))
|
||||
* windows upload clipboard file with builtin-clipboard not work ([7b50ba7](https://github.com/Molunerfinn/PicGo/commit/7b50ba7))
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* up issue template ([5f1fb08](https://github.com/Molunerfinn/PicGo/commit/5f1fb08))
|
||||
* update cos upload url ([86012c0](https://github.com/Molunerfinn/PicGo/commit/86012c0))
|
||||
|
||||
|
||||
|
||||
## :tada: 2.3.1-beta.5 (2022-08-14)
|
||||
|
||||
|
||||
@@ -472,6 +607,3 @@
|
||||
### :package: Chore
|
||||
|
||||
* add picgo bump-version ([37f1d34](https://github.com/Molunerfinn/PicGo/commit/37f1d34))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
## 贡献指南
|
||||
|
||||
### 安装与启动
|
||||
|
||||
1. 使用 [yarn](https://yarnpkg.com/) 安装依赖
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
然后通过
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
启动项目。
|
||||
|
||||
2. 只跟 Electron 主进程相关的代码请在 `src/main` 目录下添加。只跟渲染进程相关的代码请在 `src/renderer` 目录下添加。两个进程都能使用的代码请在 `src/universal` 目录下添加。 **注意**:渲染进程没有 `Node.js` 能力,所有渲染进程需要用到 `Node.js` 模块相关的代码请在 `src/main/events/picgoCoreIPC.ts` 下添加事件进行处理。
|
||||
|
||||
3. 所有的跨进程事件名请在 `src/universal/events/constants.ts` 里添加。
|
||||
|
||||
4. 所有的全局类型定义请在 `src/universal/types/` 里添加,如果是 `enum`,请在 `src/universal/types/enum.ts` 里添加。
|
||||
|
||||
|
||||
### i18n
|
||||
|
||||
1. 在 `public/i18n/` 下面创建一种语言的 `yml` 文件,例如 `zh-Hans.yml`。然后参考 `zh-CN.yml` 或者 `en.yml` 编写语言文件。并注意,PicGo 会通过语言文件中的 `LANG_DISPLAY_LABEL` 向用户展示该语言的名称。
|
||||
|
||||
2. 在 `src/universal/i18n/index.ts` 里添加一种默认语言。其中 `label` 就是语言文件中 `LANG_DISPLAY_LABEL` 的值,`value` 是语言文件名。
|
||||
|
||||
3. 如果是对已有语言文件进行更新,请在更新完,务必运行一遍 `yarn gen-i18n`,确保能生成正确的语言定义文件。
|
||||
|
||||
### 提交代码
|
||||
|
||||
1. 请检查代码没有多余的注释、`console.log` 等调试代码。
|
||||
2. 提交代码前,请执行命令 `git add . && yarn cz`,唤起 PicGo 的[代码提交规范工具](https://github.com/PicGo/bump-version)。通过该工具提交代码。
|
||||
@@ -0,0 +1,37 @@
|
||||
## Contribution Guidelines
|
||||
|
||||
### Installation and startup
|
||||
|
||||
1. Use [yarn](https://yarnpkg.com/) to install dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
then pass
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Startup project.
|
||||
|
||||
2. Please add code only related to the main process of Electron in the `src/main` directory. Code only related to the rendering process should be added in the `src/renderer` directory. Add code that can be used by both processes in the `src/universal` directory. **Note**: The rendering process does not have the `Node.js` capability. All rendering processes need to use `Node.js modules` related code, please add events under `src/main/events/picgoCoreIPC.ts` for processing.
|
||||
|
||||
3. Please add all cross-process event names in `src/universal/events/constants.ts`.
|
||||
|
||||
4. Please add all global type definitions in `src/universal/types/`, if it is `enum`, please add it in `src/universal/types/enum.ts`.
|
||||
|
||||
|
||||
### i18n
|
||||
|
||||
1. Create a language `yml` file under `public/i18n/`, for example `zh-Hans.yml`. Then refer to `zh-CN.yml` or `en.yml` to write language files. Also note that PicGo will display the name of the language to the user via `LANG_DISPLAY_LABEL` in the language file.
|
||||
|
||||
2. Add a default language to `src/universal/i18n/index.ts`. where `label` is the value of `LANG_DISPLAY_LABEL` in the language file, and `value` is the name of the language file.
|
||||
|
||||
3. If you are updating an existing language file, be sure to run `yarn gen-i18n` after the update to ensure that the correct language definition file can be generated.
|
||||
|
||||
### Submit code
|
||||
|
||||
1. Please check that the code has no extra comments, `console.log` and other debugging code.
|
||||
2. Before submitting the code, please execute the command `git add . && yarn cz` to invoke PicGo's [Code Submission Specification Tool](https://github.com/PicGo/bump-version). Submit code through this tool.
|
||||
@@ -57,16 +57,76 @@ PicGo 在 Mac 上是一个顶部栏应用,在 dock 栏是不会有图标的。
|
||||
1. 先自行搜索 error 里的报错信息,往往你能百度或者谷歌出问题原因,不必开 issue。
|
||||
2. 如果有带有 `401` 、`403` 等 `40X` 状态码字样的,不用怀疑,就是你配置写错了,仔细检查配置,看看是否多了空格之类的。
|
||||
3. 如果带有 `HttpError`、`RequestError` 、 `socket hang up` 等字样的说明这是网络问题,我无法帮你解决网络问题,请检查你自己的网络,是否有代理,DNS 设置是否正常等。
|
||||
3. 通常网络问题引起的上传失败都是因为代理设置不当导致的。如果开启了系统代理,建议同时也在 PicGo 的代理设置中设置对应的HTTP代理。参考 [#912](https://github.com/Molunerfinn/PicGo/issues/912)
|
||||
|
||||
## 10. macOS版本安装完之后没有主界面
|
||||
|
||||
请找到PicGo在顶部栏的图标,然后右键(触摸板双指点按,或者鼠标右键),即可找到「打开详细窗口」的菜单。
|
||||
|
||||
## 11. 相册突然无法显示图片或者上传后相册不更新
|
||||
## 11. 相册突然无法显示图片 或者 上传后相册不更新 或者 使用Typora+PicGo上传图片成功但是没有写回Typora
|
||||
|
||||
这个原因可能是相册存储文件损坏导致的。可以找到 PicGo 配置文件所在路径下的 `picgo.db` ,将其删掉(删掉前建议备份一遍),再重启 PicGo 试试。
|
||||
注意同时看看日志文件里有没有什么error,必要时可以提issue。
|
||||
注意同时看看日志文件里有没有什么error,必要时可以提issue。2.3.0以上的版本已经解决因为 `picgo.db` 损坏导致的上述问题,建议更新版本。
|
||||
|
||||
## 12. Gitee相关问题
|
||||
|
||||
如果在使用 Gitee 图床的时候遇到上传的问题,由于 PicGo 并没有官方提供 Gitee 上传服务,无法帮你解决,请去你所使用的 Gitee 插件仓库发相关的issue。
|
||||
|
||||
## 13. macOS系统安装完PicGo显示「文件已损坏」或者安装完打开没有反应
|
||||
|
||||
因为 PicGo 没有签名,所以会被 macOS 的安全检查所拦下。
|
||||
|
||||
1. 安装后打开遇到「文件已损坏」的情况,请按如下方式操作:
|
||||
|
||||
信任开发者,会要求输入密码:
|
||||
|
||||
```
|
||||
sudo spctl --master-disable
|
||||
```
|
||||
|
||||
然后放行 PicGo :
|
||||
|
||||
```
|
||||
xattr -cr /Applications/PicGo.app
|
||||
```
|
||||
|
||||
然后就能正常打开。
|
||||
|
||||
如果提示以下内容
|
||||
|
||||
```sh
|
||||
option -r not recognized
|
||||
|
||||
usage: xattr [-slz] file [file ...]
|
||||
xattr -p [-slz] attr_name file [file ...]
|
||||
xattr -w [-sz] attr_name attr_value file [file ...]
|
||||
xattr -d [-s] attr_name file [file ...]
|
||||
xattr -c [-s] file [file ...]
|
||||
|
||||
The first form lists the names of all xattrs on the given file(s).
|
||||
The second form (-p) prints the value of the xattr attr_name.
|
||||
The third form (-w) sets the value of the xattr attr_name to attr_value.
|
||||
The fourth form (-d) deletes the xattr attr_name.
|
||||
The fifth form (-c) deletes (clears) all xattrs.
|
||||
|
||||
options:
|
||||
-h: print this help
|
||||
-s: act on symbolic links themselves rather than their targets
|
||||
-l: print long format (attr_name: attr_value)
|
||||
-z: compress or decompress (if compressed) attribute value in zip format
|
||||
```
|
||||
执行命令
|
||||
|
||||
```
|
||||
xattr -c /Applications/PicGo.app/*
|
||||
```
|
||||
|
||||
如果上述命令依然没有效果,可以尝试下面的命令:
|
||||
|
||||
```
|
||||
sudo xattr -d com.apple.quarantine /Applications/PicGo.app/
|
||||
```
|
||||
|
||||
2. 如果安装打开后没有反应,请按下方顺序排查:
|
||||
1. macOS安装好之后,PicGo 是不会弹出主窗口的,因为 PicGo 在 macOS 系统里设计是个顶部栏应用。注意看你顶部栏的图标,如果有 PicGo 的图标,说明安装成功了,点击图标即可打开顶部栏窗口。参考上述[第八点](#8-mac-上无法打开-picgo-的主窗口界面)。
|
||||
2. 如果你是 M1 的系统,此前装过 PicGo 的 x64 版本,但是后来更新了 arm64 的版本发现打开后没反应,请重启电脑即可。
|
||||
|
||||
@@ -48,35 +48,21 @@ PicGo 本体支持如下图床:
|
||||
- 支持通过发送 HTTP 请求调用 PicGo 上传(v2.2.0+)
|
||||
- 更多功能等你自己去发现,同时也会不断开发新功能
|
||||
- 开发进度可以查看 [Projects](https://github.com/Molunerfinn/PicGo/projects),会同步更新开发进度
|
||||
- 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流
|
||||
<!-- - 欢迎加入 [官方讨论区](https://github.com/Molunerfinn/PicGo/discussions) 与我交流 -->
|
||||
|
||||
**如果第一次使用,请参考应用 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html)。遇到问题了还可以看看 [FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md) 以及被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed)。**
|
||||
|
||||
## 下载安装
|
||||
|
||||
点击此处下载 [应用](https://github.com/Molunerfinn/PicGo/releases)。从 2.3.1-beta.4 版本开始,在下载页提供 [腾讯云COS](https://cloud.tencent.com/product/cos) 下载链接,供国内用户快速下载使用。感谢 [腾讯云COS](https://cloud.tencent.com/product/cos) 提供的赞助支持!
|
||||
|
||||
还可以通过山东大学镜像站提供的镜像地址下载 [应用](https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo)。感谢山东大学镜像站提供的镜像服务!
|
||||
|
||||
### Windows
|
||||
|
||||
Windows 用户请下载最新版本的 `exe` 文件。
|
||||
|
||||
**还可以使用 [Scoop](https://scoop.sh/) 来安装 PicGo: `scoop bucket add helbing https://github.com/helbing/scoop-bucket` & `scoop install picgo`。 感谢 @helbing 的贡献!**
|
||||
|
||||
**还可以使用 [Chocolatey](https://chocolatey.org/) 来安装 PicGo: `choco install picgo`。 感谢 @iYato 的贡献!**
|
||||
|
||||
### macOS
|
||||
|
||||
macOS 用户请下载最新版本的 `dmg` 文件。
|
||||
|
||||
**还可以使用 [Homebrew](https://brew.sh/) 来安装 PicGo: `brew install picgo --cask`。感谢 @womeimingzi11 的贡献!**
|
||||
|
||||
### Linux
|
||||
|
||||
Linux 用户请下载 `AppImage` 文件。
|
||||
|
||||
**如果你是 Arch 类的 Linux 用户,可以直接通过 `yay -S picgo-appimage` 来安装 PicGo (若是没有则先安装 yay ,是 aur 仓库助手)。感谢 @houbaron 的贡献!**
|
||||
| 下载源 | 地址/安装方式 | 平台 | 备注 |
|
||||
|---|---|---|---|
|
||||
| GitHub Release | https://github.com/Molunerfinn/PicGo/releases | All | 国内下载速度可能会慢 |
|
||||
| [腾讯云COS](https://cloud.tencent.com/product/cos) | https://github.com/Molunerfinn/PicGo/releases 附在更新日志结尾 | All | 感谢 [腾讯云COS](https://cloud.tencent.com/product/cos) 提供的赞助支持 |
|
||||
| [山东大学镜像站](https://mirrors.sdu.edu.cn/) | https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo | All | 感谢 [山东大学镜像站](https://mirrors.sdu.edu.cn/) 提供的镜像支持 |
|
||||
| [Scoop](https://scoop.sh/) | `scoop bucket add helbing https://github.com/helbing/scoop-bucket` & `scoop install picgo` | Windows | 感谢 @helbing 的贡献 |
|
||||
| [Chocolatey](https://chocolatey.org/) | `choco install picgo` | Windows | 感谢 @iYato 的贡献 |
|
||||
| [Homebrew](https://brew.sh/) | `brew install picgo --cask` | macOS | 感谢 @womeimingzi11 的贡献 |
|
||||
| [AUR](https://aur.archlinux.org/packages/yay) | `yay -S picgo-appimage` | Arch-Linux | 感谢 @houbaron 的贡献 |
|
||||
|
||||
## 应用截图
|
||||
|
||||
@@ -96,6 +82,7 @@ Linux 用户请下载 `AppImage` 文件。
|
||||
2. `git clone https://github.com/Molunerfinn/PicGo.git` 并进入项目。
|
||||
3. `yarn` 下载依赖。注意如果你没有 `yarn`,请去 [官网](https://classic.yarnpkg.com/en/docs/install) 下载安装后再使用。 **用 `npm install` 将导致未知错误!**
|
||||
4. Mac 需要有 Xcode 环境,Windows 需要有 VS 环境。
|
||||
5. 如果需要贡献代码,可以参考[贡献指南](./CONTRIBUTING.md)。
|
||||
|
||||
### 开发模式
|
||||
|
||||
@@ -125,7 +112,8 @@ npm run electron:build
|
||||
## 其他相关
|
||||
|
||||
- [vs-picgo](https://github.com/PicGo/vs-picgo):PicGo 的 VS Code 版。
|
||||
- [flutter-picgo](https://github.com/PicGo/flutter-picgo):PicGo 的手机版(支持 Android 和 iOS )。
|
||||
- [flutter-picgo](https://github.com/PicGo/flutter-picgo):PicGo 的手机版 App(支持 Android 和 iOS )。
|
||||
- [PicHoro](https://github.com/Kuingsmile/PicHoro):另一款支持 PicGo 配置的手机版 App(暂时只支持 Android)。
|
||||
|
||||
## 赞助
|
||||
|
||||
@@ -143,4 +131,4 @@ npm run electron:build
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
Copyright (c) 2017 - 2019 Molunerfinn
|
||||
Copyright (c) 2017 - Now Molunerfinn
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: '',
|
||||
name: 'HomePage',
|
||||
data () {
|
||||
return {
|
||||
version: '',
|
||||
|
||||
@@ -1,26 +1,93 @@
|
||||
{
|
||||
"name": "picgo",
|
||||
"version": "2.3.1-beta.5",
|
||||
"version": "2.4.0-beta.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service electron:serve",
|
||||
"build": "vue-cli-service electron:build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
|
||||
"bump": "bump-version",
|
||||
"cz": "git-cz",
|
||||
"dev": "vue-cli-service electron:serve",
|
||||
"electron:build": "vue-cli-service electron:build",
|
||||
"electron:serve": "vue-cli-service electron:serve",
|
||||
"gen-i18n": "node ./scripts/gen-i18n-types.js",
|
||||
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps",
|
||||
"cz": "git-cz",
|
||||
"bump": "bump-version",
|
||||
"release": "vue-cli-service electron:build --publish always",
|
||||
"upload-dist": "node ./scripts/upload-dist-to-cos.js"
|
||||
"upload-dist": "node ./scripts/upload-dist.js"
|
||||
},
|
||||
"main": "background.js",
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@picgo/i18n": "^1.0.0",
|
||||
"@picgo/store": "^2.0.4",
|
||||
"axios": "^0.19.0",
|
||||
"compare-versions": "^4.1.3",
|
||||
"core-js": "^3.27.1",
|
||||
"element-plus": "^2.2.28",
|
||||
"epipebomb": "^1.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"keycode": "^2.2.0",
|
||||
"lodash-id": "^0.14.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"mitt": "^3.0.0",
|
||||
"picgo": "^1.5.1",
|
||||
"qrcode.vue": "^3.3.3",
|
||||
"shell-path": "2.1.0",
|
||||
"uuidv4": "^6.2.11",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-lazyload": "^0.3.6",
|
||||
"vue3-photo-preview": "^0.2.9",
|
||||
"write-file-atomic": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.276.0",
|
||||
"@aws-sdk/lib-storage": "^3.276.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
|
||||
"@picgo/bump-version": "^1.1.2",
|
||||
"@types/electron-devtools-installer": "^2.2.0",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/lowdb": "^1.0.9",
|
||||
"@types/node": "^16.10.2",
|
||||
"@types/request-promise-native": "^1.0.17",
|
||||
"@types/semver": "^7.3.8",
|
||||
"@types/tunnel": "^0.0.3",
|
||||
"@types/write-file-atomic": "^4.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||
"@vue/cli-plugin-router": "^5.0.8",
|
||||
"@vue/cli-plugin-typescript": "^5.0.8",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"@vue/eslint-config-standard": "^8.0.1",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"@vue/runtime-dom": "^3.2.45",
|
||||
"conventional-changelog": "^3.1.18",
|
||||
"cz-customizable": "^6.2.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"electron": "^16.0.6",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-standard": ">=16.0.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"husky": "^3.1.0",
|
||||
"stylus": "^0.54.7",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"typescript": "^4.4.3",
|
||||
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"./node_modules/@picgo/bump-version/commitlint-picgo"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
@@ -30,71 +97,10 @@
|
||||
"config": "./node_modules/@picgo/bump-version/.cz-config.js"
|
||||
}
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"./node_modules/@picgo/bump-version/commitlint-picgo"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@picgo/i18n": "^1.0.0",
|
||||
"@picgo/store": "2.0.1",
|
||||
"axios": "^0.19.0",
|
||||
"compare-versions": "^4.1.3",
|
||||
"core-js": "^3.3.2",
|
||||
"element-ui": "^2.13.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"keycode": "^2.2.0",
|
||||
"lodash-id": "^0.14.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"picgo": "^1.5.0-alpha.5",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"shell-path": "2.1.0",
|
||||
"uuidv4": "^6.2.11",
|
||||
"vue": "^2.6.10",
|
||||
"vue-gallery": "^2.0.1",
|
||||
"vue-lazyload": "^1.2.6",
|
||||
"vue-router": "^3.1.3",
|
||||
"write-file-atomic": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
|
||||
"@picgo/bump-version": "^1.1.2",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/lowdb": "^1.0.9",
|
||||
"@types/node": "^16.10.2",
|
||||
"@types/request-promise-native": "^1.0.17",
|
||||
"@types/semver": "^7.3.8",
|
||||
"@types/write-file-atomic": "^4.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vue/cli-plugin-babel": "^4.0.0",
|
||||
"@vue/cli-plugin-eslint": "^4.0.0",
|
||||
"@vue/cli-plugin-router": "^4.0.0",
|
||||
"@vue/cli-plugin-typescript": "^4.5.13",
|
||||
"@vue/cli-service": "^4.0.0",
|
||||
"@vue/eslint-config-standard": "^6.1.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"conventional-changelog": "^3.1.18",
|
||||
"cz-customizable": "^6.2.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"electron": "^16.0.6",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": ">=16.0.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"husky": "^3.1.0",
|
||||
"stylus": "^0.54.7",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"typescript": "^4.4.3",
|
||||
"vue-cli-plugin-electron-builder": "^2.1.1",
|
||||
"vue-property-decorator": "^8.3.0",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "^16.10.2",
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
LANG_DISPLAY_LABEL: 'English'
|
||||
ABOUT: About
|
||||
OPEN_MAIN_WINDOW: Open Main Window
|
||||
CHOOSE_DEFAULT_PICBED: Choose Default Picbed
|
||||
OPEN_UPDATE_HELPER: Open Update Helper
|
||||
PRIVACY_AGREEMENT: Privacy Agreement
|
||||
RELOAD_APP: Reload App
|
||||
UPLOAD_FAILED: Upload Failed
|
||||
UPLOAD_SUCCEED: Upload Succeed
|
||||
UPLOAD_PROGRESS: Upload Progress
|
||||
OPERATION_FAILED: Operation Failed
|
||||
OPERATION_SUCCEED: Operation Succeed
|
||||
UPLOADING: Uploading
|
||||
QUICK_UPLOAD: Quick Upload
|
||||
UPLOAD_BY_CLIPBOARD: Upload by Clipboard
|
||||
HIDE_WINDOW: Hide Window
|
||||
SPONSOR_PICGO: Sponsor PicGo
|
||||
SHOW_PICBED_QRCODE: Show Picbed Qrcode
|
||||
PICBED_QRCODE: Picbed Qrcode
|
||||
ENABLE: Enable
|
||||
DISABLE: Disable
|
||||
CONFIG_THING: Config ${c}
|
||||
FIND_NEW_VERSION: Find New Version
|
||||
NO_MORE_NOTICE: No More Notice
|
||||
SHOW_DEVTOOLS: Show Devtools
|
||||
CURRENT_PICBED: Current Picbed
|
||||
OPEN_TOOLBOX: Open Toolbox
|
||||
|
||||
# ---renderer i18n begin---
|
||||
|
||||
CHOOSE_YOUR_DEFAULT_PICBED: 'Choose ${d} as your default picbed:'
|
||||
UPLOAD_AREA: Upload Area
|
||||
GALLERY: Gallery
|
||||
PICBEDS_SETTINGS: Picbeds Settings
|
||||
PICGO_SETTINGS: PicGo Settings
|
||||
PLUGIN_SETTINGS: Plugins Settings
|
||||
PICGO_SPONSOR_TEXT: PicGo is a free software, if you like it, please don't forget to buy me a cup of coffee.
|
||||
ALIPAY: Alipay
|
||||
WECHATPAY: Wechat Pay
|
||||
CHOOSE_PICBED: Choose Picbed
|
||||
COPY_PICBED_CONFIG: Copy Picbed Config
|
||||
COPY_PICBED_CONFIG_SUCCEED: Copy Picbed Config Succeed
|
||||
INPUT: Input
|
||||
CANCEL: Cancel
|
||||
CONFIRM: Confirm
|
||||
CHOOSE_SHOWED_PICBED: Choose Showed Picbed
|
||||
CHOOSE_PASTE_FORMAT: Choose Paste Format
|
||||
SEARCH: Search
|
||||
COPY: Copy
|
||||
DELETE: Delete
|
||||
SELECT_ALL: Select All
|
||||
CHANGE_IMAGE_URL: Change Image URL
|
||||
CHANGE_IMAGE_URL_SUCCEED: Change Image URL Succeed
|
||||
COPY_LINK_SUCCEED: Copy Link Succeed
|
||||
BATCH_COPY_LINK_SUCCEED: Batch Copy Link Succeed
|
||||
FILE_RENAME: File Rename
|
||||
COPY_FILE_PATH: Copy file path
|
||||
OPEN_FILE_PATH: Open file path
|
||||
|
||||
# settings
|
||||
|
||||
SETTINGS: Settings
|
||||
SETTINGS_OPEN_CONFIG_FILE: Open Config File
|
||||
SETTINGS_CLICK_TO_OPEN: Click to Open
|
||||
SETTINGS_SET_LOG_FILE: Set Log File
|
||||
SETTINGS_CLICK_TO_SET: Click to Set
|
||||
SETTINGS_CLICK_TO_CHECK: Click to Check
|
||||
SETTINGS_SET_SHORTCUT: Set Shortcut
|
||||
SETTINGS_CUSTOM_LINK_FORMAT: Custom Link Format
|
||||
SETTINGS_SET_PROXY_AND_MIRROR: Set Proxy and Mirror
|
||||
SETTINGS_SET_SERVER: Set Server
|
||||
SETTINGS_CHECK_UPDATE: Check Update
|
||||
SETTINGS_OPEN_UPDATE_HELPER: Open Update Helper
|
||||
SETTINGS_OPEN: Open
|
||||
SETTINGS_CLOSE: Close
|
||||
SETTINGS_ACCEPT_BETA_UPDATE: Accept Beta Update
|
||||
SETTINGS_LAUNCH_ON_BOOT: Launch On Boot
|
||||
SETTINGS_RENAME_BEFORE_UPLOAD: Rename Before Upload
|
||||
SETTINGS_TIMESTAMP_RENAME: Timestamp Rename
|
||||
SETTINGS_OPEN_UPLOAD_TIPS: Open Upload Tips
|
||||
SETTINGS_MINI_WINDOW_ON_TOP: Mini Window On Top
|
||||
SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: Auto Copy URL After Upload
|
||||
SETTINGS_TIPS_PLACEHOLDER_URL: Use $url to represent url position
|
||||
SETTINGS_TIPS_PLACEHOLDER_FILENAME: Use $fileName to represent file name position
|
||||
SETTINGS_TIPS_PLACEHOLDER_EXTNAME: Use $extName to represent file's ext position
|
||||
SETTINGS_TIPS_SUCH_AS: 'Such as: $url/$fileName'
|
||||
SETTINGS_UPLOAD_PROXY: Upload Proxy
|
||||
SETTINGS_PLUGIN_INSTALL_PROXY: Proxy for Plugin Install
|
||||
SETTINGS_PLUGIN_INSTALL_MIRROR: Mirror for Plugin Install
|
||||
SETTINGS_CURRENT_VERSION: Current Version
|
||||
SETTINGS_NEWEST_VERSION: Newest Version
|
||||
SETTINGS_GETING: Getting...
|
||||
SETTINGS_TIPS_HAS_NEW_VERSION: PicGo has a new version, please click confirm to open download page
|
||||
SETTINGS_LOG_FILE: Log File
|
||||
SETTINGS_LOG_LEVEL: Log Level
|
||||
SETTINGS_LOG_FILE_SIZE: Log File Size
|
||||
SETTINGS_SET_PICGO_SERVER: Set PicGo Server
|
||||
SETTINGS_TIPS_SERVER_NOTICE: If you don't know what is the server's function, please read the document, or don't modify the configuration.
|
||||
SETTINGS_ENABLE_SERVER: Enable Server
|
||||
SETTINGS_SET_SERVER_HOST: Set Server Host
|
||||
SETTINGS_SET_SERVER_PORT: Set Server Port
|
||||
SETTINGS_TIP_PLACEHOLDER_HOST: Default:127.0.0.1
|
||||
SETTINGS_TIP_PLACEHOLDER_PORT: Default:36677
|
||||
SETTINGS_LOG_LEVEL_ALL: All
|
||||
SETTINGS_LOG_LEVEL_SUCCESS: Success
|
||||
SETTINGS_LOG_LEVEL_ERROR: Error
|
||||
SETTINGS_LOG_LEVEL_INFO: Info
|
||||
SETTINGS_LOG_LEVEL_WARN: Warn
|
||||
SETTINGS_LOG_LEVEL_NONE: None
|
||||
SETTINGS_RESULT: Result
|
||||
SETTINGS_DEFAULT_PICBED: Default Picbed
|
||||
SETTINGS_SET_DEFAULT_PICBED: Set Default Picbed
|
||||
SETTINGS_NOT_CONFIG_OPTIONS: Not Config Options
|
||||
SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD: Use Builtin Clipboard to Upload
|
||||
SETTINGS_CHOOSE_LANGUAGE: Choose Language
|
||||
UPLOADER_CONFIG_NAME: Configuration Name
|
||||
BUILTIN_CLIPBOARD_TIPS: Use builtin clipboard function to upload instead of using scripts
|
||||
UPLOADER_CONFIG_PLACEHOLDER: Please Enter Configuration Name
|
||||
SELECTED_SETTING_HINT: Selected
|
||||
SETTINGS_ENCODE_OUTPUT_URL: Encode Output(or Copyed) URL
|
||||
SETTINGS_SHOW_DOCK_ICON: Show Dock icon
|
||||
|
||||
# shortcut-page
|
||||
|
||||
SHORTCUT_NAME: Shortcut Name
|
||||
SHORTCUT_BIND: Shortcut Binding
|
||||
SHORTCUT_STATUS: Status
|
||||
SHORTCUT_ENABLED: Enabled
|
||||
SHORTCUT_DISABLED: Disabled
|
||||
SHORTCUT_SOURCE: Source
|
||||
SHORTCUT_HANDLE: Handle
|
||||
SHORTCUT_ENABLE: Enable
|
||||
SHORTCUT_DISABLE: Disable
|
||||
SHORTCUT_EDIT: Edit
|
||||
SHORTCUT_CHANGE_UPLOAD: Change Upload Shortcut
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: Wait to Upload
|
||||
ALREADY_UPLOAD: Already Upload
|
||||
|
||||
# upload-page
|
||||
|
||||
PICTURE_UPLOAD: Picture Upload
|
||||
DRAG_FILE_TO_HERE: Drag file to here, or
|
||||
CLICK_TO_UPLOAD: click to upload
|
||||
LINK_FORMAT: Link Format
|
||||
CUSTOM: Custom
|
||||
CLIPBOARD_PICTURE: Clipboard
|
||||
TIPS_DRAG_VALID_PICTURE_OR_URL: Drag valid picture or url to here
|
||||
TIPS_INPUT_URL: Input URL
|
||||
TIPS_HTTP_PREFIX: http:// or https://
|
||||
TIPS_INPUT_VALID_URL: Input valid URL
|
||||
|
||||
# plugins
|
||||
|
||||
PLUGIN_SEARCH_PLACEHOLDER: Search picgo plugins on npm, or click the button to view the awesome plugins list
|
||||
PLUGIN_INSTALL: Install
|
||||
PLUGIN_INSTALLING: Installing...
|
||||
PLUGIN_INSTALLED: Installed
|
||||
PLUGIN_DOING_SOMETHING: Doing...
|
||||
PLUGIN_LIST: Plugin List
|
||||
PLUGIN_IMPORT_LOCAL: Import Local Plugins
|
||||
|
||||
# tips
|
||||
|
||||
TIPS_REMOVE_LINK: This operation will remove the picture from the album, continue?
|
||||
TIPS_WILL_REMOVE_CHOOSED_IMAGES: This operation will remove the picture from the album, continue?
|
||||
TIPS_MUST_CONTAINS_URL: Must contains $url or $fileName or $extName
|
||||
TIPS_NETWORK_ERROR: Network Error
|
||||
TIPS_NEED_RELOAD: Need Reload App
|
||||
TIPS_PLEASE_CHOOSE_LOG_LEVEL: Please choose log level
|
||||
TIPS_SET_SUCCEED: Set successfully
|
||||
TIPS_PLUGIN_NOT_GUI_IMPLEMENT: This plugin is not optimized for the GUI, continue?
|
||||
TIPS_CLICK_NOTIFICATION_TO_RELOAD: Click notification to reload app
|
||||
TIPS_GET_PLUGIN_LIST_FAILED: Get plugin list failed
|
||||
|
||||
# ---renderer i18n end---
|
||||
|
||||
# plugins
|
||||
PLUGIN_INSTALL_SUCCEED: Plugin install succeed
|
||||
PLUGIN_INSTALL_FAILED: Plugin install failed
|
||||
PLUGIN_UNINSTALL_SUCCEED: Plugin uninstall succeed
|
||||
PLUGIN_UNINSTALL_FAILED: Plugin uninstall failed
|
||||
PLUGIN_UPDATE_SUCCEED: Plugin update succeed
|
||||
PLUGIN_UPDATE_FAILED: Plugin update failed
|
||||
PLUGIN_IMPORT_SUCCEED: Plugin import succeed
|
||||
PLUGIN_IMPORT_FAILED: Plugin import failed
|
||||
ENABLE_PLUGIN: Enable Plugin
|
||||
DISABLE_PLUGIN: Disable Plugin
|
||||
UNINSTALL_PLUGIN: Uninstall Plugin
|
||||
UPDATE_PLUGIN: Update Plugin
|
||||
|
||||
# toolbox
|
||||
TOOLBOX: Toolbox
|
||||
TOOLBOX_TITLE: Troubleshoot PicGo runtime issues
|
||||
TOOLBOX_SUB_TITLE: Scan the following items immediately to fix usage issues
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN: Check if the configuration file is damaged
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN: Check if the album file is damaged
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: Check if there is a problem with clipboard picture upload
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_PROXY: Check if the proxy settings are normal
|
||||
TOOLBOX_FIX_DONE_NEED_RELOAD: Repair completed, need to restart to take effect, restart or not
|
||||
TOOLBOX_CANT_AUTO_FIX: Unable to automatically repair, please repair the following problems yourself
|
||||
TOOLBOX_START_SCAN: Start scanning
|
||||
TOOLBOX_RE_SCAN: Re scanning
|
||||
TOOLBOX_START_FIX: Start fixing
|
||||
TOOLBOX_SUCCESS_TIPS: Congratulations, no problems were found
|
||||
TOOLBOX_CHECK_CONFIG_FILE_PATH_TIPS: "The configuration file path is: ${path}"
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS: The configuration file is damaged
|
||||
TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS: "The album file path is: ${path}"
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS: The album file is damaged
|
||||
TOOLBOX_CHECK_PROXY_SUCCESS_TIPS: Proxy settings normal
|
||||
TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS: No proxy settings
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT: Proxy settings incorrect
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING: Proxy settings unavailable
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS: "The temporary folder path for clipboard pictures is: ${path}"
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS: "The temporary folder for clipboard pictures does not exist: ${path}"
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: "Please create the folder yourself: ${path}"
|
||||
|
||||
# tips
|
||||
TIPS_NOTICE: Tips
|
||||
TIPS_WARNING: Warning
|
||||
TIPS_ERROR: Error
|
||||
TIPS_INSTALL_NODE_AND_RELOAD_PICGO: Please install Node.js and restart PicGo to continue
|
||||
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: Plugin is trying to remove some images from the album gallery, continue?
|
||||
TIPS_PLUGIN_OVERWRITE_GALLERY: Plugin is trying to overwrite the album gallery, continue?
|
||||
TIPS_UPLOAD_NOT_PICTURES: The latest clipboard item is not a picture
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo config file broken, has been restored to default
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo config file broken, has been restored to backup
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: 'Backup file version: ${v}'
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: Custom config file parse error, please check the path content
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: Shortcut modified successfully
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: Shortcut conflict, please reset
|
||||
TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED: Custom link style modified successfully
|
||||
TIPS_FIND_NEW_VERSION: Find new version ${v},update many new features, do you want to download the latest version?
|
||||
|
||||
# privacy
|
||||
PRIVACY: >
|
||||
|
||||
This software respects and protects the personal privacy of all users who use the service. In order to provide you with more accurate and better services, this software will use and collect some of your behavioral information in accordance with the provisions of this Privacy Policy. When you agree to the software service use agreement, you are deemed to have agreed to the entire content of this privacy policy. This privacy policy is an integral part of the software service use agreement, and it will not be used if you do not agree.
|
||||
This Agreement will be updated periodically.
|
||||
|
||||
1. Scope of application
|
||||
|
||||
|
||||
a) When you use this software, this software will record some information about your operation behavior of this software, including but not limited to the time-consuming, type, quantity and other information of your use of this software to upload files.
|
||||
|
||||
|
||||
2. Use of Information
|
||||
|
||||
|
||||
a) After obtaining your usage data, the software will upload it to the data analysis server so as to provide you with better services after analyzing the data.
|
||||
|
||||
|
||||
3. Information disclosure
|
||||
|
||||
|
||||
a) This software will not disclose your information to untrusted third parties.
|
||||
|
||||
|
||||
b) In accordance with the relevant provisions of the law, or the requirements of administrative or judicial institutions, disclose to third parties or administrative or judicial institutions;
|
||||
|
||||
|
||||
c) If you violate relevant Chinese laws, regulations or relevant rules, you need to disclose it to a third party;
|
||||
|
||||
|
||||
4. Information Security
|
||||
|
||||
|
||||
a) This software does not collect your personal information, key information and other private information, and the collected information is only used for improving the software, optimizing the experience, and understanding the daily activities of the software.
|
||||
PRIVACY_TIPS: Please agree the privacy policy to upload
|
||||
QUIT: Quit
|
||||
@@ -0,0 +1,272 @@
|
||||
LANG_DISPLAY_LABEL: 中文
|
||||
ABOUT: 关于
|
||||
OPEN_MAIN_WINDOW: 打开主窗口
|
||||
CHOOSE_DEFAULT_PICBED: 选择默认图床
|
||||
OPEN_UPDATE_HELPER: 打开更新助手
|
||||
PRIVACY_AGREEMENT: 隐私协议
|
||||
RELOAD_APP: 重启应用
|
||||
UPLOAD_SUCCEED: 上传成功
|
||||
UPLOAD_FAILED: 上传失败
|
||||
UPLOAD_PROGRESS: 上传进度
|
||||
OPERATION_SUCCEED: 操作成功
|
||||
OPERATION_FAILED: 操作失败
|
||||
UPLOADING: 正在上传
|
||||
QUICK_UPLOAD: 快捷上传
|
||||
UPLOAD_BY_CLIPBOARD: 剪贴板图片上传
|
||||
HIDE_WINDOW: 隐藏窗口
|
||||
SPONSOR_PICGO: 赞助 PicGo
|
||||
SHOW_PICBED_QRCODE: 生成图床配置二维码
|
||||
PICBED_QRCODE: 图床配置二维码
|
||||
ENABLE: 启用
|
||||
DISABLE: 禁用
|
||||
CONFIG_THING: 配置${c}
|
||||
FIND_NEW_VERSION: 发现新版本
|
||||
NO_MORE_NOTICE: 以后不再提醒
|
||||
SHOW_DEVTOOLS: 打开开发者工具
|
||||
CURRENT_PICBED: 当前图床
|
||||
OPEN_TOOLBOX: 打开修复工具箱
|
||||
|
||||
# ---renderer i18n begin---
|
||||
|
||||
CHOOSE_YOUR_DEFAULT_PICBED: 选择 ${d} 作为你默认图床:
|
||||
UPLOAD_AREA: 上传区
|
||||
GALLERY: 相册
|
||||
PICBEDS_SETTINGS: 图床设置
|
||||
PICGO_SETTINGS: PicGo设置
|
||||
PLUGIN_SETTINGS: 插件设置
|
||||
PICGO_SPONSOR_TEXT: PicGo是免费开源的软件,如果你喜欢它,对你有帮助,不妨请我喝杯咖啡?
|
||||
ALIPAY: 支付宝
|
||||
WECHATPAY: 微信支付
|
||||
CHOOSE_PICBED: 选择图床
|
||||
COPY_PICBED_CONFIG: 复制图床配置
|
||||
COPY_PICBED_CONFIG_SUCCEED: 复制图床配置成功
|
||||
INPUT: 输入框
|
||||
CANCEL: 取消
|
||||
CONFIRM: 确定
|
||||
CHOOSE_SHOWED_PICBED: 请选择显示的图床
|
||||
CHOOSE_PASTE_FORMAT: 请选择粘贴的格式
|
||||
SEARCH: 搜索
|
||||
COPY: 复制
|
||||
DELETE: 删除
|
||||
SELECT_ALL: 全选
|
||||
CHANGE_IMAGE_URL: 修改图片URL
|
||||
CHANGE_IMAGE_URL_SUCCEED: 修改图片URL成功
|
||||
COPY_LINK_SUCCEED: 复制链接成功
|
||||
BATCH_COPY_LINK_SUCCEED: 批量复制链接成功
|
||||
FILE_RENAME: 文件改名
|
||||
COPY_FILE_PATH: 复制文件路径
|
||||
OPEN_FILE_PATH: 打开文件路径
|
||||
|
||||
# settings
|
||||
|
||||
SETTINGS: 设置
|
||||
SETTINGS_OPEN_CONFIG_FILE: 打开配置文件
|
||||
SETTINGS_CLICK_TO_OPEN: 点击打开
|
||||
SETTINGS_SET_LOG_FILE: 设置日志文件
|
||||
SETTINGS_CLICK_TO_SET: 点击设置
|
||||
SETTINGS_CLICK_TO_CHECK: 点击检查
|
||||
SETTINGS_SET_SHORTCUT: 设置快捷键
|
||||
SETTINGS_CUSTOM_LINK_FORMAT: 自定义链接格式
|
||||
SETTINGS_SET_PROXY_AND_MIRROR: 设置代理和镜像地址
|
||||
SETTINGS_SET_SERVER: 设置Server
|
||||
SETTINGS_CHECK_UPDATE: 检查更新
|
||||
SETTINGS_OPEN_UPDATE_HELPER: 打开更新助手
|
||||
SETTINGS_OPEN: 开
|
||||
SETTINGS_CLOSE: 关
|
||||
SETTINGS_ACCEPT_BETA_UPDATE: 接受Beta版本更新
|
||||
SETTINGS_LAUNCH_ON_BOOT: 开机自启
|
||||
SETTINGS_RENAME_BEFORE_UPLOAD: 上传前重命名
|
||||
SETTINGS_TIMESTAMP_RENAME: 时间戳重命名
|
||||
SETTINGS_OPEN_UPLOAD_TIPS: 开启上传提示
|
||||
SETTINGS_MINI_WINDOW_ON_TOP: Mini窗口置顶
|
||||
SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: 上传后自动复制URL
|
||||
SETTINGS_TIPS_PLACEHOLDER_URL: 用占位符 $url 来表示url的位置
|
||||
SETTINGS_TIPS_PLACEHOLDER_FILENAME: 用占位符 $fileName 来表示文件名的位置
|
||||
SETTINGS_TIPS_PLACEHOLDER_EXTNAME: 用占位符 $extName 来表示文件格式的位置
|
||||
SETTINGS_TIPS_SUCH_AS: 如
|
||||
SETTINGS_UPLOAD_PROXY: 上传代理
|
||||
SETTINGS_PLUGIN_INSTALL_PROXY: 插件安装代理
|
||||
SETTINGS_PLUGIN_INSTALL_MIRROR: 插件安装镜像
|
||||
SETTINGS_CURRENT_VERSION: 当前版本
|
||||
SETTINGS_NEWEST_VERSION: 最新版本
|
||||
SETTINGS_GETING: 正在获取中
|
||||
SETTINGS_TIPS_HAS_NEW_VERSION: PicGo更新啦,请点击确定打开下载页面
|
||||
SETTINGS_LOG_FILE: 日志文件
|
||||
SETTINGS_LOG_LEVEL: 日志记录等级
|
||||
SETTINGS_LOG_FILE_SIZE: 日志文件大小
|
||||
SETTINGS_SET_PICGO_SERVER: 设置PicGo-Server
|
||||
SETTINGS_TIPS_SERVER_NOTICE: 如果你不知道Server的作用,请阅读文档,或者不用修改配置。
|
||||
SETTINGS_ENABLE_SERVER: 是否开启Server
|
||||
SETTINGS_SET_SERVER_HOST: 设置监听地址
|
||||
SETTINGS_SET_SERVER_PORT: 设置监听端口
|
||||
SETTINGS_TIP_PLACEHOLDER_HOST: 推荐默认地址:127.0.0.1
|
||||
SETTINGS_TIP_PLACEHOLDER_PORT: 推荐默认端口:36677
|
||||
SETTINGS_LOG_LEVEL_ALL: 全部-All
|
||||
SETTINGS_LOG_LEVEL_SUCCESS: 成功-Success
|
||||
SETTINGS_LOG_LEVEL_ERROR: 错误-Error
|
||||
SETTINGS_LOG_LEVEL_INFO: 普通-Info
|
||||
SETTINGS_LOG_LEVEL_WARN: 提醒-Warn
|
||||
SETTINGS_LOG_LEVEL_NONE: 不记录日志-None
|
||||
SETTINGS_RESULT: 设置结果
|
||||
SETTINGS_DEFAULT_PICBED: 设置默认图床
|
||||
SETTINGS_SET_DEFAULT_PICBED: 设为默认图床
|
||||
SETTINGS_NOT_CONFIG_OPTIONS: 暂无配置项
|
||||
SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD: 使用内置剪贴板上传
|
||||
SETTINGS_CHOOSE_LANGUAGE: 选择语言
|
||||
BUILTIN_CLIPBOARD_TIPS: 使用内置剪贴板函数而不是调用脚本获取剪贴板图片
|
||||
UPLOADER_CONFIG_NAME: 图床配置名
|
||||
UPLOADER_CONFIG_PLACEHOLDER: 请输入配置名称
|
||||
SELECTED_SETTING_HINT: 已选中
|
||||
SETTINGS_ENCODE_OUTPUT_URL: 输出(复制) URL 时进行转义
|
||||
SETTINGS_SHOW_DOCK_ICON: 显示 Dock 栏图标
|
||||
|
||||
# shortcut-page
|
||||
|
||||
SHORTCUT_NAME: 快捷键名称
|
||||
SHORTCUT_BIND: 快捷键绑定
|
||||
SHORTCUT_STATUS: 状态
|
||||
SHORTCUT_ENABLED: 已启用
|
||||
SHORTCUT_DISABLED: 已禁用
|
||||
SHORTCUT_SOURCE: 来源
|
||||
SHORTCUT_HANDLE: 操作
|
||||
SHORTCUT_ENABLE: 启用
|
||||
SHORTCUT_DISABLE: 禁用
|
||||
SHORTCUT_EDIT: 编辑
|
||||
SHORTCUT_CHANGE_UPLOAD: 修改上传快捷键
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: 等待上传
|
||||
ALREADY_UPLOAD: 已上传
|
||||
|
||||
# upload-page
|
||||
|
||||
PICTURE_UPLOAD: 图片上传
|
||||
DRAG_FILE_TO_HERE: 将文件拖拽到此处,或
|
||||
CLICK_TO_UPLOAD: 点击上传
|
||||
LINK_FORMAT: 链接格式
|
||||
CUSTOM: 自定义
|
||||
CLIPBOARD_PICTURE: 剪贴板图片
|
||||
TIPS_DRAG_VALID_PICTURE_OR_URL: 请拖入合法的图片文件或者图片URL地址
|
||||
TIPS_INPUT_URL: 请输入URL
|
||||
TIPS_HTTP_PREFIX: http://或者https://开头
|
||||
TIPS_INPUT_VALID_URL: 请输入合法的URL
|
||||
|
||||
# plugins
|
||||
|
||||
PLUGIN_SEARCH_PLACEHOLDER: 搜索npm上的PicGo插件,或者点击上方按钮查看优秀插件列表
|
||||
PLUGIN_INSTALL: 安装
|
||||
PLUGIN_INSTALLING: 安装中
|
||||
PLUGIN_INSTALLED: 已安装
|
||||
PLUGIN_DOING_SOMETHING: 进行中
|
||||
PLUGIN_LIST: 插件列表
|
||||
PLUGIN_IMPORT_LOCAL: 导入本地插件
|
||||
|
||||
# tips
|
||||
|
||||
TIPS_REMOVE_LINK: 此操作将把该图片移出相册, 是否继续?
|
||||
TIPS_WILL_REMOVE_CHOOSED_IMAGES: 将在相册中移除刚才选中的 ${m} 张图片,是否继续?
|
||||
TIPS_MUST_CONTAINS_URL: 必须含有$url 或 $fileName 或 $extName
|
||||
TIPS_NETWORK_ERROR: 网络错误暂时无法获取
|
||||
TIPS_NEED_RELOAD: 需要重启生效
|
||||
TIPS_PLEASE_CHOOSE_LOG_LEVEL: 请选择日志记录等级
|
||||
TIPS_SET_SUCCEED: 设置成功
|
||||
TIPS_PLUGIN_NOT_GUI_IMPLEMENT: 该插件未对可视化界面进行优化, 是否继续安装?
|
||||
TIPS_CLICK_NOTIFICATION_TO_RELOAD: 请点击此通知重启应用以生效
|
||||
TIPS_GET_PLUGIN_LIST_FAILED: 获取插件列表失败
|
||||
|
||||
# ---renderer i18n end---
|
||||
|
||||
# plugins
|
||||
PLUGIN_INSTALL_SUCCEED: 插件安装成功
|
||||
PLUGIN_INSTALL_FAILED: 插件安装失败
|
||||
PLUGIN_UNINSTALL_SUCCEED: 插件卸载成功
|
||||
PLUGIN_UNINSTALL_FAILED: 插件卸载失败
|
||||
PLUGIN_UPDATE_SUCCEED: 插件更新成功
|
||||
PLUGIN_UPDATE_FAILED: 插件更新失败
|
||||
PLUGIN_IMPORT_SUCCEED: 插件导入成功
|
||||
PLUGIN_IMPORT_FAILED: 插件导入失败
|
||||
ENABLE_PLUGIN: 启用插件
|
||||
DISABLE_PLUGIN: 禁用插件
|
||||
UNINSTALL_PLUGIN: 卸载插件
|
||||
UPDATE_PLUGIN: 更新插件
|
||||
|
||||
# toolbox
|
||||
TOOLBOX: 工具箱
|
||||
TOOLBOX_TITLE: 排查 PicGo 运行时问题
|
||||
TOOLBOX_SUB_TITLE: 立即扫描以下项目,修复使用问题
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN: 检查配置文件是否损坏
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN: 检查相册文件是否损坏
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: 检查剪贴板图片上传是否存在问题
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_PROXY: 检查代理设置是否正常
|
||||
TOOLBOX_FIX_DONE_NEED_RELOAD: 修复完成,需要重启生效,是否重启
|
||||
TOOLBOX_CANT_AUTO_FIX: 无法自动修复,请自行修复以下问题
|
||||
TOOLBOX_START_SCAN: 开始扫描
|
||||
TOOLBOX_RE_SCAN: 重新扫描
|
||||
TOOLBOX_START_FIX: 开始修复
|
||||
TOOLBOX_SUCCESS_TIPS: 恭喜你,没有检查出问题
|
||||
TOOLBOX_CHECK_CONFIG_FILE_PATH_TIPS: 配置文件路径是:${path}
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS: 配置文件已损坏
|
||||
TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS: 相册文件路径是:${path}
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS: 相册文件已损坏
|
||||
TOOLBOX_CHECK_PROXY_SUCCESS_TIPS: 代理设置正常
|
||||
TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS: 无代理设置
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT: 代理设置不正确
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING: 代理设置不可用
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS: 剪贴板图片临时文件夹路径是:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS: 剪贴板图片临时文件夹不存在:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 请自行创建文件夹:${path}
|
||||
|
||||
# tips
|
||||
TIPS_NOTICE: 注意
|
||||
TIPS_WARNING: 警告
|
||||
TIPS_ERROR: 发生错误
|
||||
TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 请安装Node.js并重启PicGo再继续操作
|
||||
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: 有插件正在试图删除一些相册图片,是否继续
|
||||
TIPS_PLUGIN_OVERWRITE_GALLERY: 有插件正在试图覆盖相册列表,是否继续
|
||||
TIPS_UPLOAD_NOT_PICTURES: 剪贴板最新的一条记录不是图片
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo 配置文件损坏,已经恢复为默认配置
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo 配置文件损坏,已经恢复为备份配置
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: '备份文件版本: ${v}'
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自定义文件解析出错,请检查路径内容是否正确
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷键已经修改成功
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷键冲突,请重新设置
|
||||
TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED: 自定义链接格式已经修改成功
|
||||
TIPS_FIND_NEW_VERSION: 发现新版本${v},更新了很多功能,是否去下载最新的版本?
|
||||
|
||||
# privacy
|
||||
PRIVACY: >
|
||||
|
||||
本软件尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更优质的服务,本软件会按照本隐私权政策的规定使用和收集您的一些行为信息。您在同意本软件服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本软件服务使用协议不可分割的一部分,如果不同意将无法使用。本协议会定期更新。
|
||||
|
||||
|
||||
1.适用范围
|
||||
|
||||
|
||||
a)在您使用本软件时,本软件会记录的您对本软件的一些操作行为信息,包括但不限于您使用本软件进行文件上传的耗时、类型、数量等信息。
|
||||
|
||||
|
||||
2.信息的使用
|
||||
|
||||
|
||||
a)在获得您的使用数据之后,本软件会将其上传至数据分析服务器,以便分析数据后,提供给您更好的服务。
|
||||
|
||||
|
||||
3.信息披露
|
||||
|
||||
|
||||
a)本软件不会将您的信息披露给不受信任的第三方。
|
||||
|
||||
|
||||
b)根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;
|
||||
|
||||
|
||||
c)如您出现违反中国有关法律、法规或者相关规则的情况,需要向第三方披露;
|
||||
|
||||
|
||||
4.信息安全
|
||||
|
||||
|
||||
a)本软件不会收集您的个人信息、密钥信息等隐私信息,所收集的信息仅仅作为改善软件、优化体验、了解软件日活等用途。
|
||||
PRIVACY_TIPS: 请同意隐私协议,否则无法上传。
|
||||
QUIT: 退出
|
||||
@@ -0,0 +1,272 @@
|
||||
LANG_DISPLAY_LABEL: 繁體中文
|
||||
ABOUT: 關於
|
||||
OPEN_MAIN_WINDOW: 打開主視窗
|
||||
CHOOSE_DEFAULT_PICBED: 選擇預設圖床
|
||||
OPEN_UPDATE_HELPER: 開啟更新助手
|
||||
PRIVACY_AGREEMENT: 隱私協議
|
||||
RELOAD_APP: 重啟程式
|
||||
UPLOAD_SUCCEED: 上傳成功
|
||||
UPLOAD_FAILED: 上傳失敗
|
||||
UPLOAD_PROGRESS: 上傳進度
|
||||
OPERATION_SUCCEED: 操作成功
|
||||
OPERATION_FAILED: 操作失敗
|
||||
UPLOADING: 正在上傳
|
||||
QUICK_UPLOAD: 快速上傳
|
||||
UPLOAD_BY_CLIPBOARD: 剪貼簿圖片上傳
|
||||
HIDE_WINDOW: 隱藏視窗
|
||||
SPONSOR_PICGO: 贊助 PicGo
|
||||
SHOW_PICBED_QRCODE: 產生圖床配置 QRCODE
|
||||
PICBED_QRCODE: 圖床配置 QRCODE
|
||||
ENABLE: 啟用
|
||||
DISABLE: 禁用
|
||||
CONFIG_THING: 設定${c}
|
||||
FIND_NEW_VERSION: 發現新版本
|
||||
NO_MORE_NOTICE: 以後不再提醒
|
||||
SHOW_DEVTOOLS: 開啟開發者工具
|
||||
CURRENT_PICBED: 當前圖床
|
||||
OPEN_TOOLBOX: 開啟修復工具箱
|
||||
|
||||
# ---renderer i18n begin---
|
||||
|
||||
CHOOSE_YOUR_DEFAULT_PICBED: 選擇 ${d} 作為你的預設圖床:
|
||||
UPLOAD_AREA: 上傳區
|
||||
GALLERY: 相簿
|
||||
PICBEDS_SETTINGS: 圖床設定
|
||||
PICGO_SETTINGS: PicGo設定
|
||||
PLUGIN_SETTINGS: 插件設定
|
||||
PICGO_SPONSOR_TEXT: PicGo是開放原始碼的軟體,如果你喜歡它,對你有幫助,不妨請我喝杯咖啡?
|
||||
ALIPAY: 支付寶
|
||||
WECHATPAY: 微信支付
|
||||
CHOOSE_PICBED: 選擇圖床
|
||||
COPY_PICBED_CONFIG: 複製圖床設定
|
||||
COPY_PICBED_CONFIG_SUCCEED: 複製圖床設定成功
|
||||
INPUT: 輸入框
|
||||
CANCEL: 取消
|
||||
CONFIRM: 確定
|
||||
CHOOSE_SHOWED_PICBED: 請選擇顯示的圖床
|
||||
CHOOSE_PASTE_FORMAT: 請選擇貼上的格式
|
||||
SEARCH: 搜尋
|
||||
COPY: 複製
|
||||
DELETE: 刪除
|
||||
SELECT_ALL: 全選
|
||||
CHANGE_IMAGE_URL: 修改圖片URL
|
||||
CHANGE_IMAGE_URL_SUCCEED: 修改圖片URL成功
|
||||
COPY_LINK_SUCCEED: 複製連結成功
|
||||
BATCH_COPY_LINK_SUCCEED: 批量複製連結成功
|
||||
FILE_RENAME: 檔案改名
|
||||
COPY_FILE_PATH: 複製檔案路徑
|
||||
OPEN_FILE_PATH: 打開檔案路徑
|
||||
|
||||
# settings
|
||||
|
||||
SETTINGS: 設定
|
||||
SETTINGS_OPEN_CONFIG_FILE: 打開設定檔案
|
||||
SETTINGS_CLICK_TO_OPEN: 點擊打開
|
||||
SETTINGS_SET_LOG_FILE: 設定記錄檔案
|
||||
SETTINGS_CLICK_TO_SET: 點擊設定
|
||||
SETTINGS_CLICK_TO_CHECK: 點擊檢查
|
||||
SETTINGS_SET_SHORTCUT: 設定快捷鍵
|
||||
SETTINGS_CUSTOM_LINK_FORMAT: 自訂連結格式
|
||||
SETTINGS_SET_PROXY_AND_MIRROR: 設定PROXY和鏡像地址
|
||||
SETTINGS_SET_SERVER: 設定Server
|
||||
SETTINGS_CHECK_UPDATE: 檢查更新
|
||||
SETTINGS_OPEN_UPDATE_HELPER: 打開更新助手
|
||||
SETTINGS_OPEN: 開
|
||||
SETTINGS_CLOSE: 關
|
||||
SETTINGS_ACCEPT_BETA_UPDATE: 接受Beta版本更新
|
||||
SETTINGS_LAUNCH_ON_BOOT: 開機時啟動
|
||||
SETTINGS_RENAME_BEFORE_UPLOAD: 上傳前重新命名
|
||||
SETTINGS_TIMESTAMP_RENAME: 以時間戳命名
|
||||
SETTINGS_OPEN_UPLOAD_TIPS: 開啟上傳提示
|
||||
SETTINGS_MINI_WINDOW_ON_TOP: Mini視窗置頂
|
||||
SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: 上傳後自動複製URL
|
||||
SETTINGS_TIPS_PLACEHOLDER_URL: 用佔位符 $url 來表示URL的位置
|
||||
SETTINGS_TIPS_PLACEHOLDER_FILENAME: 用佔位符 $fileName 來表示檔案名稱的位置
|
||||
SETTINGS_TIPS_PLACEHOLDER_EXTNAME: 用佔位符 $extName 來表示檔案格式的位置
|
||||
SETTINGS_TIPS_SUCH_AS: 如
|
||||
SETTINGS_UPLOAD_PROXY: 上傳PROXY
|
||||
SETTINGS_PLUGIN_INSTALL_PROXY: 插件安裝PROXY
|
||||
SETTINGS_PLUGIN_INSTALL_MIRROR: 插件安裝鏡像
|
||||
SETTINGS_CURRENT_VERSION: 當前版本
|
||||
SETTINGS_NEWEST_VERSION: 最新版本
|
||||
SETTINGS_GETING: 正在取得中
|
||||
SETTINGS_TIPS_HAS_NEW_VERSION: PicGo更新啦,請點擊確定開啟下載頁面
|
||||
SETTINGS_LOG_FILE: 記錄檔案
|
||||
SETTINGS_LOG_LEVEL: 記錄等级
|
||||
SETTINGS_LOG_FILE_SIZE: 記錄檔案大小
|
||||
SETTINGS_SET_PICGO_SERVER: 設定PicGo-Server
|
||||
SETTINGS_TIPS_SERVER_NOTICE: 如果你不知道Server的作用,請閱讀文檔,或者不用修改設定。
|
||||
SETTINGS_ENABLE_SERVER: 是否開啟Server
|
||||
SETTINGS_SET_SERVER_HOST: 設定監聽地址
|
||||
SETTINGS_SET_SERVER_PORT: 設定監聽端口
|
||||
SETTINGS_TIP_PLACEHOLDER_HOST: 推薦預設地址:127.0.0.1
|
||||
SETTINGS_TIP_PLACEHOLDER_PORT: 推薦預設端口:36677
|
||||
SETTINGS_LOG_LEVEL_ALL: 全部-All
|
||||
SETTINGS_LOG_LEVEL_SUCCESS: 成功-Success
|
||||
SETTINGS_LOG_LEVEL_ERROR: 錯誤-Error
|
||||
SETTINGS_LOG_LEVEL_INFO: 普通-Info
|
||||
SETTINGS_LOG_LEVEL_WARN: 提醒-Warn
|
||||
SETTINGS_LOG_LEVEL_NONE: 不記錄-None
|
||||
SETTINGS_RESULT: 設定結果
|
||||
SETTINGS_DEFAULT_PICBED: 設定預設圖床
|
||||
SETTINGS_SET_DEFAULT_PICBED: 設為預設圖床
|
||||
SETTINGS_NOT_CONFIG_OPTIONS: 暫無設定選項
|
||||
SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD: 使用內建剪貼簿上傳
|
||||
SETTINGS_CHOOSE_LANGUAGE: 選擇語言
|
||||
BUILTIN_CLIPBOARD_TIPS: 使用內建剪貼簿函數而不是調用腳本取得剪貼簿內的照片
|
||||
UPLOADER_CONFIG_NAME: 圖床配置名
|
||||
UPLOADER_CONFIG_PLACEHOLDER: 請輸入配置名稱
|
||||
SELECTED_SETTING_HINT: 已選中
|
||||
SETTINGS_ENCODE_OUTPUT_URL: 輸出(複製) URL 時進行轉義
|
||||
SETTINGS_SHOW_DOCK_ICON: 顯示 Dock 欄圖示
|
||||
|
||||
# shortcut-page
|
||||
|
||||
SHORTCUT_NAME: 快捷鍵名稱
|
||||
SHORTCUT_BIND: 快捷鍵綁定
|
||||
SHORTCUT_STATUS: 狀態
|
||||
SHORTCUT_ENABLED: 已啟用
|
||||
SHORTCUT_DISABLED: 已禁用
|
||||
SHORTCUT_SOURCE: 來源
|
||||
SHORTCUT_HANDLE: 操作
|
||||
SHORTCUT_ENABLE: 啟用
|
||||
SHORTCUT_DISABLE: 禁用
|
||||
SHORTCUT_EDIT: 編輯
|
||||
SHORTCUT_CHANGE_UPLOAD: 修改上傳快捷鍵
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: 等待上傳
|
||||
ALREADY_UPLOAD: 已上傳
|
||||
|
||||
# upload-page
|
||||
|
||||
PICTURE_UPLOAD: 圖片上傳
|
||||
DRAG_FILE_TO_HERE: 將檔案拖曳到此處,或
|
||||
CLICK_TO_UPLOAD: 點擊上傳
|
||||
LINK_FORMAT: 連結格式
|
||||
CUSTOM: 自訂
|
||||
CLIPBOARD_PICTURE: 剪貼簿圖片
|
||||
TIPS_DRAG_VALID_PICTURE_OR_URL: 請拖入合法的圖片檔案或者圖片URL地址
|
||||
TIPS_INPUT_URL: 請輸入URL
|
||||
TIPS_HTTP_PREFIX: http://或者https://開頭
|
||||
TIPS_INPUT_VALID_URL: 請輸入合法的URL
|
||||
|
||||
# plugins
|
||||
|
||||
PLUGIN_SEARCH_PLACEHOLDER: 搜尋npm上的PicGo插件,或者點擊上方按鈕查看優秀插件列表
|
||||
PLUGIN_INSTALL: 安裝
|
||||
PLUGIN_INSTALLING: 安裝中
|
||||
PLUGIN_INSTALLED: 已安裝
|
||||
PLUGIN_DOING_SOMETHING: 進行中
|
||||
PLUGIN_LIST: 插件列表
|
||||
PLUGIN_IMPORT_LOCAL: 導入本地插件
|
||||
|
||||
# tips
|
||||
|
||||
TIPS_REMOVE_LINK: 此操作將在相簿中移除該圖片,是否繼續?
|
||||
TIPS_WILL_REMOVE_CHOOSED_IMAGES: 將在相簿中移除剛才選中的 ${m} 張圖片,是否繼續?
|
||||
TIPS_MUST_CONTAINS_URL: 必須含有$url 或 $fileName 或 $extName
|
||||
TIPS_NETWORK_ERROR: 網路錯誤,暫時無法取得
|
||||
TIPS_NEED_RELOAD: 需要重新啟動生效
|
||||
TIPS_PLEASE_CHOOSE_LOG_LEVEL: 請選擇記錄等級
|
||||
TIPS_SET_SUCCEED: 設定成功
|
||||
TIPS_PLUGIN_NOT_GUI_IMPLEMENT: 該插件未對GUI進行優化,是否繼續安裝?
|
||||
TIPS_CLICK_NOTIFICATION_TO_RELOAD: 請點擊此通知重新啟動程式以生效
|
||||
TIPS_GET_PLUGIN_LIST_FAILED: 取得插件列表失敗
|
||||
|
||||
# ---renderer i18n end---
|
||||
|
||||
# plugins
|
||||
PLUGIN_INSTALL_SUCCEED: 插件安裝成功
|
||||
PLUGIN_INSTALL_FAILED: 插件安裝失敗
|
||||
PLUGIN_UNINSTALL_SUCCEED: 插件卸載成功
|
||||
PLUGIN_UNINSTALL_FAILED: 插件卸載失敗
|
||||
PLUGIN_UPDATE_SUCCEED: 插件更新成功
|
||||
PLUGIN_UPDATE_FAILED: 插件更新失敗
|
||||
PLUGIN_IMPORT_SUCCEED: 插件導入成功
|
||||
PLUGIN_IMPORT_FAILED: 插件導入失敗
|
||||
ENABLE_PLUGIN: 啟用插件
|
||||
DISABLE_PLUGIN: 禁用插件
|
||||
UNINSTALL_PLUGIN: 卸載插件
|
||||
UPDATE_PLUGIN: 更新插件
|
||||
|
||||
# toolbox
|
||||
TOOLBOX: 工具箱
|
||||
TOOLBOX_TITLE: 排查 PicGo 執行時問題
|
||||
TOOLBOX_SUB_TITLE: 立即掃描以下項目,修復使用問題
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN: 檢查配置文件是否損壞
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN: 檢查相冊文件是否損壞
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD: 檢查剪貼板圖片上傳是否存在問題
|
||||
TOOLBOX_CHECK_PROBLEM_WITH_PROXY: 檢查代理設置是否正常
|
||||
TOOLBOX_FIX_DONE_NEED_RELOAD: 修復完成,需要重啓生效,是否重啓
|
||||
TOOLBOX_CANT_AUTO_FIX: 無法自動修復,請自行修復以下問題
|
||||
TOOLBOX_START_SCAN: 開始掃描
|
||||
TOOLBOX_RE_SCAN: 重新掃描
|
||||
TOOLBOX_START_FIX: 開始修復
|
||||
TOOLBOX_SUCCESS_TIPS: 恭喜你,沒有檢查出問題
|
||||
TOOLBOX_CHECK_CONFIG_FILE_PATH_TIPS: 配置文件路徑是:${path}
|
||||
TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS: 配置文件已損壞
|
||||
TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS: 相冊文件路徑是:${path}
|
||||
TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS: 相冊文件已損壞
|
||||
TOOLBOX_CHECK_PROXY_SUCCESS_TIPS: 代理設置正常
|
||||
TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS: 無代理設置
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT: 代理設置不正確
|
||||
TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING: 代理設置不可用
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS: 剪貼板圖片臨時文件夾路徑是:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS: 剪貼板圖片臨時文件夾不存在:${path}
|
||||
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 請自行創建文件夾:${path}
|
||||
|
||||
# tips
|
||||
TIPS_NOTICE: 注意
|
||||
TIPS_WARNING: 警告
|
||||
TIPS_ERROR: 發生錯誤
|
||||
TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 請安裝Node.js並重新啟動PicGo再繼續操作
|
||||
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: 有插件正在試圖刪除一些相簿圖片,是否繼續?
|
||||
TIPS_PLUGIN_OVERWRITE_GALLERY: 有插件正在試圖覆蓋相簿列表,是否繼續?
|
||||
TIPS_UPLOAD_NOT_PICTURES: 剪貼簿最新的一條記錄不是圖片
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo 設定檔案已損壞,已經恢復為預設設定
|
||||
TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo 設定檔案已損壞,已經恢復為備份設定
|
||||
TIPS_PICGO_BACKUP_FILE_VERSION: '備份檔案版本: ${v}'
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自訂設定檔案解析出錯,請檢查路徑內容是否正確
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷鍵已經修改成功
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷鍵衝突,請重新設定
|
||||
TIPS_CUSTOM_LINK_STYLE_MODIFIED_SUCCEED: 自訂連結格式已經修改成功
|
||||
TIPS_FIND_NEW_VERSION: 發現新版本${v},更新了很多功能,是否去下載最新的版本?
|
||||
|
||||
# privacy
|
||||
PRIVACY: >
|
||||
|
||||
本軟體尊重並保護所有使用服務用戶的個人隱私權。為了給您提供更準確、更優質的服務,本軟體會按照本隱私權政策的規定使用和收集您的一些行為信息。您在同意本軟體服務使用協議之時,即視為您已經同意本隱私權政策全部內容。本隱私權政策屬於本軟體服務使用協議不可分割的一部分,如果不同意將無法使用。本協議會定期更新。
|
||||
|
||||
|
||||
1.適用範圍
|
||||
|
||||
|
||||
a)在您使用本軟體時,本軟體會記錄的您對本軟體的一些操作行為信息,包括但不限於您使用本軟體進行文件上傳的耗時、類型、數量等信息。
|
||||
|
||||
|
||||
2.信息的使用
|
||||
|
||||
|
||||
a)在獲得您的使用數據之後,本軟體會將其上傳至數據分析服務器,以便分析數據後,提供給您更好的服務。
|
||||
|
||||
|
||||
3.信息披露
|
||||
|
||||
|
||||
a)本軟體不會將您的信息披露給不受信任的第三方。
|
||||
|
||||
|
||||
b)根據法律的有關規定,或者行政或司法機構的要求,向第三方或者行政、司法機構披露;
|
||||
|
||||
|
||||
c)如您出現違反中國有關法律、法規或者相關規則的情況,需要向第三方披露;
|
||||
|
||||
|
||||
4.信息安全
|
||||
|
||||
|
||||
a)本軟體不會收集您的個人信息、密鑰信息等隱私信息,所收集的信息僅僅作為改善軟體、優化體驗、了解軟體日活等用途。
|
||||
PRIVACY_TIPS: 請同意隱私協議,否則無法上傳。
|
||||
QUIT: 退出
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>PicGo</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
||||
|
Before Width: | Height: | Size: 481 B |
|
Before Width: | Height: | Size: 807 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 541 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1672454579182" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2800" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M160 0h512l256 256v704c0 35.3472-28.6528 64-64 64H160c-35.3472 0-64-28.6528-64-64V64c0-35.3472 28.6528-64 64-64z" fill="#CCCCCC" p-id="2801"></path><path d="M672 0l256 256h-192c-35.3472 0-64-28.6528-64-64V0z" fill="#EAEAEA" p-id="2802"></path><path d="M384 499.2c0-25.6 5.12-46.08 10.24-58.88 5.12-12.8 15.36-25.6 28.16-35.84 12.8-12.8 25.6-20.48 43.52-25.6 15.36-5.12 30.72-7.68 48.64-7.68 35.84 0 64 10.24 89.6 30.72C627.2 422.4 640 448 640 481.28c0 15.36-5.12 28.16-10.24 40.96s-17.92 28.16-38.4 46.08-28.16 30.72-35.84 38.4c-7.68 7.68-10.24 17.92-15.36 28.16-5.12 10.24-2.56 17.92-2.56 43.52h-51.2c0-25.6 2.56-38.4 5.12-51.2s7.68-23.04 15.36-33.28 15.36-23.04 33.28-40.96c17.92-17.92 30.72-30.72 35.84-38.4 5.12-7.68 10.24-20.48 10.24-38.4s-7.68-30.72-20.48-43.52-30.72-20.48-53.76-20.48c-51.2 0-76.8 35.84-76.8 87.04h-51.2z m153.6 281.6h-51.2v-51.2h51.2v51.2z" fill="#FFFFFF" p-id="2803"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -2,7 +2,7 @@ const pkg = require('../package.json')
|
||||
const version = pkg.version
|
||||
// TODO: use the same name format
|
||||
const generateURL = (platform, ext, prefix = 'PicGo-') => {
|
||||
return `https://picgo-1251750343.cos.ap-chengdu.myqcloud.com/${prefix}${version}${platform}${ext}`
|
||||
return `https://picgo-release.molunerfinn.com/${version}/${prefix}${version}${platform}${ext}`
|
||||
}
|
||||
|
||||
const platformExtList = [
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
const yaml = require('js-yaml')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const languageFileName = 'zh-CN.yml' // use zh-CN for type is OK
|
||||
const i18nFolder = path.join(__dirname, '../public/i18n')
|
||||
const typeFolder = path.join(__dirname, '../src/universal/types')
|
||||
const languageFile = path.join(i18nFolder, languageFileName)
|
||||
|
||||
const langFile = fs.readFileSync(languageFile, 'utf8')
|
||||
|
||||
const obj = yaml.load(langFile)
|
||||
|
||||
const keys = Object.keys(obj)
|
||||
|
||||
const types =
|
||||
`interface ILocales {
|
||||
${keys.map(key => `${key}: string`).join('\n ')}
|
||||
}
|
||||
type ILocalesKey = keyof ILocales
|
||||
`
|
||||
|
||||
fs.writeFileSync(path.join(typeFolder, 'i18n.d.ts'), types)
|
||||
@@ -1,103 +0,0 @@
|
||||
// upload dist bundled-app to cos
|
||||
require('dotenv').config()
|
||||
const crypto = require('crypto')
|
||||
const fs = require('fs')
|
||||
const mime = require('mime-types')
|
||||
const pkg = require('../package.json')
|
||||
const configList = require('./config')
|
||||
const axios = require('axios').default
|
||||
const path = require('path')
|
||||
const distPath = path.join(__dirname, '../dist_electron')
|
||||
|
||||
const BUCKET = 'picgo-1251750343'
|
||||
const AREA = 'ap-chengdu'
|
||||
const VERSION = pkg.version
|
||||
const FILE_PATH = `${VERSION}/`
|
||||
const SECRET_ID = process.env.PICGO_ENV_COS_SECRET_ID
|
||||
const SECRET_KEY = process.env.PICGO_ENV_COS_SECRET_KEY
|
||||
|
||||
// https://cloud.tencent.com/document/product/436/7778#signature
|
||||
/**
|
||||
* @param {string} fileName
|
||||
* @returns
|
||||
*/
|
||||
const generateSignature = (fileName, folder = FILE_PATH) => {
|
||||
const secretKey = SECRET_KEY
|
||||
const area = AREA
|
||||
const bucket = BUCKET
|
||||
const path = folder
|
||||
const today = Math.floor(new Date().getTime() / 1000)
|
||||
const tomorrow = today + 86400
|
||||
const signTime = `${today};${tomorrow}`
|
||||
const signKey = crypto.createHmac('sha1', secretKey).update(signTime).digest('hex')
|
||||
const httpString = `put\n/${path}${fileName}\n\nhost=${bucket}.cos.${area}.myqcloud.com\n`
|
||||
const sha1edHttpString = crypto.createHash('sha1').update(httpString).digest('hex')
|
||||
const stringToSign = `sha1\n${signTime}\n${sha1edHttpString}\n`
|
||||
const signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex')
|
||||
return {
|
||||
signature,
|
||||
signTime
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} fileName
|
||||
* @param {Buffer} fileBuffer
|
||||
* @param {{ signature: string, signTime: string }} signature
|
||||
* @returns
|
||||
*/
|
||||
const getReqOptions = (fileName, fileBuffer, signature, folder = FILE_PATH) => {
|
||||
return {
|
||||
method: 'PUT',
|
||||
url: `http://${BUCKET}.cos.${AREA}.myqcloud.com/${encodeURI(folder)}${encodeURI(fileName)}`,
|
||||
headers: {
|
||||
Host: `${BUCKET}.cos.${AREA}.myqcloud.com`,
|
||||
Authorization: `q-sign-algorithm=sha1&q-ak=${SECRET_ID}&q-sign-time=${signature.signTime}&q-key-time=${signature.signTime}&q-header-list=host&q-url-param-list=&q-signature=${signature.signature}`,
|
||||
contentType: mime.lookup(fileName),
|
||||
useAgent: `PicGo;${pkg.version};null;null`
|
||||
},
|
||||
maxContentLength: Infinity,
|
||||
maxBodyLength: Infinity,
|
||||
data: fileBuffer,
|
||||
resolveWithFullResponse: true
|
||||
}
|
||||
}
|
||||
|
||||
const uploadFile = async () => {
|
||||
try {
|
||||
const platform = process.platform
|
||||
if (configList[platform]) {
|
||||
let versionFileHasUploaded = false
|
||||
for (const [index, config] of configList[platform].entries()) {
|
||||
const fileName = `${config.appNameWithPrefix}${VERSION}${config.arch}${config.ext}`
|
||||
const filePath = path.join(distPath, fileName)
|
||||
const versionFilePath = path.join(distPath, config['version-file'])
|
||||
let versionFileName = config['version-file']
|
||||
if (VERSION.toLocaleLowerCase().includes('beta')) {
|
||||
versionFileName = versionFileName.replace('.yml', '.beta.yml')
|
||||
}
|
||||
// upload dist file
|
||||
const signature = generateSignature(fileName)
|
||||
const reqOptions = getReqOptions(fileName, fs.readFileSync(filePath), signature)
|
||||
console.log('[PicGo Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`)
|
||||
await axios.request(reqOptions)
|
||||
|
||||
// upload version file
|
||||
if (!versionFileHasUploaded) {
|
||||
const signature = generateSignature(versionFileName, '')
|
||||
const reqOptions = getReqOptions(versionFileName, fs.readFileSync(versionFilePath), signature, '')
|
||||
console.log('[PicGo Version File] Uploading...', versionFileName)
|
||||
await axios.request(reqOptions)
|
||||
versionFileHasUploaded = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('platform not supported!', platform)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
uploadFile()
|
||||
@@ -0,0 +1,175 @@
|
||||
// upload dist bundled-app to r2
|
||||
// upload version file to cos
|
||||
|
||||
require('dotenv').config()
|
||||
// const crypto = require('crypto')
|
||||
// const axios = require('axios').default
|
||||
const fs = require('fs')
|
||||
const pkg = require('../package.json')
|
||||
const configList = require('./config')
|
||||
const mime = require('mime-types')
|
||||
const path = require('path')
|
||||
const distPath = path.join(__dirname, '../dist_electron')
|
||||
const S3Client = require('@aws-sdk/client-s3').S3Client
|
||||
const Upload = require('@aws-sdk/lib-storage').Upload
|
||||
// const BUCKET = 'picgo-1251750343'
|
||||
// const COS_SECRET_ID = process.env.PICGO_ENV_COS_SECRET_ID
|
||||
// const COS_SECRET_KEY = process.env.PICGO_ENV_COS_SECRET_KEY
|
||||
|
||||
const S3_BUCKET = 'picgo'
|
||||
// const AREA = 'ap-chengdu'
|
||||
const VERSION = pkg.version
|
||||
const FILE_PATH = `${VERSION}/`
|
||||
const S3_SECRET_ID = process.env.PICGO_ENV_S3_SECRET_ID
|
||||
const S3_SECRET_KEY = process.env.PICGO_ENV_S3_SECRET_KEY
|
||||
const S3_ACCOUNT_ID = process.env.PICGO_ENV_S3_ACCOUNT_ID
|
||||
|
||||
const S3Options = {
|
||||
credentials: {
|
||||
accessKeyId: S3_SECRET_ID,
|
||||
secretAccessKey: S3_SECRET_KEY
|
||||
},
|
||||
endpoint: `https://${S3_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
||||
sslEnabled: true,
|
||||
region: 'auto'
|
||||
}
|
||||
|
||||
// https://cloud.tencent.com/document/product/436/7778#signature
|
||||
// /**
|
||||
// * @param {string} fileName
|
||||
// * @returns
|
||||
// */
|
||||
// const generateSignature = (fileName, folder = FILE_PATH) => {
|
||||
// const secretKey = COS_SECRET_ID
|
||||
// // const area = AREA
|
||||
// const bucket = BUCKET
|
||||
// const path = folder
|
||||
// const today = Math.floor(new Date().getTime() / 1000)
|
||||
// const tomorrow = today + 86400
|
||||
// const signTime = `${today};${tomorrow}`
|
||||
// const signKey = crypto.createHmac('sha1', secretKey).update(signTime).digest('hex')
|
||||
// const httpString = `put\n/${path}${fileName}\n\nhost=${bucket}.cos.accelerate.myqcloud.com\n`
|
||||
// const sha1edHttpString = crypto.createHash('sha1').update(httpString).digest('hex')
|
||||
// const stringToSign = `sha1\n${signTime}\n${sha1edHttpString}\n`
|
||||
// const signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex')
|
||||
// return {
|
||||
// signature,
|
||||
// signTime
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param {string} fileName
|
||||
// * @param {Buffer} fileBuffer
|
||||
// * @param {{ signature: string, signTime: string }} signature
|
||||
// * @returns
|
||||
// */
|
||||
// const getReqOptions = (fileName, fileBuffer, signature, folder = FILE_PATH) => {
|
||||
// return {
|
||||
// method: 'PUT',
|
||||
// url: `http://${BUCKET}.cos.accelerate.myqcloud.com/${encodeURI(folder)}${encodeURI(fileName)}`,
|
||||
// headers: {
|
||||
// Host: `${BUCKET}.cos.accelerate.myqcloud.com`,
|
||||
// Authorization: `q-sign-algorithm=sha1&q-ak=${COS_SECRET_KEY}&q-sign-time=${signature.signTime}&q-key-time=${signature.signTime}&q-header-list=host&q-url-param-list=&q-signature=${signature.signature}`,
|
||||
// contentType: mime.lookup(fileName),
|
||||
// useAgent: `PicGo;${pkg.version};null;null`
|
||||
// },
|
||||
// maxContentLength: Infinity,
|
||||
// maxBodyLength: Infinity,
|
||||
// data: fileBuffer,
|
||||
// resolveWithFullResponse: true
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* a backup for version file
|
||||
*/
|
||||
// const uploadVersionFile = async () => {
|
||||
// try {
|
||||
// const platform = process.platform
|
||||
// if (configList[platform]) {
|
||||
// let versionFileHasUploaded = false
|
||||
// for (const [, config] of configList[platform].entries()) {
|
||||
// const versionFilePath = path.join(distPath, config['version-file'])
|
||||
// let versionFileName = config['version-file']
|
||||
// if (VERSION.toLocaleLowerCase().includes('beta')) {
|
||||
// versionFileName = versionFileName.replace('.yml', '.beta.yml')
|
||||
// }
|
||||
// // upload version file
|
||||
// if (!versionFileHasUploaded) {
|
||||
// const signature = generateSignature(versionFileName, '')
|
||||
// const reqOptions = getReqOptions(versionFileName, fs.readFileSync(versionFilePath), signature, '')
|
||||
// console.log('[PicGo Version File] Uploading...', versionFileName)
|
||||
// await axios.request(reqOptions)
|
||||
// versionFileHasUploaded = true
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// console.warn('platform not supported!', platform)
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error(e)
|
||||
// }
|
||||
// }
|
||||
|
||||
const uploadDist = async () => {
|
||||
try {
|
||||
const platform = process.platform
|
||||
if (configList[platform]) {
|
||||
let versionFileHasUploaded = false
|
||||
for (const [index, config] of configList[platform].entries()) {
|
||||
const fileName = `${config.appNameWithPrefix}${VERSION}${config.arch}${config.ext}`
|
||||
const filePath = path.join(distPath, fileName)
|
||||
const versionFilePath = path.join(distPath, config['version-file'])
|
||||
let versionFileName = config['version-file']
|
||||
if (VERSION.toLocaleLowerCase().includes('beta')) {
|
||||
versionFileName = versionFileName.replace('.yml', '.beta.yml')
|
||||
}
|
||||
|
||||
const client = new S3Client(S3Options)
|
||||
const uploadDistToS3 = new Upload({
|
||||
client,
|
||||
params: {
|
||||
Bucket: S3_BUCKET,
|
||||
Key: `${FILE_PATH}${fileName}`,
|
||||
Body: fs.createReadStream(filePath),
|
||||
ContentType: 'application/octet-stream'
|
||||
}
|
||||
})
|
||||
// upload dist file
|
||||
console.log('[PicGo Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`)
|
||||
uploadDistToS3.on('httpUploadProgress', progress => {
|
||||
console.log(`[PicGo Dist] Uploading... ${progress.loaded}/${progress.total}`)
|
||||
})
|
||||
await uploadDistToS3.done()
|
||||
|
||||
// upload version file
|
||||
if (!versionFileHasUploaded) {
|
||||
const uploadVersionFileToS3 = new Upload({
|
||||
client,
|
||||
params: {
|
||||
Bucket: S3_BUCKET,
|
||||
Key: `${versionFileName}`,
|
||||
Body: fs.createReadStream(versionFilePath),
|
||||
ContentType: mime.lookup(versionFileName)
|
||||
}
|
||||
})
|
||||
console.log('[PicGo Version File] Uploading...', versionFileName)
|
||||
await uploadVersionFileToS3.done()
|
||||
versionFileHasUploaded = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('platform not supported!', platform)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const main = async () => {
|
||||
await uploadDist()
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -1,25 +1,29 @@
|
||||
import Vue from 'vue'
|
||||
import { createApp } from 'vue'
|
||||
import App from './renderer/App.vue'
|
||||
import router from './renderer/router'
|
||||
import ElementUI from 'element-ui'
|
||||
import ElementUI from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import { webFrame } from 'electron'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import VueLazyLoad from 'vue-lazyload'
|
||||
import VueLazyLoad from 'vue3-lazyload'
|
||||
import axios from 'axios'
|
||||
import mainMixin from './renderer/utils/mainMixin'
|
||||
import bus from '@/utils/bus'
|
||||
import { mainMixin } from './renderer/utils/mainMixin'
|
||||
import { dragMixin } from '@/utils/mixin'
|
||||
import { initTalkingData } from './renderer/utils/analytics'
|
||||
import db from './renderer/utils/db'
|
||||
import { T, i18n } from '#/i18n/index'
|
||||
import { handleURLParams } from '@/utils/beforeOpen'
|
||||
import { i18nManager, T } from './renderer/i18n/index'
|
||||
import { getConfig, saveConfig, sendToMain, triggerRPC } from '@/utils/dataSender'
|
||||
import { store } from '@/store'
|
||||
import vue3PhotoPreview from 'vue3-photo-preview'
|
||||
import 'vue3-photo-preview/dist/index.css'
|
||||
|
||||
webFrame.setVisualZoomLevelLimits(1, 1)
|
||||
|
||||
// do here before vue init
|
||||
handleURLParams()
|
||||
// handleURLParams()
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$builtInPicBed = [
|
||||
const app = createApp(App)
|
||||
|
||||
app.config.globalProperties.$builtInPicBed = [
|
||||
'smms',
|
||||
'imgur',
|
||||
'qiniu',
|
||||
@@ -28,19 +32,28 @@ Vue.prototype.$builtInPicBed = [
|
||||
'aliyun',
|
||||
'github'
|
||||
]
|
||||
Vue.prototype.$$db = db
|
||||
Vue.prototype.$http = axios
|
||||
Vue.prototype.$bus = bus
|
||||
Vue.prototype.$T = T
|
||||
Vue.prototype.$i18n = i18n
|
||||
app.config.unwrapInjectedRef = true
|
||||
|
||||
Vue.use(ElementUI)
|
||||
Vue.use(VueLazyLoad)
|
||||
Vue.mixin(mainMixin)
|
||||
app.config.globalProperties.$$db = db
|
||||
app.config.globalProperties.$http = axios
|
||||
app.config.globalProperties.$T = T
|
||||
app.config.globalProperties.$i18n = i18nManager
|
||||
app.config.globalProperties.getConfig = getConfig
|
||||
app.config.globalProperties.triggerRPC = triggerRPC
|
||||
app.config.globalProperties.saveConfig = saveConfig
|
||||
app.config.globalProperties.sendToMain = sendToMain
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
app.mixin(mainMixin)
|
||||
app.mixin(dragMixin)
|
||||
|
||||
app.use(VueLazyLoad, {
|
||||
error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg`
|
||||
})
|
||||
app.use(ElementUI)
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
app.use(vue3PhotoPreview)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
initTalkingData()
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// get notice from remote
|
||||
// such as some notices for users; some updates for users
|
||||
import fs from 'fs-extra'
|
||||
import { app, clipboard, dialog, shell } from 'electron'
|
||||
import { IRemoteNoticeActionType, IRemoteNoticeTriggerCount, IRemoteNoticeTriggerHook } from '#/types/enum'
|
||||
import { lte, gte } from 'semver'
|
||||
import path from 'path'
|
||||
|
||||
import axios from 'axios'
|
||||
import windowManager from '../window/windowManager'
|
||||
import { showNotification } from '~/main/utils/common'
|
||||
import { isDev } from '~/universal/utils/common'
|
||||
|
||||
// for test
|
||||
const REMOTE_NOTICE_URL = isDev ? 'http://localhost:8181/remote-notice.json' : 'https://picgo-1251750343.cos.accelerate.myqcloud.com/remote-notice.yml'
|
||||
|
||||
const REMOTE_NOTICE_LOCAL_STORAGE_FILE = 'picgo-remote-notice.json'
|
||||
|
||||
const STORE_PATH = app.getPath('userData')
|
||||
|
||||
const REMOTE_NOTICE_LOCAL_STORAGE_PATH = path.join(STORE_PATH, REMOTE_NOTICE_LOCAL_STORAGE_FILE)
|
||||
|
||||
class RemoteNoticeHandler {
|
||||
private remoteNotice: IRemoteNotice | null = null
|
||||
private remoteNoticeLocalCountStorage: IRemoteNoticeLocalCountStorage | null = null
|
||||
|
||||
async init () {
|
||||
this.remoteNotice = await this.getRemoteNoticeInfo()
|
||||
this.initLocalCountStorage()
|
||||
}
|
||||
|
||||
private initLocalCountStorage () {
|
||||
const localCountStorage = {}
|
||||
if (!fs.existsSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH)) {
|
||||
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
|
||||
}
|
||||
try {
|
||||
const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, 'utf8')
|
||||
this.remoteNoticeLocalCountStorage = localCountStorage
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
this.remoteNoticeLocalCountStorage = localCountStorage
|
||||
}
|
||||
}
|
||||
|
||||
private saveLocalCountStorage (newData?: IRemoteNoticeLocalCountStorage) {
|
||||
if (newData) {
|
||||
this.remoteNoticeLocalCountStorage = newData
|
||||
}
|
||||
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify(this.remoteNoticeLocalCountStorage))
|
||||
}
|
||||
|
||||
private async getRemoteNoticeInfo (): Promise<IRemoteNotice | null> {
|
||||
try {
|
||||
const noticeInfo = await axios({
|
||||
method: 'get',
|
||||
url: REMOTE_NOTICE_URL,
|
||||
responseType: 'json'
|
||||
}).then(res => res.data) as IRemoteNotice
|
||||
return noticeInfo
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if the notice is not shown or is always shown, then show the notice
|
||||
* @param action
|
||||
*/
|
||||
private checkActionCount (action: IRemoteNoticeAction) {
|
||||
try {
|
||||
if (!this.remoteNoticeLocalCountStorage) {
|
||||
return true
|
||||
}
|
||||
const actionCount = this.remoteNoticeLocalCountStorage[action.id]
|
||||
if (actionCount === undefined) {
|
||||
if (action.triggerCount === IRemoteNoticeTriggerCount.ALWAYS) {
|
||||
this.remoteNoticeLocalCountStorage[action.id] = 1 // if always, count number
|
||||
} else {
|
||||
this.remoteNoticeLocalCountStorage[action.id] = true
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
// here is the count of action
|
||||
// if not always show, then can't show
|
||||
if (action.triggerCount !== IRemoteNoticeTriggerCount.ALWAYS) {
|
||||
return false
|
||||
} else {
|
||||
const preCount = this.remoteNoticeLocalCountStorage[action.id]
|
||||
if (typeof preCount !== 'number') {
|
||||
this.remoteNoticeLocalCountStorage[action.id] = true
|
||||
return true
|
||||
} else {
|
||||
this.remoteNoticeLocalCountStorage[action.id] = preCount + 1
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.saveLocalCountStorage()
|
||||
}
|
||||
}
|
||||
|
||||
private async doActions (actions: IRemoteNoticeAction[]) {
|
||||
for (const action of actions) {
|
||||
if (this.checkActionCount(action)) {
|
||||
switch (action.type) {
|
||||
case IRemoteNoticeActionType.SHOW_DIALOG: {
|
||||
// SHOW DIALOG
|
||||
const currentWindow = windowManager.getAvailableWindow()
|
||||
dialog.showOpenDialog(currentWindow, action.data?.options)
|
||||
break
|
||||
}
|
||||
case IRemoteNoticeActionType.SHOW_NOTICE:
|
||||
showNotification({
|
||||
title: action.data?.title || '',
|
||||
body: action.data?.content || '',
|
||||
clickToCopy: !!action.data?.copyToClipboard,
|
||||
copyContent: action.data?.copyToClipboard || '',
|
||||
clickFn () {
|
||||
if (action.data?.url) {
|
||||
shell.openExternal(action.data.url)
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
case IRemoteNoticeActionType.OPEN_URL:
|
||||
// OPEN URL
|
||||
shell.openExternal(action.data?.url || '')
|
||||
break
|
||||
case IRemoteNoticeActionType.COMMON:
|
||||
// DO COMMON CASE
|
||||
if (action.data?.copyToClipboard) {
|
||||
clipboard.writeText(action.data.copyToClipboard)
|
||||
}
|
||||
if (action.data?.url) {
|
||||
shell.openExternal(action.data.url)
|
||||
}
|
||||
break
|
||||
case IRemoteNoticeActionType.SHOW_MESSAGE_BOX: {
|
||||
const currentWindow = windowManager.getAvailableWindow()
|
||||
dialog.showMessageBox(currentWindow, {
|
||||
title: action.data?.title || '',
|
||||
message: action.data?.content || '',
|
||||
type: 'info',
|
||||
buttons: action.data?.buttons?.map(item => item.label) || ['Yes']
|
||||
}).then(res => {
|
||||
const button = action.data?.buttons?.[res.response]
|
||||
if (button?.type === 'cancel') {
|
||||
// do nothing
|
||||
} else {
|
||||
if (button?.action) {
|
||||
this.doActions([button?.action])
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
triggerHook (hook: IRemoteNoticeTriggerHook) {
|
||||
if (!this.remoteNotice || !this.remoteNotice.list) {
|
||||
return
|
||||
}
|
||||
const actions = this.remoteNotice.list
|
||||
.filter(item => {
|
||||
if (item.versionMatch) {
|
||||
switch (item.versionMatch) {
|
||||
case 'exact':
|
||||
return item.versions.includes(app.getVersion())
|
||||
case 'gte':
|
||||
return item.versions.some(version => {
|
||||
// appVersion >= version
|
||||
return gte(app.getVersion(), version)
|
||||
})
|
||||
case 'lte':
|
||||
return item.versions.some(version => {
|
||||
// appVersion <= version
|
||||
return lte(app.getVersion(), version)
|
||||
})
|
||||
}
|
||||
}
|
||||
return item.versions.includes(app.getVersion())
|
||||
})
|
||||
.map(item => item.actions)
|
||||
.reduce((pre, cur) => pre.concat(cur), [])
|
||||
.filter(item => item.hooks.includes(hook))
|
||||
this.doActions(actions)
|
||||
}
|
||||
}
|
||||
|
||||
const remoteNoticeHandler = new RemoteNoticeHandler()
|
||||
|
||||
export {
|
||||
remoteNoticeHandler
|
||||
}
|
||||
@@ -1,46 +1,32 @@
|
||||
import fs from 'fs-extra'
|
||||
import {
|
||||
app,
|
||||
Menu,
|
||||
Tray,
|
||||
dialog,
|
||||
clipboard,
|
||||
systemPreferences,
|
||||
Notification
|
||||
Notification,
|
||||
nativeTheme
|
||||
} from 'electron'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import getPicBeds from '~/main/utils/getPicBeds'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import picgo from '@core/picgo'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import pkg from 'root/package.json'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { ensureFilePath, handleCopyUrl } from '~/main/utils/common'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
import { T } from '#/i18n'
|
||||
// import { T } from '#/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
import { isMacOSVersionGreaterThanOrEqualTo } from '~/main/utils/getMacOSVersion'
|
||||
import { buildPicBedListMenu } from '~/main/events/remotes/menu'
|
||||
let contextMenu: Menu | null
|
||||
let menu: Menu | null
|
||||
let tray: Tray | null
|
||||
// need to build new menu
|
||||
export function createContextMenu () {
|
||||
const picBeds = getPicBeds()
|
||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
||||
const submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
return {
|
||||
label: item.name,
|
||||
type: 'radio',
|
||||
checked: db.get('picBed.current') === item.type,
|
||||
click () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type,
|
||||
'picBed.uploader': item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const submenu = buildPicBedListMenu()
|
||||
contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: T('ABOUT'),
|
||||
@@ -146,12 +132,13 @@ export function createContextMenu () {
|
||||
}
|
||||
])
|
||||
}
|
||||
return contextMenu!
|
||||
}
|
||||
|
||||
const getTrayIcon = () => {
|
||||
if (process.platform === 'darwin') {
|
||||
const isMacOSGreaterThan11 = isMacOSVersionGreaterThanOrEqualTo('11')
|
||||
return isMacOSGreaterThan11 ? `${__static}/menubar-newdarwin-v2.png` : `${__static}/menubar.png`
|
||||
return isMacOSGreaterThan11 ? `${__static}/menubar-newdarwinTemplate.png` : `${__static}/menubar.png`
|
||||
} else {
|
||||
return `${__static}/menubar-nodarwin.png`
|
||||
}
|
||||
@@ -172,18 +159,37 @@ export function createTray () {
|
||||
tray.on('click', (event, bounds) => {
|
||||
if (process.platform === 'darwin') {
|
||||
toggleWindow(bounds)
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
const img = clipboard.readImage()
|
||||
const obj: ImgInfo[] = []
|
||||
if (!img.isEmpty()) {
|
||||
// 从剪贴板来的图片默认转为png
|
||||
// @ts-ignore
|
||||
const imgUrl = 'data:image/png;base64,' + Buffer.from(img.toPNG(), 'binary').toString('base64')
|
||||
obj.push({
|
||||
width: img.getSize().width,
|
||||
height: img.getSize().height,
|
||||
imgUrl
|
||||
})
|
||||
// https://github.com/electron/electron/issues/9035
|
||||
const imgPath = clipboard.read('public.file-url')
|
||||
if (imgPath) {
|
||||
const decodePath = ensureFilePath(imgPath)
|
||||
if (decodePath === imgPath) {
|
||||
obj.push({
|
||||
imgUrl: imgPath
|
||||
})
|
||||
} else {
|
||||
if (decodePath !== '') {
|
||||
// 带有中文的路径,无法直接被img.src所使用,会被转义
|
||||
const base64 = await fs.readFile(decodePath.replace('file://', ''), { encoding: 'base64' })
|
||||
obj.push({
|
||||
imgUrl: `data:image/png;base64,${base64}`
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const imgUrl = img.toDataURL()
|
||||
// console.log(imgUrl)
|
||||
obj.push({
|
||||
width: img.getSize().width,
|
||||
height: img.getSize().height,
|
||||
imgUrl
|
||||
})
|
||||
}
|
||||
}
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('clipboardFiles', obj)
|
||||
}, 0)
|
||||
@@ -201,7 +207,7 @@ export function createTray () {
|
||||
})
|
||||
|
||||
tray.on('drag-enter', () => {
|
||||
if (systemPreferences.isDarkMode()) {
|
||||
if (nativeTheme.shouldUseDarkColors) {
|
||||
tray!.setImage(`${__static}/upload-dark.png`)
|
||||
} else {
|
||||
tray!.setImage(`${__static}/upload.png`)
|
||||
@@ -209,7 +215,8 @@ export function createTray () {
|
||||
})
|
||||
|
||||
tray.on('drag-end', () => {
|
||||
tray!.setImage(`${__static}/menubar.png`)
|
||||
const menubarPic = getTrayIcon()
|
||||
tray!.setImage(menubarPic)
|
||||
})
|
||||
|
||||
// drop-files only be supported in macOS
|
||||
@@ -226,8 +233,8 @@ export function createTray () {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl!,
|
||||
icon: files[i]
|
||||
body: imgs[i].imgUrl!
|
||||
// icon: files[i]
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
@@ -247,7 +254,21 @@ export function createTray () {
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDockIcon () {
|
||||
if (process.platform === 'darwin') {
|
||||
if (db.get('settings.showDockIcon') !== false) {
|
||||
app.dock.show()
|
||||
app.dock.setMenu(createContextMenu())
|
||||
} else {
|
||||
app.dock.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createMenu () {
|
||||
if (menu) {
|
||||
return menu
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
const template = [{
|
||||
label: 'Edit',
|
||||
|
||||
@@ -9,7 +9,7 @@ import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { handleUrlEncode } from '#/utils/common'
|
||||
import { T } from '#/i18n/index'
|
||||
import { T } from '~/main/i18n/index'
|
||||
// import dayjs from 'dayjs'
|
||||
|
||||
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
@@ -23,6 +23,7 @@ const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
|
||||
export const uploadClipboardFiles = async (): Promise<string> => {
|
||||
const img = await handleClipboardUploading()
|
||||
console.log(img)
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
@@ -30,8 +31,8 @@ export const uploadClipboardFiles = async (): Promise<string> => {
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: img[0].imgUrl!,
|
||||
icon: img[0].imgUrl
|
||||
body: img[0].imgUrl!
|
||||
// icon: img[0].imgUrl
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
@@ -68,8 +69,8 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl!,
|
||||
icon: files[i].path
|
||||
body: imgs[i].imgUrl!
|
||||
// icon: files[i].path
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
|
||||
@@ -12,10 +12,10 @@ import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import util from 'util'
|
||||
import { IPicGo } from 'picgo'
|
||||
import { showNotification, calcDurationRange } from '~/main/utils/common'
|
||||
import { showNotification, calcDurationRange, getClipboardFilePath } from '~/main/utils/common'
|
||||
import { RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { T } from '~/universal/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
import fse from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
@@ -121,16 +121,21 @@ class Uploader {
|
||||
async uploadWithBuildInClipboard (): Promise<ImgInfo[]|false> {
|
||||
let filePath = ''
|
||||
try {
|
||||
const nativeImage = clipboard.readImage()
|
||||
if (nativeImage.isEmpty()) {
|
||||
return false
|
||||
const imgPath = getClipboardFilePath()
|
||||
if (!imgPath) {
|
||||
const nativeImage = clipboard.readImage()
|
||||
if (nativeImage.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
const buffer = nativeImage.toPNG()
|
||||
const baseDir = picgo.baseDir
|
||||
const fileName = `${dayjs().format('YYYYMMDDHHmmSSS')}.png`
|
||||
filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName)
|
||||
await writeFile(filePath, buffer)
|
||||
return await this.upload([filePath])
|
||||
} else {
|
||||
return await this.upload([imgPath])
|
||||
}
|
||||
const buffer = nativeImage.toPNG()
|
||||
const baseDir = picgo.baseDir
|
||||
const fileName = `${dayjs().format('YYYYMMDDHHmmSSS')}.png`
|
||||
filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName)
|
||||
await writeFile(filePath, buffer)
|
||||
return await this.upload([filePath])
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
return false
|
||||
|
||||
@@ -15,3 +15,7 @@ export const MINI_WINDOW_URL = isDevelopment
|
||||
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
|
||||
: 'picgo://./index.html#rename-page'
|
||||
|
||||
export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development'
|
||||
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page`
|
||||
: 'picgo://./index.html#toolbox-page'
|
||||
|
||||
@@ -2,25 +2,28 @@ import {
|
||||
SETTING_WINDOW_URL,
|
||||
TRAY_WINDOW_URL,
|
||||
MINI_WINDOW_URL,
|
||||
RENAME_WINDOW_URL
|
||||
RENAME_WINDOW_URL,
|
||||
TOOLBOX_WINDOW_URL
|
||||
} from './constants'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
|
||||
import bus from '@core/bus'
|
||||
import { CREATE_APP_MENU } from '@core/bus/constants'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import { app } from 'electron'
|
||||
import { i18n } from '~/universal/i18n'
|
||||
import { URLSearchParams } from 'url'
|
||||
import { remoteNoticeHandler } from '../remoteNotice'
|
||||
import { T } from '~/main/i18n'
|
||||
// import { URLSearchParams } from 'url'
|
||||
|
||||
const windowList = new Map<IWindowList, IWindowListItem>()
|
||||
|
||||
const handleWindowParams = (windowURL: string) => {
|
||||
const [baseURL, hash = ''] = windowURL.split('#')
|
||||
const search = new URLSearchParams()
|
||||
const lang = i18n.getLanguage()
|
||||
search.append('lang', lang)
|
||||
return `${baseURL}?${search.toString()}#${hash}`
|
||||
// const [baseURL, hash = ''] = windowURL.split('#')
|
||||
// const search = new URLSearchParams()
|
||||
// const lang = i18n.getLanguage()
|
||||
// search.append('lang', lang)
|
||||
// return `${baseURL}?${search.toString()}#${hash}`
|
||||
return windowURL
|
||||
}
|
||||
|
||||
windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
@@ -40,7 +43,8 @@ windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true,
|
||||
backgroundThrottling: false
|
||||
backgroundThrottling: false,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -56,6 +60,7 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options () {
|
||||
const showDockIcon = db.get('settings.showDockIcon') !== false
|
||||
const options: IBrowserWindowOptions = {
|
||||
height: 450,
|
||||
width: 800,
|
||||
@@ -67,6 +72,7 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
title: 'PicGo',
|
||||
vibrancy: 'ultra-dark',
|
||||
transparent: true,
|
||||
skipTaskbar: !showDockIcon,
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
@@ -82,10 +88,14 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
options.backgroundColor = '#3f3c37'
|
||||
options.transparent = false
|
||||
options.icon = `${__static}/logo.png`
|
||||
options.skipTaskbar = false
|
||||
}
|
||||
return options
|
||||
},
|
||||
callback (window, windowManager) {
|
||||
window.once('show', () => {
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
|
||||
})
|
||||
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
|
||||
window.on('closed', () => {
|
||||
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
@@ -177,4 +187,53 @@ windowList.set(IWindowList.RENAME_WINDOW, {
|
||||
}
|
||||
})
|
||||
|
||||
windowList.set(IWindowList.TOOLBOX_WINDOW, {
|
||||
isValid: true,
|
||||
multiple: false,
|
||||
options () {
|
||||
const options: IBrowserWindowOptions = {
|
||||
height: 450,
|
||||
width: 800,
|
||||
show: false,
|
||||
frame: true,
|
||||
center: true,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
title: `PicGo ${T('TOOLBOX')}`,
|
||||
vibrancy: 'ultra-dark',
|
||||
icon: `${__static}/logo.png`,
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
|
||||
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
|
||||
nodeIntegrationInWorker: true,
|
||||
webSecurity: false
|
||||
}
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
options.backgroundColor = '#3f3c37'
|
||||
options.autoHideMenuBar = true
|
||||
options.transparent = false
|
||||
}
|
||||
return options
|
||||
},
|
||||
async callback (window, windowManager) {
|
||||
window.loadURL(TOOLBOX_WINDOW_URL)
|
||||
const currentWindow = windowManager.getAvailableWindow()
|
||||
if (currentWindow && currentWindow.isVisible()) {
|
||||
// bounds: { x: 821, y: 75, width: 800, height: 450 }
|
||||
const bounds = currentWindow.getBounds()
|
||||
const positionX = bounds.x + bounds.width / 2 - 400
|
||||
let positionY
|
||||
// if is the settingWindow
|
||||
if (bounds.height > 400) {
|
||||
positionY = bounds.y + bounds.height / 2 - 225
|
||||
} else { // if is the miniWindow
|
||||
positionY = bounds.y + bounds.height / 2
|
||||
}
|
||||
window.setPosition(positionX, positionY, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default windowList
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'path'
|
||||
import { app as APP } from 'electron'
|
||||
import { getLogger } from '@core/utils/localLogger'
|
||||
import dayjs from 'dayjs'
|
||||
import { T } from '~/universal/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
const STORE_PATH = APP.getPath('userData')
|
||||
const configFilePath = path.join(STORE_PATH, 'data.json')
|
||||
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from 'fs-extra'
|
||||
import { dbPathChecker, dbPathDir, getGalleryDBPath } from './dbChecker'
|
||||
import { DBStore, JSONStore } from '@picgo/store'
|
||||
import { T } from '~/universal/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
const STORE_PATH = dbPathDir()
|
||||
|
||||
@@ -9,7 +9,7 @@ if (!fs.pathExistsSync(STORE_PATH)) {
|
||||
fs.mkdirpSync(STORE_PATH)
|
||||
}
|
||||
const CONFIG_PATH: string = dbPathChecker()
|
||||
const DB_PATH: string = getGalleryDBPath().dbPath
|
||||
export const DB_PATH: string = getGalleryDBPath().dbPath
|
||||
|
||||
class ConfigStore {
|
||||
private db: JSONStore
|
||||
@@ -37,9 +37,12 @@ class ConfigStore {
|
||||
this.read()
|
||||
}
|
||||
|
||||
read (flush = false) {
|
||||
// if flush -> true, read origin file again
|
||||
this.db.read(flush)
|
||||
flush () {
|
||||
this.db = new JSONStore(CONFIG_PATH)
|
||||
}
|
||||
|
||||
read () {
|
||||
this.db.read()
|
||||
return this.db
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
|
||||
import pkg from 'root/package.json'
|
||||
import { PicGo } from 'picgo'
|
||||
import db from 'apis/core/datastore'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
const CONFIG_PATH = dbPathChecker()
|
||||
|
||||
@@ -18,12 +19,16 @@ picgo.GUI_VERSION = global.PICGO_GUI_VERSION
|
||||
|
||||
const originPicGoSaveConfig = picgo.saveConfig.bind(picgo)
|
||||
|
||||
function flushDB () {
|
||||
db.flush()
|
||||
}
|
||||
|
||||
const debounced = debounce(flushDB, 1000)
|
||||
|
||||
picgo.saveConfig = (config: IStringKeyMap) => {
|
||||
originPicGoSaveConfig(config)
|
||||
// flush electron's db
|
||||
setTimeout(() => {
|
||||
db.read(true)
|
||||
}, 0)
|
||||
debounced()
|
||||
}
|
||||
|
||||
export default picgo
|
||||
|
||||
@@ -7,24 +7,36 @@ const checkLogFileIsLarge = (logPath: string): {
|
||||
logFileSize?: number
|
||||
logFileSizeLimit?: number
|
||||
} => {
|
||||
if (fs.existsSync(logPath)) {
|
||||
const logFileSize = fs.statSync(logPath).size
|
||||
const logFileSizeLimit = 10 * 1024 * 1024 // 10 MB default
|
||||
try {
|
||||
if (fs.existsSync(logPath)) {
|
||||
const logFileSize = fs.statSync(logPath).size
|
||||
const logFileSizeLimit = 10 * 1024 * 1024 // 10 MB default
|
||||
return {
|
||||
isLarge: logFileSize > logFileSizeLimit,
|
||||
logFileSize,
|
||||
logFileSizeLimit
|
||||
}
|
||||
}
|
||||
return {
|
||||
isLarge: logFileSize > logFileSizeLimit,
|
||||
logFileSize,
|
||||
logFileSizeLimit
|
||||
isLarge: false
|
||||
}
|
||||
} catch (e) {
|
||||
// why throw error???
|
||||
console.log(e)
|
||||
return {
|
||||
isLarge: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
isLarge: false
|
||||
}
|
||||
}
|
||||
|
||||
const recreateLogFile = (logPath: string): void => {
|
||||
if (fs.existsSync(logPath)) {
|
||||
fs.unlinkSync(logPath)
|
||||
fs.createFileSync(logPath)
|
||||
try {
|
||||
if (fs.existsSync(logPath)) {
|
||||
fs.unlinkSync(logPath)
|
||||
fs.createFileSync(logPath)
|
||||
}
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +44,25 @@ const recreateLogFile = (logPath: string): void => {
|
||||
* for local log before picgo inited
|
||||
*/
|
||||
const getLogger = (logPath: string) => {
|
||||
if (!fs.existsSync(logPath)) {
|
||||
fs.ensureFileSync(logPath)
|
||||
}
|
||||
if (checkLogFileIsLarge(logPath).isLarge) {
|
||||
recreateLogFile(logPath)
|
||||
let hasUncathcedError = false
|
||||
try {
|
||||
if (!fs.existsSync(logPath)) {
|
||||
fs.ensureFileSync(logPath)
|
||||
}
|
||||
if (checkLogFileIsLarge(logPath).isLarge) {
|
||||
recreateLogFile(logPath)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
hasUncathcedError = true
|
||||
}
|
||||
return (type: string, ...msg: any[]) => {
|
||||
if (hasUncathcedError) {
|
||||
if (checkLogFileIsLarge(logPath).isLarge) {
|
||||
recreateLogFile(logPath)
|
||||
}
|
||||
return
|
||||
}
|
||||
try {
|
||||
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ${type.toUpperCase()}] `
|
||||
msg.forEach((item: ILogArgvTypeWithError) => {
|
||||
@@ -54,9 +78,15 @@ const getLogger = (logPath: string) => {
|
||||
log += '\n'
|
||||
console.log(log)
|
||||
// A synchronized approach to avoid log msg sequence errors
|
||||
fs.appendFileSync(logPath, log)
|
||||
if (checkLogFileIsLarge(logPath).isLarge) {
|
||||
recreateLogFile(logPath)
|
||||
}
|
||||
if (!hasUncathcedError) {
|
||||
fs.appendFileSync(logPath, log)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.log(e)
|
||||
hasUncathcedError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,11 @@ import {
|
||||
SHOW_INPUT_BOX
|
||||
} from '~/universal/events/constants'
|
||||
import { DBStore } from '@picgo/store'
|
||||
import { T } from '~/universal/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
// Cross-process support may be required in the future
|
||||
class GuiApi implements IGuiApi {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
private static instance: GuiApi
|
||||
private windowId: number = -1
|
||||
private settingWindowId: number = -1
|
||||
@@ -69,7 +70,7 @@ class GuiApi implements IGuiApi {
|
||||
async showFileExplorer (options: IShowFileExplorerOption = {}) {
|
||||
this.windowId = await getWindowId()
|
||||
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
|
||||
return res.filePaths?.[0]
|
||||
return res.filePaths || []
|
||||
}
|
||||
|
||||
async upload (input: IUploadOption) {
|
||||
@@ -83,8 +84,8 @@ class GuiApi implements IGuiApi {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: imgs[i].imgUrl as string,
|
||||
icon: imgs[i].imgUrl
|
||||
body: imgs[i].imgUrl as string
|
||||
// icon: imgs[i].imgUrl
|
||||
})
|
||||
setTimeout(() => {
|
||||
notification.show()
|
||||
|
||||
@@ -25,10 +25,9 @@ import {
|
||||
SHOW_UPLOAD_PAGE_MENU,
|
||||
OPEN_USER_STORE_FILE,
|
||||
OPEN_URL,
|
||||
RELOAD_APP,
|
||||
SHOW_PLUGIN_PAGE_MENU,
|
||||
SET_MINI_WINDOW_POS,
|
||||
CHANGE_LANGUAGE
|
||||
GET_PICBEDS
|
||||
} from '#/events/constants'
|
||||
import {
|
||||
uploadClipboardFiles,
|
||||
@@ -36,9 +35,9 @@ import {
|
||||
} from '~/main/apis/app/uploader/apis'
|
||||
import picgoCoreIPC from './picgoCoreIPC'
|
||||
import { handleCopyUrl } from '~/main/utils/common'
|
||||
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildUploadPageMenu } from './remotes/menu'
|
||||
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildPicBedListMenu } from './remotes/menu'
|
||||
import path from 'path'
|
||||
import { i18n, T } from '~/universal/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
const STORE_PATH = app.getPath('userData')
|
||||
|
||||
@@ -48,15 +47,16 @@ export default {
|
||||
// from macOS tray
|
||||
ipcMain.on('uploadClipboardFiles', async () => {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
|
||||
const img = await uploader.setWebContents(trayWindow.webContents).upload()
|
||||
// macOS use builtin clipboard is OK
|
||||
const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard()
|
||||
if (img !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_SUCCEED'),
|
||||
body: img[0].imgUrl!,
|
||||
body: img[0].imgUrl!
|
||||
// icon: file[0]
|
||||
icon: img[0].imgUrl
|
||||
// icon: img[0].imgUrl
|
||||
})
|
||||
notification.show()
|
||||
await GalleryDB.getInstance().insert(img[0])
|
||||
@@ -69,6 +69,7 @@ export default {
|
||||
})
|
||||
|
||||
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
|
||||
console.log('handle')
|
||||
uploadClipboardFiles()
|
||||
})
|
||||
|
||||
@@ -152,9 +153,9 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('getPicBeds', (evt: IpcMainEvent) => {
|
||||
ipcMain.on(GET_PICBEDS, (evt: IpcMainEvent) => {
|
||||
const picBeds = getPicBeds()
|
||||
evt.sender.send('getPicBeds', picBeds)
|
||||
evt.sender.send(GET_PICBEDS, picBeds)
|
||||
evt.returnValue = picBeds
|
||||
})
|
||||
|
||||
@@ -185,7 +186,7 @@ export default {
|
||||
})
|
||||
ipcMain.on(SHOW_UPLOAD_PAGE_MENU, () => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const menu = buildUploadPageMenu()
|
||||
const menu = buildPicBedListMenu()
|
||||
menu.popup({
|
||||
window
|
||||
})
|
||||
@@ -216,18 +217,10 @@ export default {
|
||||
ipcMain.on(OPEN_URL, (evt: IpcMainEvent, url: string) => {
|
||||
shell.openExternal(url)
|
||||
})
|
||||
ipcMain.on(RELOAD_APP, () => {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
})
|
||||
ipcMain.on(SET_MINI_WINDOW_POS, (evt: IpcMainEvent, pos: IMiniWindowPos) => {
|
||||
const window = BrowserWindow.getFocusedWindow()
|
||||
window?.setBounds(pos)
|
||||
})
|
||||
ipcMain.on(CHANGE_LANGUAGE, (evt: IpcMainEvent, lang: string) => {
|
||||
lang = lang || 'zh-CN'
|
||||
i18n.setLanguage(lang)
|
||||
})
|
||||
},
|
||||
dispose () {}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,16 @@ import {
|
||||
PICGO_OPEN_FILE,
|
||||
PASTE_TEXT,
|
||||
OPEN_WINDOW,
|
||||
DEFAULT_LOGO
|
||||
GET_LANGUAGE_LIST,
|
||||
SET_CURRENT_LANGUAGE,
|
||||
GET_CURRENT_LANGUAGE
|
||||
} from '#/events/constants'
|
||||
|
||||
import { GalleryDB } from 'apis/core/datastore'
|
||||
import { IObject, IFilter } from '@picgo/store/dist/types'
|
||||
import pasteTemplate from '../utils/pasteTemplate'
|
||||
import { T } from '~/universal/i18n'
|
||||
import { i18nManager, T } from '~/main/i18n'
|
||||
import { rpcServer } from './rpc'
|
||||
|
||||
// eslint-disable-next-line
|
||||
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
|
||||
@@ -367,10 +370,34 @@ const handleOpenWindow = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleDefaultLogo = () => {
|
||||
ipcMain.on(DEFAULT_LOGO, (event: IpcMainEvent) => {
|
||||
event.sender.send(DEFAULT_LOGO, path.join(__static, 'roundLogo.png'))
|
||||
const handleI18n = () => {
|
||||
ipcMain.on(GET_LANGUAGE_LIST, (event: IpcMainEvent) => {
|
||||
event.sender.send(GET_LANGUAGE_LIST, i18nManager.languageList)
|
||||
})
|
||||
ipcMain.on(SET_CURRENT_LANGUAGE, (event: IpcMainEvent, language: string) => {
|
||||
i18nManager.setCurrentLanguage(language)
|
||||
const { lang, locales } = i18nManager.getCurrentLocales()
|
||||
picgo.i18n.setLanguage(lang)
|
||||
if (process.platform === 'darwin') {
|
||||
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
|
||||
trayWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales)
|
||||
}
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||
settingWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales)
|
||||
if (windowManager.has(IWindowList.MINI_WINDOW)) {
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)
|
||||
miniWindow?.webContents.send(SET_CURRENT_LANGUAGE, lang, locales)
|
||||
}
|
||||
// event.sender.send(SET_CURRENT_LANGUAGE, lang, locales)
|
||||
})
|
||||
ipcMain.on(GET_CURRENT_LANGUAGE, (event: IpcMainEvent) => {
|
||||
const { lang, locales } = i18nManager.getCurrentLocales()
|
||||
event.sender.send(GET_CURRENT_LANGUAGE, lang, locales)
|
||||
})
|
||||
}
|
||||
|
||||
const handleRPCActions = () => {
|
||||
rpcServer.start()
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -386,7 +413,8 @@ export default {
|
||||
handleImportLocalPlugin()
|
||||
handleOpenFile()
|
||||
handleOpenWindow()
|
||||
handleDefaultLogo()
|
||||
handleI18n()
|
||||
handleRPCActions()
|
||||
},
|
||||
// TODO: separate to single file
|
||||
handlePluginUninstall,
|
||||
|
||||
@@ -9,10 +9,11 @@ import {
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
import pkg from 'root/package.json'
|
||||
import GuiApi from 'apis/gui'
|
||||
import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN, SHOW_MAIN_PAGE_DONATION, SHOW_MAIN_PAGE_QRCODE } from '~/universal/events/constants'
|
||||
import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_DONE, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN, SHOW_MAIN_PAGE_DONATION, SHOW_MAIN_PAGE_QRCODE } from '~/universal/events/constants'
|
||||
import picgoCoreIPC from '~/main/events/picgoCoreIPC'
|
||||
import { PicGo as PicGoCore } from 'picgo'
|
||||
import { T } from '~/universal/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
import { changeCurrentUploader } from '~/main/utils/handleUploaderConfig'
|
||||
|
||||
interface GuiMenuItem {
|
||||
label: string
|
||||
@@ -20,24 +21,7 @@ interface GuiMenuItem {
|
||||
}
|
||||
|
||||
const buildMiniPageMenu = () => {
|
||||
const picBeds = getPicBeds()
|
||||
const current = picgo.getConfig('picBed.uploader')
|
||||
const submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
return {
|
||||
label: item.name,
|
||||
type: 'radio',
|
||||
checked: current === item.type,
|
||||
click () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type,
|
||||
'picBed.uploader': item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const submenu = buildPicBedListMenu()
|
||||
const template = [
|
||||
{
|
||||
label: T('OPEN_MAIN_WINDOW'),
|
||||
@@ -111,6 +95,13 @@ const buildMainPageMenu = (win: BrowserWindow) => {
|
||||
win?.webContents?.send(SHOW_MAIN_PAGE_QRCODE)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: T('OPEN_TOOLBOX'),
|
||||
click () {
|
||||
const window = windowManager.create(IWindowList.TOOLBOX_WINDOW)
|
||||
window?.show()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: T('SHOW_DEVTOOLS'),
|
||||
click () {
|
||||
@@ -128,26 +119,58 @@ const buildMainPageMenu = (win: BrowserWindow) => {
|
||||
return Menu.buildFromTemplate(template)
|
||||
}
|
||||
|
||||
const buildUploadPageMenu = () => {
|
||||
const buildPicBedListMenu = () => {
|
||||
const picBeds = getPicBeds()
|
||||
const currentPicBed = picgo.getConfig('picBed.uploader')
|
||||
const submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
const currentPicBedName = picBeds.find(item => item.type === currentPicBed)?.name
|
||||
const picBedConfigList = picgo.getConfig<IUploaderConfig>('uploader')
|
||||
const currentPicBedMenuItem = [{
|
||||
label: `${T('CURRENT_PICBED')} - ${currentPicBedName}`,
|
||||
enabled: false
|
||||
}, {
|
||||
type: 'separator'
|
||||
}]
|
||||
let submenu = picBeds.filter(item => item.visible).map(item => {
|
||||
const configList = picBedConfigList?.[item.type]?.configList
|
||||
const defaultId = picBedConfigList?.[item.type]?.defaultId
|
||||
const hasSubmenu = !!configList
|
||||
return {
|
||||
label: item.name,
|
||||
type: 'radio',
|
||||
checked: currentPicBed === item.type,
|
||||
click () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type,
|
||||
'picBed.uploader': item.type
|
||||
type: !hasSubmenu ? 'checkbox' : undefined,
|
||||
checked: !hasSubmenu ? (currentPicBed === item.type) : undefined,
|
||||
submenu: hasSubmenu
|
||||
? configList.map((config) => {
|
||||
return {
|
||||
label: config._configName || 'Default',
|
||||
// if only one config, use checkbox, or radio will checked as default
|
||||
// see: https://github.com/electron/electron/issues/21292
|
||||
type: 'checkbox',
|
||||
checked: config._id === defaultId && (item.type === currentPicBed),
|
||||
click: function () {
|
||||
changeCurrentUploader(item.type, config, config._id)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
: undefined,
|
||||
click: !hasSubmenu
|
||||
? function () {
|
||||
picgo.saveConfig({
|
||||
'picBed.current': item.type,
|
||||
'picBed.uploader': item.type
|
||||
})
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
|
||||
}
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
submenu = currentPicBedMenuItem.concat(submenu)
|
||||
// @ts-ignore
|
||||
return Menu.buildFromTemplate(submenu)
|
||||
}
|
||||
|
||||
@@ -194,6 +217,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
|
||||
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, false)
|
||||
window.webContents.send(PICGO_HANDLE_PLUGIN_DONE, plugin.fullName)
|
||||
if (plugin.config.transformer.name) {
|
||||
handleRestoreState('transformer', plugin.config.transformer.name)
|
||||
}
|
||||
@@ -289,6 +313,6 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
|
||||
export {
|
||||
buildMiniPageMenu,
|
||||
buildMainPageMenu,
|
||||
buildUploadPageMenu,
|
||||
buildPicBedListMenu,
|
||||
buildPluginPageMenu
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ipcMain, IpcMainEvent } from 'electron'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { RPC_ACTIONS } from '#/events/constants'
|
||||
import { configRouter } from './routes/config'
|
||||
import { versionRouter } from './routes/version'
|
||||
import { toolboxRouter } from './routes/toolbox'
|
||||
import { systemRouter } from './routes/system'
|
||||
|
||||
class RPCServer implements IRPCServer {
|
||||
private routes: IRPCRoutes = new Map()
|
||||
|
||||
private rpcEventHandler = async (event: IpcMainEvent, action: IRPCActionType, args: any[], callbackId: string) => {
|
||||
try {
|
||||
const handler = this.routes.get(action)
|
||||
if (!handler) {
|
||||
return this.sendBack(event, action, null, callbackId)
|
||||
}
|
||||
const res = await handler?.(args, event)
|
||||
this.sendBack(event, action, res, callbackId)
|
||||
} catch (e) {
|
||||
this.sendBack(event, action, null, callbackId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if sendback data is null, then it means that the action is not supported or error occurs
|
||||
* if there is no callbackId, then do not send back
|
||||
*/
|
||||
private sendBack (event: IpcMainEvent, action: IRPCActionType, data: any, callbackId: string) {
|
||||
if (callbackId) {
|
||||
event.sender.send(RPC_ACTIONS, data, action, callbackId)
|
||||
}
|
||||
}
|
||||
|
||||
start () {
|
||||
ipcMain.on(RPC_ACTIONS, this.rpcEventHandler)
|
||||
}
|
||||
|
||||
use (routes: IRPCRoutes) {
|
||||
for (const [action, handler] of routes) {
|
||||
this.routes.set(action, handler)
|
||||
}
|
||||
}
|
||||
|
||||
stop () {
|
||||
ipcMain.off(RPC_ACTIONS, this.rpcEventHandler)
|
||||
}
|
||||
}
|
||||
|
||||
const rpcServer = new RPCServer()
|
||||
|
||||
rpcServer.use(configRouter.routes())
|
||||
rpcServer.use(versionRouter.routes())
|
||||
rpcServer.use(toolboxRouter.routes())
|
||||
rpcServer.use(systemRouter.routes())
|
||||
|
||||
export {
|
||||
rpcServer
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
|
||||
export class RPCRouter implements IRPCRouter {
|
||||
private routeMap: IRPCRoutes = new Map()
|
||||
add = <T>(action: IRPCActionType, handler: IRPCHandler<T>) => {
|
||||
this.routeMap.set(action, handler)
|
||||
return this
|
||||
}
|
||||
|
||||
routes () {
|
||||
return this.routeMap
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { RPCRouter } from '../router'
|
||||
import { deleteUploaderConfig, getUploaderConfigList, selectUploaderConfig, updateUploaderConfig } from '~/main/utils/handleUploaderConfig'
|
||||
|
||||
const configRouter = new RPCRouter()
|
||||
|
||||
configRouter
|
||||
.add(IRPCActionType.GET_PICBED_CONFIG_LIST, async (args) => {
|
||||
const [type] = args as IGetUploaderConfigListArgs
|
||||
const config = getUploaderConfigList(type)
|
||||
return config
|
||||
})
|
||||
.add(IRPCActionType.DELETE_PICBED_CONFIG, async (args) => {
|
||||
const [type, id] = args as IDeleteUploaderConfigArgs
|
||||
const config = deleteUploaderConfig(type, id)
|
||||
return config
|
||||
})
|
||||
.add(IRPCActionType.SELECT_UPLOADER, async (args) => {
|
||||
const [type, id] = args as ISelectUploaderConfigArgs
|
||||
selectUploaderConfig(type, id)
|
||||
return true
|
||||
})
|
||||
.add(IRPCActionType.UPDATE_UPLOADER_CONFIG, async (args) => {
|
||||
const [type, id, config] = args as IUpdateUploaderConfigArgs
|
||||
updateUploaderConfig(type, id, config)
|
||||
return true
|
||||
})
|
||||
|
||||
export {
|
||||
configRouter
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { IRPCActionType, IWindowList } from '~/universal/types/enum'
|
||||
import { RPCRouter } from '../router'
|
||||
import { app, clipboard, shell } from 'electron'
|
||||
import windowManager from '~/main/apis/app/window/windowManager'
|
||||
|
||||
const systemRouter = new RPCRouter()
|
||||
|
||||
systemRouter
|
||||
.add(IRPCActionType.RELOAD_APP, async () => {
|
||||
app.relaunch()
|
||||
app.exit(0)
|
||||
})
|
||||
.add(IRPCActionType.OPEN_FILE, async (args) => {
|
||||
const [filePath] = args as IOpenFileArgs
|
||||
shell.openPath(filePath)
|
||||
})
|
||||
.add(IRPCActionType.COPY_TEXT, async (args) => {
|
||||
const [text] = args as ICopyTextArgs
|
||||
return clipboard.writeText(text)
|
||||
})
|
||||
.add(IRPCActionType.SHOW_DOCK_ICON, async (args) => {
|
||||
const [visible] = args as IShowDockIconArgs
|
||||
app.dock[visible ? 'show' : 'hide']()
|
||||
const win = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||
if (!visible) {
|
||||
win?.show()
|
||||
win?.focus()
|
||||
win?.setSkipTaskbar(true)
|
||||
} else {
|
||||
win?.setSkipTaskbar(false)
|
||||
}
|
||||
})
|
||||
|
||||
export {
|
||||
systemRouter
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { dbPathChecker, defaultConfigPath } from '~/main/apis/core/datastore/dbChecker'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
|
||||
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
import { sendToolboxResWithType } from './utils'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD)
|
||||
|
||||
const defaultClipboardImagePath = path.join(defaultConfigPath, CLIPBOARD_IMAGE_FOLDER)
|
||||
|
||||
export const checkClipboardUploadMap: IToolboxCheckerMap<
|
||||
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
|
||||
> = {
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async (event) => {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.LOADING
|
||||
})
|
||||
const configFilePath = dbPathChecker()
|
||||
if (fs.existsSync(configFilePath)) {
|
||||
const dirPath = path.dirname(configFilePath)
|
||||
const clipboardImagePath = path.join(dirPath, CLIPBOARD_IMAGE_FOLDER)
|
||||
if (fs.existsSync(clipboardImagePath)) {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_TIPS', {
|
||||
path: clipboardImagePath
|
||||
}),
|
||||
value: clipboardImagePath
|
||||
})
|
||||
} else {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS', {
|
||||
path: clipboardImagePath
|
||||
}),
|
||||
value: path.dirname(clipboardImagePath)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS', {
|
||||
path: defaultClipboardImagePath
|
||||
}),
|
||||
value: path.dirname(defaultClipboardImagePath)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fixClipboardUploadMap: IToolboxFixMap<
|
||||
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
|
||||
> = {
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async () => {
|
||||
const configFilePath = dbPathChecker()
|
||||
const dirPath = path.dirname(configFilePath)
|
||||
const clipboardImagePath = path.join(dirPath, CLIPBOARD_IMAGE_FOLDER)
|
||||
try {
|
||||
fs.mkdirsSync(clipboardImagePath)
|
||||
return {
|
||||
type: IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD,
|
||||
status: IToolboxItemCheckStatus.SUCCESS
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
type: IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD,
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: T('TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS', {
|
||||
path: clipboardImagePath
|
||||
}),
|
||||
value: path.dirname(clipboardImagePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import fs from 'fs-extra'
|
||||
import { IpcMainEvent } from 'electron'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
|
||||
import { sendToolboxResWithType } from './utils'
|
||||
import { dbPathChecker } from '~/main/apis/core/datastore/dbChecker'
|
||||
import { GalleryDB, DB_PATH } from '~/main/apis/core/datastore'
|
||||
import path from 'path'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
export const checkFileMap: IToolboxCheckerMap<
|
||||
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
|
||||
> = {
|
||||
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async (event: IpcMainEvent) => {
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_CONFIG_FILE_BROKEN)
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.LOADING
|
||||
})
|
||||
const configFilePath = dbPathChecker()
|
||||
try {
|
||||
if (fs.existsSync(configFilePath)) {
|
||||
await fs.readJSON(configFilePath)
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: T('TOOLBOX_CHECK_CONFIG_FILE_PATH_TIPS', {
|
||||
path: configFilePath
|
||||
}),
|
||||
value: configFilePath
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: T('TOOLBOX_CHECK_CONFIG_FILE_BROKEN_TIPS'),
|
||||
value: path.dirname(configFilePath)
|
||||
})
|
||||
}
|
||||
},
|
||||
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async (event) => {
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_GALLERY_FILE_BROKEN)
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.LOADING
|
||||
})
|
||||
const galleryDB = GalleryDB.getInstance()
|
||||
if (galleryDB.errorList.length === 0) {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: T('TOOLBOX_CHECK_GALLERY_FILE_PATH_TIPS', {
|
||||
path: DB_PATH
|
||||
}),
|
||||
value: path.dirname(DB_PATH)
|
||||
})
|
||||
} else {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: T('TOOLBOX_CHECK_GALLERY_FILE_BROKEN_TIPS'),
|
||||
value: path.dirname(DB_PATH)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fixFileMap: IToolboxFixMap<
|
||||
IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
|
||||
> = {
|
||||
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: async () => {
|
||||
try {
|
||||
fs.unlinkSync(dbPathChecker())
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
return {
|
||||
type: IToolboxItemType.IS_CONFIG_FILE_BROKEN,
|
||||
status: IToolboxItemCheckStatus.SUCCESS
|
||||
}
|
||||
},
|
||||
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async () => {
|
||||
try {
|
||||
fs.unlinkSync(DB_PATH)
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
return {
|
||||
type: IToolboxItemType.IS_GALLERY_FILE_BROKEN,
|
||||
status: IToolboxItemCheckStatus.SUCCESS
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import fs from 'fs-extra'
|
||||
import { IToolboxItemCheckStatus, IToolboxItemType } from '~/universal/types/enum'
|
||||
import { sendToolboxResWithType } from './utils'
|
||||
import tunnel from 'tunnel'
|
||||
import { dbPathChecker } from '~/main/apis/core/datastore/dbChecker'
|
||||
import { IConfig } from 'picgo'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
const getProxy = (proxyStr: string): AxiosRequestConfig['proxy'] | false => {
|
||||
if (proxyStr) {
|
||||
try {
|
||||
const proxyOptions = new URL(proxyStr)
|
||||
return {
|
||||
host: proxyOptions.hostname,
|
||||
port: parseInt(proxyOptions.port || '0', 10),
|
||||
protocol: proxyOptions.protocol
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
|
||||
|
||||
export const checkProxyMap: IToolboxCheckerMap<
|
||||
IToolboxItemType.HAS_PROBLEM_WITH_PROXY
|
||||
> = {
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async (event) => {
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.LOADING
|
||||
})
|
||||
const configFilePath = dbPathChecker()
|
||||
if (fs.existsSync(configFilePath)) {
|
||||
let config: IConfig | undefined
|
||||
try {
|
||||
config = await fs.readJSON(configFilePath) as IConfig
|
||||
} catch (e) {
|
||||
}
|
||||
if (!config) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: T('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
|
||||
})
|
||||
}
|
||||
|
||||
const proxy = config.picBed?.proxy
|
||||
if (!proxy) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: T('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
|
||||
})
|
||||
} else {
|
||||
const proxyOptions = getProxy(proxy)
|
||||
if (!proxyOptions) {
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: T('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_CORRECT')
|
||||
})
|
||||
} else {
|
||||
const httpsAgent = tunnel.httpsOverHttp({
|
||||
proxy: {
|
||||
host: proxyOptions.host,
|
||||
port: proxyOptions.port
|
||||
}
|
||||
})
|
||||
try {
|
||||
await axios.get('https://www.google.com', {
|
||||
httpsAgent
|
||||
})
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: T('TOOLBOX_CHECK_PROXY_SUCCESS_TIPS')
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.ERROR,
|
||||
msg: T('TOOLBOX_CHECK_PROXY_PROXY_IS_NOT_WORKING')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendToolboxRes(event, {
|
||||
status: IToolboxItemCheckStatus.SUCCESS,
|
||||
msg: T('TOOLBOX_CHECK_PROXY_NO_PROXY_TIPS')
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { IRPCActionType, IToolboxItemType } from '~/universal/types/enum'
|
||||
import { RPCRouter } from '../../router'
|
||||
import { checkFileMap, fixFileMap } from './checkFile'
|
||||
import { checkClipboardUploadMap, fixClipboardUploadMap } from './checkClipboardUpload'
|
||||
import { checkProxyMap } from './checkProxy'
|
||||
|
||||
const toolboxRouter = new RPCRouter()
|
||||
|
||||
const toolboxCheckMap: Partial<IToolboxCheckerMap<IToolboxItemType>> = {
|
||||
...checkFileMap,
|
||||
...checkClipboardUploadMap,
|
||||
...checkProxyMap
|
||||
}
|
||||
|
||||
const toolboxFixMap: Partial<IToolboxFixMap<IToolboxItemType>> = {
|
||||
...fixFileMap,
|
||||
...fixClipboardUploadMap
|
||||
}
|
||||
|
||||
toolboxRouter
|
||||
.add(IRPCActionType.TOOLBOX_CHECK, async (args, event) => {
|
||||
const [type] = args as IToolboxCheckArgs
|
||||
if (type) {
|
||||
const handler = toolboxCheckMap[type]
|
||||
if (handler) {
|
||||
handler(event)
|
||||
}
|
||||
} else {
|
||||
// do check all
|
||||
for (const key in toolboxCheckMap) {
|
||||
const handler = toolboxCheckMap[key as IToolboxItemType]
|
||||
if (handler) {
|
||||
handler(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.add(IRPCActionType.TOOLBOX_CHECK_FIX, async (args, event) => {
|
||||
const [type] = args as IToolboxCheckArgs
|
||||
const handler = toolboxFixMap[type]
|
||||
if (handler) {
|
||||
return await handler(event)
|
||||
}
|
||||
})
|
||||
|
||||
export {
|
||||
toolboxRouter
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { IpcMainEvent } from 'electron'
|
||||
import { IRPCActionType, IToolboxItemType } from '~/universal/types/enum'
|
||||
|
||||
export const sendToolboxResWithType = (type: IToolboxItemType) => (event: IpcMainEvent, res?: Omit<IToolboxCheckRes, 'type'>) => {
|
||||
return event.sender.send(IRPCActionType.TOOLBOX_CHECK_RES, {
|
||||
...res,
|
||||
type
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { RPCRouter } from '../router'
|
||||
import { getLatestVersion } from '~/main/utils/getLatestVersion'
|
||||
|
||||
const versionRouter = new RPCRouter()
|
||||
|
||||
versionRouter
|
||||
.add(IRPCActionType.GET_LATEST_VERSION, async (args) => {
|
||||
const [type] = args as IGetLatestVersionArgs
|
||||
const config = await getLatestVersion(type)
|
||||
return config
|
||||
})
|
||||
|
||||
export {
|
||||
versionRouter
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import yaml from 'js-yaml'
|
||||
import { ObjectAdapter, I18n } from '@picgo/i18n'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { builtinI18nList } from '#/i18n'
|
||||
|
||||
class I18nManager {
|
||||
private i18n: I18n | null = null
|
||||
private builtinI18nFolder = path.join(__static, 'i18n')
|
||||
private outterI18nFolder = ''
|
||||
private localesMap: Map<string, ILocales> = new Map()
|
||||
private currentLanguage: string = 'zh-CN'
|
||||
readonly defaultLanguage: string = 'zh-CN'
|
||||
private i18nFileList: II18nItem[] = builtinI18nList
|
||||
|
||||
setOutterI18nFolder (folder: string) {
|
||||
this.outterI18nFolder = folder
|
||||
}
|
||||
|
||||
addI18nFile (file: string, label: string) {
|
||||
this.i18nFileList.push({
|
||||
label,
|
||||
value: file
|
||||
})
|
||||
}
|
||||
|
||||
private getLocales (lang: string): ILocales {
|
||||
if (this.localesMap.has(lang)) {
|
||||
return this.localesMap.get(lang)!
|
||||
}
|
||||
let localesPath = path.join(this.builtinI18nFolder, `${lang}.yml`)
|
||||
if (!fs.existsSync(localesPath)) {
|
||||
localesPath = path.join(this.outterI18nFolder, `${lang}.yml`)
|
||||
if (!fs.existsSync(localesPath)) {
|
||||
localesPath = path.join(this.builtinI18nFolder, `${this.defaultLanguage}.yml`)
|
||||
}
|
||||
}
|
||||
try {
|
||||
const localesString = fs.readFileSync(localesPath, 'utf8')
|
||||
const locales = yaml.load(localesString) as unknown as ILocales
|
||||
this.localesMap.set(lang, locales)
|
||||
return locales
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// if error, use default language
|
||||
localesPath = path.join(this.builtinI18nFolder, `${this.defaultLanguage}.yml`)
|
||||
const localesString = fs.readFileSync(localesPath, 'utf8')
|
||||
const locales = yaml.load(localesString) as unknown as ILocales
|
||||
this.localesMap.set(lang, locales)
|
||||
return locales
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentLanguage (lang: string) {
|
||||
const locales = this.getLocales(lang)
|
||||
this.currentLanguage = lang
|
||||
this.initI18n(lang, locales)
|
||||
}
|
||||
|
||||
private initI18n (lang: string = this.defaultLanguage, locales: ILocales) {
|
||||
const objectAdapter = new ObjectAdapter({
|
||||
[lang]: locales
|
||||
})
|
||||
this.i18n = new I18n({
|
||||
adapter: objectAdapter,
|
||||
defaultLanguage: lang
|
||||
})
|
||||
}
|
||||
|
||||
T (key: ILocalesKey, args: IStringKeyMap = {}): string {
|
||||
return this.i18n?.translate(key, args) || key
|
||||
}
|
||||
|
||||
get languageList () {
|
||||
return this.i18nFileList
|
||||
}
|
||||
|
||||
getCurrentLocales () {
|
||||
return {
|
||||
lang: this.currentLanguage,
|
||||
locales: this.getLocales(this.currentLanguage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const T = (key: ILocalesKey, args: IStringKeyMap = {}): string => {
|
||||
return i18nManager.T(key, args)
|
||||
}
|
||||
|
||||
export const i18nManager = new I18nManager()
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'path'
|
||||
import { dbPathDir } from 'apis/core/datastore/dbChecker'
|
||||
import { app } from 'electron'
|
||||
import { getLogger } from 'apis/core/utils/localLogger'
|
||||
const STORE_PATH = dbPathDir()
|
||||
const STORE_PATH = app.getPath('userData')
|
||||
const LOG_PATH = path.join(STORE_PATH, 'picgo-gui-local.log')
|
||||
|
||||
const logger = getLogger(LOG_PATH)
|
||||
@@ -9,8 +9,7 @@ const logger = getLogger(LOG_PATH)
|
||||
// since the error may occur in picgo-core
|
||||
// so we can't use the log from picgo
|
||||
|
||||
const handleProcessError = (error: Error) => {
|
||||
console.error(error)
|
||||
const handleProcessError = (error: Error | string) => {
|
||||
logger('error', error)
|
||||
}
|
||||
|
||||
@@ -21,3 +20,21 @@ process.on('uncaughtException', error => {
|
||||
process.on('unhandledRejection', (error: any) => {
|
||||
handleProcessError(error)
|
||||
})
|
||||
|
||||
// thanks to https://github.com/camunda/camunda-modeler/pull/3314
|
||||
function bootstrapEPIPESuppression () {
|
||||
let suppressing = false
|
||||
function logEPIPEErrorOnce () {
|
||||
if (suppressing) {
|
||||
return
|
||||
}
|
||||
|
||||
suppressing = true
|
||||
handleProcessError('Detected EPIPE error; suppressing further EPIPE errors')
|
||||
}
|
||||
|
||||
require('epipebomb')(process.stdout, logEPIPEErrorOnce)
|
||||
require('epipebomb')(process.stderr, logEPIPEErrorOnce)
|
||||
}
|
||||
|
||||
bootstrapEPIPESuppression()
|
||||
|
||||
@@ -12,7 +12,7 @@ import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
import beforeOpen from '~/main/utils/beforeOpen'
|
||||
import ipcList from '~/main/events/ipcList'
|
||||
import busEventList from '~/main/events/busEventList'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import {
|
||||
updateShortKeyFromVersion212,
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
uploadClipboardFiles
|
||||
} from 'apis/app/uploader/apis'
|
||||
import {
|
||||
createTray
|
||||
createTray, handleDockIcon
|
||||
} from 'apis/app/system'
|
||||
import server from '~/main/server/index'
|
||||
import updateChecker from '~/main/utils/updateChecker'
|
||||
@@ -35,6 +35,8 @@ import logger from 'apis/core/picgo/logger'
|
||||
import picgo from 'apis/core/picgo'
|
||||
import fixPath from './fixPath'
|
||||
import { initI18n } from '~/main/utils/handleI18n'
|
||||
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
|
||||
import { isMacOS } from '../utils/getMacOSVersion'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
@@ -83,6 +85,7 @@ class LifeCycle {
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
createTray()
|
||||
handleDockIcon()
|
||||
db.set('needReload', false)
|
||||
updateChecker()
|
||||
// 不需要阻塞
|
||||
@@ -101,12 +104,10 @@ class LifeCycle {
|
||||
notice.show()
|
||||
}
|
||||
}
|
||||
await remoteNoticeHandler.init()
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
||||
}
|
||||
if (!app.isReady()) {
|
||||
app.on('ready', readyFunction)
|
||||
} else {
|
||||
readyFunction()
|
||||
}
|
||||
app.whenReady().then(readyFunction)
|
||||
}
|
||||
|
||||
private onRunning () {
|
||||
@@ -131,6 +132,13 @@ class LifeCycle {
|
||||
if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
}
|
||||
// click dock to open setting window
|
||||
if (isMacOS) {
|
||||
handleDockIcon()
|
||||
if (db.get('settings.showDockIcon') !== false) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)?.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: db.get('settings.autoStart') || false
|
||||
|
||||
@@ -3,7 +3,7 @@ import ConfigStore from '~/main/apis/core/datastore'
|
||||
import path from 'path'
|
||||
import fse from 'fs-extra'
|
||||
import { PicGo as PicGoCore } from 'picgo'
|
||||
import { T } from '~/universal/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
// from v2.1.2
|
||||
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
|
||||
// #557 极端情况可能会出现配置不存在,需要重新写入
|
||||
|
||||
@@ -45,7 +45,7 @@ class Server {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (request.method === 'POST') {
|
||||
if (!routers.getHandler(request.url!)) {
|
||||
logger.warn(`[PicGo Server] don't support [${request.url}] url`)
|
||||
|
||||
@@ -2,6 +2,9 @@ import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
|
||||
import yaml from 'js-yaml'
|
||||
import { i18nManager } from '~/main/i18n'
|
||||
// import { ILocales } from '~/universal/types/i18n'
|
||||
|
||||
const configPath = dbPathChecker()
|
||||
const CONFIG_DIR = path.dirname(configPath)
|
||||
@@ -11,13 +14,14 @@ function beforeOpen () {
|
||||
resolveMacWorkFlow()
|
||||
}
|
||||
resolveClipboardImageGenerator()
|
||||
resolveOtherI18nFiles()
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS 右键菜单
|
||||
*/
|
||||
function resolveMacWorkFlow () {
|
||||
const dest = `${os.homedir}/Library/Services/Upload pictures with PicGo.workflow`
|
||||
const dest = `${os.homedir()}/Library/Services/Upload pictures with PicGo.workflow`
|
||||
if (fs.existsSync(dest)) {
|
||||
return true
|
||||
} else {
|
||||
@@ -29,6 +33,20 @@ function resolveMacWorkFlow () {
|
||||
}
|
||||
}
|
||||
|
||||
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
|
||||
try {
|
||||
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
|
||||
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
|
||||
|
||||
if (!file1 || !file2 || !file1.equals(file2)) {
|
||||
fs.copyFileSync(filePath1, filePath2)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
fs.copyFileSync(filePath1, filePath2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化剪贴板生成图片的脚本
|
||||
*/
|
||||
@@ -44,20 +62,6 @@ function resolveClipboardImageGenerator () {
|
||||
})
|
||||
}
|
||||
|
||||
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
|
||||
try {
|
||||
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
|
||||
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
|
||||
|
||||
if (!file1 || !file2 || !file1.equals(file2)) {
|
||||
fs.copyFileSync(filePath1, filePath2)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
fs.copyFileSync(filePath1, filePath2)
|
||||
}
|
||||
}
|
||||
|
||||
function getClipboardFiles () {
|
||||
const files = [
|
||||
'/linux.sh',
|
||||
@@ -76,4 +80,32 @@ function resolveClipboardImageGenerator () {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化其他语言文件
|
||||
*/
|
||||
function resolveOtherI18nFiles () {
|
||||
const i18nFolder = path.join(CONFIG_DIR, 'i18n')
|
||||
if (!fs.pathExistsSync(i18nFolder)) {
|
||||
fs.mkdirSync(i18nFolder)
|
||||
}
|
||||
i18nManager.setOutterI18nFolder(i18nFolder)
|
||||
const i18nFiles = fs.readdirSync(path.join(CONFIG_DIR, 'i18n'), {
|
||||
withFileTypes: true
|
||||
})
|
||||
i18nFiles.forEach(item => {
|
||||
if (item.isFile() && item.name?.endsWith('.yml')) {
|
||||
const i18nFilePath = path.join(i18nFolder, item.name)
|
||||
const i18nFile = fs.readFileSync(i18nFilePath, 'utf8')
|
||||
try {
|
||||
const i18nFileObj = yaml.load(i18nFile) as unknown as ILocales
|
||||
if (i18nFileObj?.LANG_DISPLAY_LABEL) {
|
||||
i18nManager.addI18nFile(item.name.replace('.yml', ''), i18nFileObj.LANG_DISPLAY_LABEL)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default beforeOpen
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import fs from 'fs-extra'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { clipboard, Notification, dialog } from 'electron'
|
||||
|
||||
@@ -14,16 +15,21 @@ export const handleCopyUrl = (str: string): void => {
|
||||
export const showNotification = (options: IPrivateShowNotificationOption = {
|
||||
title: '',
|
||||
body: '',
|
||||
clickToCopy: false
|
||||
clickToCopy: false,
|
||||
copyContent: '',
|
||||
clickFn: () => {}
|
||||
}) => {
|
||||
const notification = new Notification({
|
||||
title: options.title,
|
||||
body: options.body,
|
||||
icon: options.icon || undefined
|
||||
body: options.body
|
||||
// icon: options.icon || undefined
|
||||
})
|
||||
const handleClick = () => {
|
||||
if (options.clickToCopy) {
|
||||
clipboard.writeText(options.body)
|
||||
clipboard.writeText(options.copyContent || options.body)
|
||||
}
|
||||
if (options.clickFn) {
|
||||
options.clickFn()
|
||||
}
|
||||
}
|
||||
notification.once('click', handleClick)
|
||||
@@ -69,3 +75,45 @@ export const calcDurationRange = (duration: number) => {
|
||||
// max range
|
||||
return 100000
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS public.file-url will get encoded file path,
|
||||
* so we need to decode it
|
||||
*/
|
||||
export const ensureFilePath = (filePath: string, prefix = 'file://'): string => {
|
||||
filePath = filePath.replace(prefix, '')
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
filePath = decodeURIComponent(filePath)
|
||||
if (fs.existsSync(filePath)) {
|
||||
return `${prefix}${filePath}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* for builtin clipboard to get image path from clipboard
|
||||
* @returns
|
||||
*/
|
||||
export const getClipboardFilePath = (): string => {
|
||||
// TODO: linux support
|
||||
const img = clipboard.readImage()
|
||||
if (img.isEmpty()) {
|
||||
if (process.platform === 'win32') {
|
||||
const imgPath = clipboard.readBuffer('FileNameW')?.toString('ucs2')?.replace(RegExp(String.fromCharCode(0), 'g'), '')
|
||||
if (imgPath) {
|
||||
return imgPath
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (process.platform === 'darwin') {
|
||||
let imgPath = clipboard.read('public.file-url') // will get file://xxx/xxx
|
||||
imgPath = ensureFilePath(imgPath)
|
||||
if (imgPath) {
|
||||
return imgPath.replace('file://', '')
|
||||
}
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
// for referer policy, we can't use it in renderer
|
||||
import axios from 'axios'
|
||||
import { RELEASE_URL, RELEASE_URL_BACKUP } from './static'
|
||||
import { RELEASE_URL, RELEASE_URL_BACKUP } from '../../universal/utils/static'
|
||||
import yaml from 'js-yaml'
|
||||
|
||||
export const getLatestVersion = async (isCheckBetaUpdate: boolean = false) => {
|
||||
let res: string = ''
|
||||
try {
|
||||
res = await axios.get(RELEASE_URL).then(r => {
|
||||
res = await axios.get(RELEASE_URL, {
|
||||
headers: {
|
||||
Referer: 'https://github.com'
|
||||
}
|
||||
}).then(r => {
|
||||
const list = r.data as IStringKeyMap[]
|
||||
if (isCheckBetaUpdate) {
|
||||
const betaList = list.filter(item => item.name.includes('beta'))
|
||||
@@ -14,7 +19,11 @@ export const getLatestVersion = async (isCheckBetaUpdate: boolean = false) => {
|
||||
const normalList = list.filter(item => !item.name.includes('beta'))
|
||||
return normalList[0].name
|
||||
}).catch(async () => {
|
||||
const result = await axios.get(isCheckBetaUpdate ? `${RELEASE_URL_BACKUP}/latest.beta.yml` : `${RELEASE_URL_BACKUP}/latest.yml`)
|
||||
const result = await axios.get(isCheckBetaUpdate ? `${RELEASE_URL_BACKUP}/latest.beta.yml` : `${RELEASE_URL_BACKUP}/latest.yml`, {
|
||||
headers: {
|
||||
Referer: 'https://github.com'
|
||||
}
|
||||
})
|
||||
const r = yaml.load(result.data) as IStringKeyMap
|
||||
return r.version
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { Logger } from 'picgo'
|
||||
import { isUrl } from '~/universal/utils/common'
|
||||
interface IResultFileObject {
|
||||
path: string
|
||||
}
|
||||
@@ -34,6 +35,11 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger
|
||||
return null // for uploading images in clipboard
|
||||
} else if ((fileList?.length || 0) > 0) {
|
||||
const result = fileList!.map(item => {
|
||||
if (isUrl(item)) {
|
||||
return {
|
||||
path: item
|
||||
}
|
||||
}
|
||||
if (path.isAbsolute(item)) {
|
||||
return {
|
||||
path: item
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { i18n } from '#/i18n'
|
||||
import { i18nManager } from '~/main/i18n'
|
||||
export const initI18n = () => {
|
||||
const currentLanguage = db.get('settings.language') || 'zh-CN'
|
||||
i18n.setLanguage(currentLanguage)
|
||||
i18nManager.setCurrentLanguage(currentLanguage)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import { uuid } from 'uuidv4'
|
||||
import { trimValues } from '#/utils/common'
|
||||
import picgo from '@core/picgo'
|
||||
|
||||
export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IPicGoPluginConfig[] => {
|
||||
for (const i in config) {
|
||||
if (typeof config[i].default === 'function') {
|
||||
config[i].default = config[i].default()
|
||||
}
|
||||
if (typeof config[i].choices === 'function') {
|
||||
config[i].choices = (config[i].choices as Function)()
|
||||
}
|
||||
}
|
||||
return config as IPicGoPluginConfig[]
|
||||
}
|
||||
|
||||
export const completeUploaderMetaConfig = (originData: IStringKeyMap): IUploaderConfigListItem => {
|
||||
return Object.assign({
|
||||
_configName: 'Default'
|
||||
}, trimValues(originData), {
|
||||
_id: uuid(),
|
||||
_createdAt: Date.now(),
|
||||
_updatedAt: Date.now()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* get picbed config by type
|
||||
* it will trigger the uploader config function & get the uploader config result
|
||||
* & not just read from
|
||||
*/
|
||||
export const getPicBedConfig = (type: string) => {
|
||||
const name = picgo.helper.uploader.get(type)?.name || type
|
||||
if (picgo.helper.uploader.get(type)?.config) {
|
||||
const _config = picgo.helper.uploader.get(type)!.config!(picgo)
|
||||
const config = handleConfigWithFunction(_config)
|
||||
return {
|
||||
config,
|
||||
name
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
config: [],
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const changeCurrentUploader = (type: string, config?: IStringKeyMap, id?: string) => {
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
if (id) {
|
||||
picgo.saveConfig({
|
||||
[`uploader.${type}.defaultId`]: id
|
||||
})
|
||||
}
|
||||
if (config) {
|
||||
picgo.saveConfig({
|
||||
[`picBed.${type}`]: config
|
||||
})
|
||||
}
|
||||
picgo.saveConfig({
|
||||
'picBed.current': type,
|
||||
'picBed.uploader': type
|
||||
})
|
||||
}
|
||||
|
||||
export const selectUploaderConfig = (type: string, id: string) => {
|
||||
const { configList } = getUploaderConfigList(type)
|
||||
const config = configList.find((item: IStringKeyMap) => item._id === id)
|
||||
if (config) {
|
||||
picgo.saveConfig({
|
||||
[`uploader.${type}.defaultId`]: id,
|
||||
[`picBed.${type}`]: config
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const getUploaderConfigList = (type: string): IUploaderConfigItem => {
|
||||
if (!type) {
|
||||
return {
|
||||
configList: [],
|
||||
defaultId: ''
|
||||
}
|
||||
}
|
||||
const currentUploaderConfig = picgo.getConfig<IStringKeyMap>(`uploader.${type}`) ?? {}
|
||||
let configList = currentUploaderConfig.configList
|
||||
let defaultId = currentUploaderConfig.defaultId || ''
|
||||
if (!configList) {
|
||||
const res = upgradeUploaderConfig(type)
|
||||
configList = res.configList
|
||||
defaultId = res.defaultId
|
||||
}
|
||||
return {
|
||||
configList,
|
||||
defaultId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete uploader config by type & id
|
||||
*/
|
||||
export const deleteUploaderConfig = (type: string, id: string): IUploaderConfigItem | void => {
|
||||
const { configList, defaultId } = getUploaderConfigList(type)
|
||||
if (configList.length <= 1) {
|
||||
return
|
||||
}
|
||||
let newDefaultId = defaultId
|
||||
const updatedConfigList = configList.filter((item: IStringKeyMap) => item._id !== id)
|
||||
if (id === defaultId) {
|
||||
newDefaultId = updatedConfigList[0]._id
|
||||
changeCurrentUploader(type, updatedConfigList[0], updatedConfigList[0]._id)
|
||||
}
|
||||
picgo.saveConfig({
|
||||
[`uploader.${type}.configList`]: updatedConfigList
|
||||
})
|
||||
return {
|
||||
configList: updatedConfigList,
|
||||
defaultId: newDefaultId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* upgrade old uploader config to new format
|
||||
*/
|
||||
export const upgradeUploaderConfig = (type: string): {
|
||||
configList: IStringKeyMap[]
|
||||
defaultId: string
|
||||
} => {
|
||||
const uploaderConfig = picgo.getConfig<IStringKeyMap>(`picBed.${type}`) ?? {}
|
||||
if (!uploaderConfig._id) {
|
||||
Object.assign(uploaderConfig, completeUploaderMetaConfig(uploaderConfig))
|
||||
}
|
||||
|
||||
const uploaderConfigList = [uploaderConfig]
|
||||
picgo.saveConfig({
|
||||
[`uploader.${type}`]: {
|
||||
configList: uploaderConfigList,
|
||||
defaultId: uploaderConfig._id
|
||||
},
|
||||
[`picBed.${type}`]: uploaderConfig
|
||||
})
|
||||
return {
|
||||
configList: uploaderConfigList,
|
||||
defaultId: uploaderConfig._id
|
||||
}
|
||||
}
|
||||
|
||||
export const updateUploaderConfig = (type: string, id: string, config: IStringKeyMap) => {
|
||||
const { configList, defaultId } = getUploaderConfigList(type)
|
||||
const existConfig = configList.find((item: IStringKeyMap) => item._id === id)
|
||||
let updatedConfig: IUploaderConfigListItem
|
||||
let updatedDefaultId = defaultId
|
||||
if (existConfig) {
|
||||
updatedConfig = Object.assign(existConfig, trimValues(config), {
|
||||
_updatedAt: Date.now()
|
||||
})
|
||||
} else {
|
||||
updatedConfig = completeUploaderMetaConfig(config)
|
||||
updatedDefaultId = updatedConfig._id
|
||||
configList.push(updatedConfig)
|
||||
}
|
||||
picgo.saveConfig({
|
||||
[`uploader.${type}.configList`]: configList,
|
||||
[`uploader.${type}.defaultId`]: updatedDefaultId,
|
||||
[`picBed.${type}`]: updatedConfig
|
||||
})
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
import { IPasteStyle } from '#/types/enum'
|
||||
import db from 'apis/core/datastore'
|
||||
import { handleUrlEncode } from '~/universal/utils/common'
|
||||
|
||||
const formatCustomLink = (customLink: string, item: ImgInfo) => {
|
||||
const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
|
||||
const url = item.url || item.imgUrl
|
||||
const extName = item.extname
|
||||
const formatObj = {
|
||||
url,
|
||||
fileName
|
||||
fileName,
|
||||
extName
|
||||
}
|
||||
const keys = Object.keys(formatObj) as ['url', 'fileName']
|
||||
const keys = Object.keys(formatObj) as ['url', 'fileName', 'extName']
|
||||
keys.forEach(item => {
|
||||
if (customLink.indexOf(`$${item}`) !== -1) {
|
||||
const reg = new RegExp(`\\$${item}`, 'g')
|
||||
@@ -18,14 +22,20 @@ const formatCustomLink = (customLink: string, item: ImgInfo) => {
|
||||
}
|
||||
|
||||
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
|
||||
const url = item.url || item.imgUrl
|
||||
let url = item.url || item.imgUrl
|
||||
if (db.get('settings.encodeOutputURL') !== false) {
|
||||
url = handleUrlEncode(url)
|
||||
}
|
||||
const _customLink = customLink || '$url'
|
||||
const tpl = {
|
||||
markdown: ``,
|
||||
HTML: `<img src="${url}"/>`,
|
||||
URL: url,
|
||||
UBB: `[IMG]${url}[/IMG]`,
|
||||
Custom: formatCustomLink(_customLink, item)
|
||||
Custom: formatCustomLink(_customLink, {
|
||||
...item,
|
||||
url
|
||||
})
|
||||
}
|
||||
return tpl[style]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { showMessageBox } from '~/main/utils/common'
|
||||
import { T } from '~/universal/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
|
||||
class PrivacyManager {
|
||||
async check () {
|
||||
|
||||
@@ -2,8 +2,8 @@ import { dialog, shell } from 'electron'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import pkg from 'root/package.json'
|
||||
import { lt } from 'semver'
|
||||
import { T } from '~/universal/i18n'
|
||||
import { getLatestVersion } from '#/utils/getLatestVersion'
|
||||
import { T } from '~/main/i18n'
|
||||
import { getLatestVersion } from '~/main/utils/getLatestVersion'
|
||||
const version = pkg.version
|
||||
// const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases'
|
||||
// const releaseUrlBackup = 'https://picgo-1251750343.cos.ap-chengdu.myqcloud.com'
|
||||
|
||||
@@ -1,12 +1,40 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
import { useStore } from '@/hooks/useStore'
|
||||
import { onBeforeMount, onMounted, onUnmounted } from 'vue'
|
||||
import { getConfig } from './utils/dataSender'
|
||||
import type { IConfig } from 'picgo'
|
||||
import bus from './utils/bus'
|
||||
import { FORCE_UPDATE } from '~/universal/events/constants'
|
||||
|
||||
const store = useStore()
|
||||
onBeforeMount(async () => {
|
||||
const config = await getConfig<IConfig>()
|
||||
if (config) {
|
||||
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
bus.on(FORCE_UPDATE, () => {
|
||||
store?.updateForceUpdateTime()
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
bus.off(FORCE_UPDATE)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'picgo'
|
||||
name: 'PicGoApp'
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<div id="choose-pic-bed">
|
||||
<span>{{ $T('CHOOSE_YOUR_DEFAULT_PICBED', { d: label }) }}</span>
|
||||
<el-switch
|
||||
v-model="value"
|
||||
@change="choosePicBed"
|
||||
>
|
||||
</el-switch>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from 'vue-property-decorator'
|
||||
@Component({
|
||||
name: 'choose-pic-bed'
|
||||
})
|
||||
export default class extends Vue {
|
||||
value = false
|
||||
@Prop() type!: string
|
||||
@Prop() label!: string
|
||||
async created () {
|
||||
const current = await this.getConfig<string>('picBed.current')
|
||||
if (this.type === current) {
|
||||
this.value = true
|
||||
}
|
||||
}
|
||||
|
||||
choosePicBed () {
|
||||
this.saveConfig({
|
||||
'picBed.current': this.type,
|
||||
'picBed.uploader': this.type
|
||||
})
|
||||
this.$emit('update:choosed', this.type)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -1,25 +1,40 @@
|
||||
<template>
|
||||
<div id="config-form">
|
||||
<div
|
||||
id="config-form"
|
||||
:class="props.colorMode === 'white' ? 'white' : ''"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
ref="$form"
|
||||
label-position="left"
|
||||
label-width="50%"
|
||||
:model="ruleForm"
|
||||
ref="form"
|
||||
size="mini"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('UPLOADER_CONFIG_NAME')"
|
||||
required
|
||||
prop="_configName"
|
||||
>
|
||||
<el-input
|
||||
v-model="ruleForm._configName"
|
||||
type="input"
|
||||
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- dynamic config -->
|
||||
<el-form-item
|
||||
v-for="(item, index) in configList"
|
||||
:key="item.name + index"
|
||||
:label="item.alias || item.name"
|
||||
:required="item.required"
|
||||
:prop="item.name"
|
||||
:key="item.name + index"
|
||||
>
|
||||
<el-input
|
||||
v-if="item.type === 'input' || item.type === 'password'"
|
||||
:type="item.type === 'password' ? 'password' : 'input'"
|
||||
v-model="ruleForm[item.name]"
|
||||
:type="item.type === 'password' ? 'password' : 'input'"
|
||||
:placeholder="item.message || item.name"
|
||||
></el-input>
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="item.type === 'list' && item.choices"
|
||||
v-model="ruleForm[item.name]"
|
||||
@@ -27,10 +42,10 @@
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:key="choice.name || choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
></el-option>
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="item.type === 'checkbox' && item.choices"
|
||||
@@ -41,104 +56,120 @@
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:key="choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
></el-option>
|
||||
/>
|
||||
</el-select>
|
||||
<el-switch
|
||||
v-else-if="item.type === 'confirm'"
|
||||
v-model="ruleForm[item.name]"
|
||||
active-text="yes"
|
||||
inactive-text="no"
|
||||
>
|
||||
</el-switch>
|
||||
/>
|
||||
</el-form-item>
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {
|
||||
Component,
|
||||
Vue,
|
||||
Prop,
|
||||
Watch
|
||||
} from 'vue-property-decorator'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch, toRefs } from 'vue'
|
||||
import { cloneDeep, union } from 'lodash'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
@Component({
|
||||
name: 'config-form'
|
||||
interface IProps {
|
||||
config: any[]
|
||||
type: 'uploader' | 'transformer' | 'plugin'
|
||||
id: string
|
||||
colorMode?: 'white' | 'dark'
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
const $route = useRoute()
|
||||
const $form = ref<FormInstance>()
|
||||
|
||||
const configList = ref<IPicGoPluginConfig[]>([])
|
||||
const ruleForm = reactive<IStringKeyMap>({})
|
||||
|
||||
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => {
|
||||
handleConfigChange(val)
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
export default class extends Vue {
|
||||
@Prop() private config!: any[]
|
||||
@Prop() readonly type!: 'uploader' | 'transformer' | 'plugin'
|
||||
@Prop() readonly id!: string
|
||||
configList: IPicGoPluginConfig[] = []
|
||||
ruleForm: IStringKeyMap = {}
|
||||
@Watch('config', {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
handleConfigChange (val: any) {
|
||||
this.handleConfig(val)
|
||||
}
|
||||
|
||||
async validate () {
|
||||
return new Promise((resolve) => {
|
||||
// @ts-ignore
|
||||
this.$refs.form.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(this.ruleForm)
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
function handleConfigChange (val: any) {
|
||||
handleConfig(val)
|
||||
}
|
||||
|
||||
async function validate (): Promise<IStringKeyMap | false> {
|
||||
return new Promise((resolve) => {
|
||||
$form.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(ruleForm)
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getConfigType () {
|
||||
switch (this.type) {
|
||||
case 'plugin': {
|
||||
return this.id
|
||||
}
|
||||
case 'uploader': {
|
||||
return `picBed.${this.id}`
|
||||
}
|
||||
case 'transformer': {
|
||||
return `transformer.${this.id}`
|
||||
}
|
||||
default:
|
||||
return 'unknown'
|
||||
function getConfigType () {
|
||||
switch (props.type) {
|
||||
case 'plugin': {
|
||||
return props.id
|
||||
}
|
||||
}
|
||||
|
||||
async handleConfig (val: IPicGoPluginConfig[]) {
|
||||
this.ruleForm = Object.assign({}, {})
|
||||
const config = await this.getConfig<IPicGoPluginConfig>(this.getConfigType())
|
||||
if (val.length > 0) {
|
||||
this.configList = cloneDeep(val).map((item) => {
|
||||
let defaultValue = item.default !== undefined
|
||||
? item.default
|
||||
: item.type === 'checkbox'
|
||||
? []
|
||||
: null
|
||||
if (item.type === 'checkbox') {
|
||||
const defaults = item.choices?.filter((i: any) => {
|
||||
return i.checked
|
||||
}).map((i: any) => i.value) || []
|
||||
defaultValue = union(defaultValue, defaults)
|
||||
}
|
||||
if (config && config[item.name] !== undefined) {
|
||||
defaultValue = config[item.name]
|
||||
}
|
||||
this.$set(this.ruleForm, item.name, defaultValue)
|
||||
return item
|
||||
})
|
||||
case 'uploader': {
|
||||
return `picBed.${props.id}`
|
||||
}
|
||||
case 'transformer': {
|
||||
return `transformer.${props.id}`
|
||||
}
|
||||
default:
|
||||
return 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfig (val: IPicGoPluginConfig[]) {
|
||||
const config = await getCurConfigFormData()
|
||||
const configId = $route.params.configId
|
||||
Object.assign(ruleForm, config)
|
||||
if (val.length > 0) {
|
||||
configList.value = cloneDeep(val).map((item) => {
|
||||
if (!configId) return item
|
||||
let defaultValue = item.default !== undefined
|
||||
? item.default
|
||||
: item.type === 'checkbox'
|
||||
? []
|
||||
: null
|
||||
if (item.type === 'checkbox') {
|
||||
const defaults = item.choices?.filter((i: any) => {
|
||||
return i.checked
|
||||
}).map((i: any) => i.value) || []
|
||||
defaultValue = union(defaultValue, defaults)
|
||||
}
|
||||
if (config && config[item.name] !== undefined) {
|
||||
defaultValue = config[item.name]
|
||||
}
|
||||
ruleForm[item.name] = defaultValue
|
||||
return item
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function getCurConfigFormData () {
|
||||
const configId = $route.params.configId
|
||||
const curTypeConfigList = await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`) || []
|
||||
return curTypeConfigList.find(i => i._id === configId) || {}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
getConfigType
|
||||
})
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#config-form
|
||||
@@ -146,15 +177,25 @@ export default class extends Vue {
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
&-item
|
||||
display: flex
|
||||
justify-content space-between
|
||||
border-bottom 1px solid darken(#eee, 50%)
|
||||
padding-bottom 16px
|
||||
&:last-child
|
||||
border-bottom none
|
||||
&__content
|
||||
justify-content flex-end
|
||||
.el-button-group
|
||||
width 100%
|
||||
.el-button
|
||||
width 50%
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
.el-radio-group
|
||||
margin-left 25px
|
||||
.el-switch__label
|
||||
&.is-active
|
||||
color #409EFF
|
||||
&.white
|
||||
.el-form-item
|
||||
border-bottom 1px solid #ddd
|
||||
</style>
|
||||
|
||||
@@ -1,69 +1,84 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="showInputBoxVisible"
|
||||
:title="inputBoxOptions.title || $T('INPUT')"
|
||||
:visible.sync="showInputBoxVisible"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<el-input
|
||||
v-model="inputBoxValue"
|
||||
:placeholder="inputBoxOptions.placeholder"></el-input>
|
||||
<span slot="footer">
|
||||
<el-button @click="handleInputBoxCancel" round>{{ $T('CANCEL') }}</el-button>
|
||||
<el-button type="primary" @click="handleInputBoxConfirm" round>{{ $T('CONFIRM') }}</el-button>
|
||||
</span>
|
||||
:placeholder="inputBoxOptions.placeholder"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button
|
||||
round
|
||||
@click="handleInputBoxCancel"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
@click="handleInputBoxConfirm"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onBeforeUnmount, onBeforeMount } from 'vue'
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import {
|
||||
SHOW_INPUT_BOX,
|
||||
SHOW_INPUT_BOX_RESPONSE
|
||||
} from '~/universal/events/constants'
|
||||
@Component({
|
||||
name: 'input-box-dialog'
|
||||
import $bus from '@/utils/bus'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
const inputBoxValue = ref('')
|
||||
const showInputBoxVisible = ref(false)
|
||||
const inputBoxOptions = reactive({
|
||||
title: '',
|
||||
placeholder: ''
|
||||
})
|
||||
export default class extends Vue {
|
||||
inputBoxValue = ''
|
||||
showInputBoxVisible = false
|
||||
inputBoxOptions = {
|
||||
title: '',
|
||||
placeholder: ''
|
||||
}
|
||||
|
||||
created () {
|
||||
ipcRenderer.on(SHOW_INPUT_BOX, this.ipcEventHandler)
|
||||
this.$bus.$on(SHOW_INPUT_BOX, this.initInputBoxValue)
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
ipcRenderer.on(SHOW_INPUT_BOX, ipcEventHandler)
|
||||
$bus.on(SHOW_INPUT_BOX, initInputBoxValue)
|
||||
})
|
||||
|
||||
ipcEventHandler (evt: IpcRendererEvent, options: IShowInputBoxOption) {
|
||||
this.initInputBoxValue(options)
|
||||
}
|
||||
function ipcEventHandler (evt: IpcRendererEvent, options: IShowInputBoxOption) {
|
||||
initInputBoxValue(options)
|
||||
}
|
||||
|
||||
initInputBoxValue (options: IShowInputBoxOption) {
|
||||
this.inputBoxValue = options.value || ''
|
||||
this.inputBoxOptions.title = options.title || ''
|
||||
this.inputBoxOptions.placeholder = options.placeholder || ''
|
||||
this.showInputBoxVisible = true
|
||||
}
|
||||
function initInputBoxValue (options: IShowInputBoxOption) {
|
||||
inputBoxValue.value = options.value || ''
|
||||
inputBoxOptions.title = options.title || ''
|
||||
inputBoxOptions.placeholder = options.placeholder || ''
|
||||
showInputBoxVisible.value = true
|
||||
}
|
||||
|
||||
handleInputBoxCancel () {
|
||||
// TODO: RPCServer
|
||||
this.showInputBoxVisible = false
|
||||
ipcRenderer.send(SHOW_INPUT_BOX, '')
|
||||
this.$bus.$emit(SHOW_INPUT_BOX_RESPONSE, '')
|
||||
}
|
||||
function handleInputBoxCancel () {
|
||||
// TODO: RPCServer
|
||||
showInputBoxVisible.value = false
|
||||
sendToMain(SHOW_INPUT_BOX, '')
|
||||
$bus.emit(SHOW_INPUT_BOX_RESPONSE, '')
|
||||
}
|
||||
|
||||
handleInputBoxConfirm () {
|
||||
this.showInputBoxVisible = false
|
||||
ipcRenderer.send(SHOW_INPUT_BOX, this.inputBoxValue)
|
||||
this.$bus.$emit(SHOW_INPUT_BOX_RESPONSE, this.inputBoxValue)
|
||||
}
|
||||
function handleInputBoxConfirm () {
|
||||
showInputBoxVisible.value = false
|
||||
sendToMain(SHOW_INPUT_BOX, inputBoxValue.value)
|
||||
$bus.emit(SHOW_INPUT_BOX_RESPONSE, inputBoxValue.value)
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener(SHOW_INPUT_BOX, this.ipcEventHandler)
|
||||
this.$bus.$off(SHOW_INPUT_BOX)
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeListener(SHOW_INPUT_BOX, ipcEventHandler)
|
||||
$bus.off(SHOW_INPUT_BOX)
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'InputBoxDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="toolbox-handler">
|
||||
<ElButton
|
||||
type="text"
|
||||
@click="() => props.handler(value)"
|
||||
>
|
||||
{{ props.handlerText }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { IToolboxItemCheckStatus } from '~/universal/types/enum'
|
||||
interface IProps {
|
||||
status: IToolboxItemCheckStatus
|
||||
value: any
|
||||
handlerText: string
|
||||
handler: (value: any) => void | Promise<void>
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ToolboxHandler'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<el-icon
|
||||
:color="color"
|
||||
class="toolbox-status-icon"
|
||||
>
|
||||
<template v-if="props.status === IToolboxItemCheckStatus.SUCCESS">
|
||||
<SuccessFilled />
|
||||
</template>
|
||||
<template v-if="props.status === IToolboxItemCheckStatus.ERROR">
|
||||
<CircleCloseFilled />
|
||||
</template>
|
||||
<template v-if="props.status === IToolboxItemCheckStatus.LOADING">
|
||||
<Loading />
|
||||
</template>
|
||||
</el-icon>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { CircleCloseFilled, Loading, SuccessFilled } from '@element-plus/icons-vue'
|
||||
import { computed } from 'vue'
|
||||
import { IToolboxItemCheckStatus } from '~/universal/types/enum'
|
||||
interface IProps {
|
||||
status: IToolboxItemCheckStatus
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const color = computed(() => {
|
||||
switch (props.status) {
|
||||
case IToolboxItemCheckStatus.SUCCESS:
|
||||
return '#67C23A'
|
||||
case IToolboxItemCheckStatus.ERROR:
|
||||
return '#F56C6C'
|
||||
default:
|
||||
return '#909399'
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ToolboxStatusIcon'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.toolbox-status-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
{{ props.settingText }}
|
||||
<template v-if="props.tooltips">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="props.tooltips"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon style="margin-left: 4px">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-switch
|
||||
v-model="value"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { saveConfig } from '@/utils/dataSender'
|
||||
import { ref, watch } from 'vue'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
|
||||
interface IProps {
|
||||
tooltips?: string
|
||||
settingProps: string
|
||||
settingText: string
|
||||
defaultValue: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
// to fix v-model blank bug
|
||||
watch(() => props.defaultValue, (v) => {
|
||||
value.value = v
|
||||
})
|
||||
|
||||
const value = ref<boolean>(props.defaultValue)
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const handleChange = (val: ICheckBoxValueType) => {
|
||||
saveConfig(props.settingProps, val)
|
||||
emit('change', val)
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CheckboxFormItem'
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { onUnmounted } from 'vue'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
|
||||
export const useIPCOn = (channel: string, listener: IpcRendererListener) => {
|
||||
ipcRenderer.on(channel, listener)
|
||||
|
||||
onUnmounted(() => {
|
||||
ipcRenderer.removeListener(channel, listener)
|
||||
})
|
||||
}
|
||||
|
||||
export const useIPCOnce = (channel: string, listener: IpcRendererListener) => {
|
||||
ipcRenderer.once(channel, listener)
|
||||
|
||||
onUnmounted(() => {
|
||||
ipcRenderer.removeListener(channel, listener)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* will auto removeListener when component unmounted
|
||||
*/
|
||||
export const useIPC = () => {
|
||||
return {
|
||||
on: (channel: IRPCActionType, listener: IpcRendererListener) => useIPCOn(channel, listener),
|
||||
once: (channel: IRPCActionType, listener: IpcRendererListener) => useIPCOnce(channel, listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { inject } from 'vue'
|
||||
import { storeKey } from '@/store'
|
||||
|
||||
export const useStore = () => {
|
||||
return inject(storeKey) ?? null
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { ObjectAdapter, I18n } from '@picgo/i18n'
|
||||
import { GET_CURRENT_LANGUAGE, SET_CURRENT_LANGUAGE, FORCE_UPDATE, GET_LANGUAGE_LIST } from '#/events/constants'
|
||||
import bus from '@/utils/bus'
|
||||
import { builtinI18nList } from '#/i18n'
|
||||
|
||||
export class I18nManager {
|
||||
private i18n: I18n | null = null
|
||||
private i18nFileList: II18nItem[] = builtinI18nList
|
||||
|
||||
private getLanguageList () {
|
||||
ipcRenderer.send(GET_LANGUAGE_LIST)
|
||||
ipcRenderer.once(GET_LANGUAGE_LIST, (event, list: II18nItem[]) => {
|
||||
this.i18nFileList = list
|
||||
})
|
||||
}
|
||||
|
||||
private getCurrentLanguage () {
|
||||
ipcRenderer.send(GET_CURRENT_LANGUAGE)
|
||||
ipcRenderer.once(GET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => {
|
||||
this.setLocales(lang, locales)
|
||||
bus.emit(FORCE_UPDATE)
|
||||
})
|
||||
}
|
||||
|
||||
private setLocales (lang: string, locales: ILocales) {
|
||||
const objectAdapter = new ObjectAdapter({
|
||||
[lang]: locales
|
||||
})
|
||||
this.i18n = new I18n({
|
||||
adapter: objectAdapter,
|
||||
defaultLanguage: lang
|
||||
})
|
||||
}
|
||||
|
||||
constructor () {
|
||||
this.getCurrentLanguage()
|
||||
this.getLanguageList()
|
||||
ipcRenderer.on(SET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => {
|
||||
this.setLocales(lang, locales)
|
||||
bus.emit(FORCE_UPDATE)
|
||||
})
|
||||
}
|
||||
|
||||
T (key: ILocalesKey, args: IStringKeyMap = {}): string {
|
||||
return this.i18n?.translate(key, args) || key
|
||||
}
|
||||
|
||||
setCurrentLanguage (lang: string) {
|
||||
ipcRenderer.send(SET_CURRENT_LANGUAGE, lang)
|
||||
}
|
||||
|
||||
get languageList () {
|
||||
return this.i18nFileList
|
||||
}
|
||||
}
|
||||
|
||||
const i18nManager = new I18nManager()
|
||||
|
||||
const T = (key: ILocalesKey, args: IStringKeyMap = {}): string => {
|
||||
return i18nManager.T(key, args)
|
||||
}
|
||||
|
||||
export {
|
||||
i18nManager,
|
||||
T
|
||||
}
|
||||
@@ -1,37 +1,69 @@
|
||||
<template>
|
||||
<div id="main-page">
|
||||
<div class="fake-title-bar" :class="{ 'darwin': os === 'darwin' }">
|
||||
<div
|
||||
class="fake-title-bar"
|
||||
:class="{ 'darwin': os === 'darwin' }"
|
||||
>
|
||||
<div class="fake-title-bar__title">
|
||||
PicGo - {{ version }}
|
||||
</div>
|
||||
<div class="handle-bar" v-if="os !== 'darwin'">
|
||||
<i class="el-icon-minus" @click="minimizeWindow"></i>
|
||||
<i class="el-icon-circle-plus-outline" @click="openMiniWindow"></i>
|
||||
<i class="el-icon-close" @click="closeWindow"></i>
|
||||
<div
|
||||
v-if="os !== 'darwin'"
|
||||
class="handle-bar"
|
||||
>
|
||||
<el-icon
|
||||
class="minus"
|
||||
@click="minimizeWindow"
|
||||
>
|
||||
<Minus />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
class="plus"
|
||||
@click="openMiniWindow"
|
||||
>
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
class="close"
|
||||
@click="closeWindow"
|
||||
>
|
||||
<Close />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<el-row style="padding-top: 22px;" class="main-content">
|
||||
<el-col :span="5" class="side-bar-menu">
|
||||
<el-row
|
||||
style="padding-top: 22px;"
|
||||
class="main-content"
|
||||
>
|
||||
<el-col
|
||||
class="side-bar-menu"
|
||||
>
|
||||
<el-menu
|
||||
class="picgo-sidebar"
|
||||
:default-active="defaultActive"
|
||||
@select="handleSelect"
|
||||
:unique-opened="true"
|
||||
@select="handleSelect"
|
||||
@open="handleGetPicPeds"
|
||||
>
|
||||
<el-menu-item index="upload">
|
||||
<i class="el-icon-upload"></i>
|
||||
<span slot="title">{{ $T('UPLOAD_AREA') }}</span>
|
||||
>
|
||||
<el-menu-item :index="routerConfig.UPLOAD_PAGE">
|
||||
<el-icon>
|
||||
<UploadFilled />
|
||||
</el-icon>
|
||||
<span>{{ $T('UPLOAD_AREA') }}</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="gallery">
|
||||
<i class="el-icon-picture"></i>
|
||||
<span slot="title">{{ $T('GALLERY') }}</span>
|
||||
<el-menu-item :index="routerConfig.GALLERY_PAGE">
|
||||
<el-icon>
|
||||
<PictureFilled />
|
||||
</el-icon>
|
||||
<span>{{ $T('GALLERY') }}</span>
|
||||
</el-menu-item>
|
||||
<el-submenu
|
||||
<el-sub-menu
|
||||
index="sub-menu"
|
||||
>
|
||||
<template slot="title">
|
||||
<i class="el-icon-menu"></i>
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<Menu />
|
||||
</el-icon>
|
||||
<span>{{ $T('PICBEDS_SETTINGS') }}</span>
|
||||
</template>
|
||||
<template
|
||||
@@ -39,72 +71,97 @@
|
||||
>
|
||||
<el-menu-item
|
||||
v-if="item.visible"
|
||||
:index="`picbeds-${item.type}`"
|
||||
:key="item.type"
|
||||
:index="`${routerConfig.UPLOADER_CONFIG_PAGE}-${item.type}`"
|
||||
>
|
||||
<!-- <i :class="`el-icon-ui-${item.type}`"></i> -->
|
||||
<span slot="title">{{ item.name }}</span>
|
||||
<span>{{ item.name }}</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-submenu>
|
||||
<el-menu-item index="setting">
|
||||
<i class="el-icon-setting"></i>
|
||||
<span slot="title">{{ $T('PICGO_SETTINGS') }}</span>
|
||||
</el-sub-menu>
|
||||
<el-menu-item :index="routerConfig.SETTING_PAGE">
|
||||
<el-icon>
|
||||
<Setting />
|
||||
</el-icon>
|
||||
<span>{{ $T('PICGO_SETTINGS') }}</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="plugin">
|
||||
<i class="el-icon-share"></i>
|
||||
<span slot="title">{{ $T('PLUGIN_SETTINGS') }}</span>
|
||||
<el-menu-item :index="routerConfig.PLUGIN_PAGE">
|
||||
<el-icon>
|
||||
<Share />
|
||||
</el-icon>
|
||||
<span>{{ $T('PLUGIN_SETTINGS') }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<i class="el-icon-info setting-window" @click="openDialog"></i>
|
||||
<el-icon
|
||||
class="info-window"
|
||||
@click="openMenu"
|
||||
>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="19"
|
||||
:offset="5"
|
||||
style="height: 428px"
|
||||
class="main-wrapper"
|
||||
:class="{ 'darwin': os === 'darwin' }">
|
||||
<transition name="picgo-fade" mode="out-in">
|
||||
<keep-alive>
|
||||
<router-view v-if="$route && $route.meta && $route.meta.keepAlive"></router-view>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
<transition name="picgo-fade" mode="out-in">
|
||||
<router-view :key="$route.path" v-if="!($route && $route.meta && $route.meta.keepAlive)"></router-view>
|
||||
</transition>
|
||||
:class="{ 'darwin': os === 'darwin' }"
|
||||
>
|
||||
<router-view
|
||||
v-slot="{ Component }"
|
||||
>
|
||||
<transition
|
||||
name="picgo-fade"
|
||||
mode="out-in"
|
||||
>
|
||||
<keep-alive :include="keepAlivePages">
|
||||
<component
|
||||
:is="Component"
|
||||
/>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="$T('SPONSOR_PICGO')"
|
||||
:visible.sync="visible"
|
||||
width="70%"
|
||||
top="10vh"
|
||||
>
|
||||
{{ $T('PICGO_SPONSOR_TEXT') }}
|
||||
<el-row class="support">
|
||||
<el-col :span="12">
|
||||
<img src="https://user-images.githubusercontent.com/12621342/34188165-e7cdf372-e56f-11e7-8732-1338c88b9bb7.jpg" :alt="$T('ALIPAY')">
|
||||
<div class="support-title">{{ $T('ALIPAY') }}</div>
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/12621342/34188165-e7cdf372-e56f-11e7-8732-1338c88b9bb7.jpg"
|
||||
:alt="$T('ALIPAY')"
|
||||
>
|
||||
<div class="support-title">
|
||||
{{ $T('ALIPAY') }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<img src="https://user-images.githubusercontent.com/12621342/34188201-212cda84-e570-11e7-9b7a-abb298699d85.jpg" :alt="$T('WECHATPAY')">
|
||||
<div class="support-title">{{ $T('WECHATPAY') }}</div>
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/12621342/34188201-212cda84-e570-11e7-9b7a-abb298699d85.jpg"
|
||||
:alt="$T('WECHATPAY')"
|
||||
>
|
||||
<div class="support-title">
|
||||
{{ $T('WECHATPAY') }}
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="qrcodeVisible"
|
||||
class="qrcode-dialog"
|
||||
top="3vh"
|
||||
width="60%"
|
||||
:title="$T('PICBED_QRCODE')"
|
||||
:visible.sync="qrcodeVisible"
|
||||
:modal-append-to-body="false"
|
||||
lock-scroll
|
||||
>
|
||||
<el-form
|
||||
label-position="left"
|
||||
label-width="70px"
|
||||
size="mini"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('CHOOSE_PICBED')"
|
||||
@@ -119,7 +176,7 @@
|
||||
:key="item.type"
|
||||
:label="item.name"
|
||||
:value="item.type"
|
||||
></el-option>
|
||||
/>
|
||||
</el-select>
|
||||
<el-button
|
||||
v-show="choosedPicBedForQRCode.length > 0"
|
||||
@@ -143,129 +200,153 @@
|
||||
<input-box-dialog />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
<script lang="ts" setup>
|
||||
// import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import {
|
||||
Setting,
|
||||
UploadFilled,
|
||||
PictureFilled,
|
||||
Menu,
|
||||
Share,
|
||||
InfoFilled,
|
||||
Minus,
|
||||
CirclePlus,
|
||||
Close
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import { T } from '@/i18n/index'
|
||||
import { ref, onBeforeUnmount, Ref, onBeforeMount, watch, nextTick, reactive } from 'vue'
|
||||
import { onBeforeRouteUpdate, useRouter } from 'vue-router'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import pick from 'lodash/pick'
|
||||
import pkg from 'root/package.json'
|
||||
import * as config from '@/router/config'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent,
|
||||
clipboard
|
||||
} from 'electron'
|
||||
import mixin from '@/utils/mixin'
|
||||
import InputBoxDialog from '@/components/InputBoxDialog.vue'
|
||||
import {
|
||||
MINIMIZE_WINDOW,
|
||||
CLOSE_WINDOW,
|
||||
SHOW_MAIN_PAGE_MENU,
|
||||
SHOW_MAIN_PAGE_QRCODE,
|
||||
SHOW_MAIN_PAGE_DONATION
|
||||
SHOW_MAIN_PAGE_DONATION,
|
||||
GET_PICBEDS
|
||||
} from '~/universal/events/constants'
|
||||
@Component({
|
||||
name: 'main-page',
|
||||
mixins: [mixin],
|
||||
components: {
|
||||
InputBoxDialog,
|
||||
QrcodeVue
|
||||
import { getConfig, sendToMain } from '@/utils/dataSender'
|
||||
const version = ref(process.env.NODE_ENV === 'production' ? pkg.version : 'Dev')
|
||||
const routerConfig = reactive(config)
|
||||
const defaultActive = ref(routerConfig.UPLOAD_PAGE)
|
||||
const visible = ref(false)
|
||||
const os = ref('')
|
||||
const $router = useRouter()
|
||||
const picBed: Ref<IPicBedType[]> = ref([])
|
||||
const qrcodeVisible = ref(false)
|
||||
const picBedConfigString = ref('')
|
||||
const choosedPicBedForQRCode: Ref<string[]> = ref([])
|
||||
|
||||
const keepAlivePages = $router.getRoutes().filter(item => item.meta.keepAlive).map(item => item.name as string)
|
||||
|
||||
onBeforeMount(() => {
|
||||
os.value = process.platform
|
||||
sendToMain(GET_PICBEDS)
|
||||
ipcRenderer.on(GET_PICBEDS, getPicBeds)
|
||||
handleGetPicPeds()
|
||||
ipcRenderer.on(SHOW_MAIN_PAGE_QRCODE, () => {
|
||||
qrcodeVisible.value = true
|
||||
})
|
||||
ipcRenderer.on(SHOW_MAIN_PAGE_DONATION, () => {
|
||||
visible.value = true
|
||||
})
|
||||
})
|
||||
|
||||
watch(() => choosedPicBedForQRCode, (val) => {
|
||||
if (val.value.length > 0) {
|
||||
nextTick(async () => {
|
||||
const picBedConfig = await getConfig('picBed')
|
||||
const config = pick(picBedConfig, ...choosedPicBedForQRCode.value)
|
||||
picBedConfigString.value = JSON.stringify(config)
|
||||
})
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
const handleGetPicPeds = () => {
|
||||
sendToMain(GET_PICBEDS)
|
||||
}
|
||||
|
||||
const handleSelect = (index: string) => {
|
||||
defaultActive.value = index
|
||||
const type = index.match(routerConfig.UPLOADER_CONFIG_PAGE)
|
||||
if (type === null) {
|
||||
$router.push({
|
||||
name: index
|
||||
})
|
||||
} else {
|
||||
const type = index.replace(`${routerConfig.UPLOADER_CONFIG_PAGE}-`, '')
|
||||
$router.push({
|
||||
name: routerConfig.UPLOADER_CONFIG_PAGE,
|
||||
params: {
|
||||
type
|
||||
}
|
||||
})
|
||||
// if (this.$builtInPicBed.includes(picBed)) {
|
||||
// this.$router.push({
|
||||
// name: picBed
|
||||
// })
|
||||
// } else {
|
||||
// this.$router.push({
|
||||
// name: 'others',
|
||||
// params: {
|
||||
// type: picBed
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
function minimizeWindow () {
|
||||
sendToMain(MINIMIZE_WINDOW)
|
||||
}
|
||||
|
||||
function closeWindow () {
|
||||
sendToMain(CLOSE_WINDOW)
|
||||
}
|
||||
|
||||
function openMenu () {
|
||||
sendToMain(SHOW_MAIN_PAGE_MENU)
|
||||
}
|
||||
|
||||
function openMiniWindow () {
|
||||
sendToMain('openMiniWindow')
|
||||
}
|
||||
|
||||
function handleCopyPicBedConfig () {
|
||||
clipboard.writeText(picBedConfigString.value)
|
||||
$message.success(T('COPY_PICBED_CONFIG_SUCCEED'))
|
||||
}
|
||||
|
||||
function getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
|
||||
picBed.value = picBeds
|
||||
}
|
||||
|
||||
onBeforeRouteUpdate(async (to) => {
|
||||
if (to.params.type) {
|
||||
defaultActive.value = `${routerConfig.UPLOADER_CONFIG_PAGE}-${to.params.type}`
|
||||
} else {
|
||||
defaultActive.value = to.name as string
|
||||
}
|
||||
})
|
||||
export default class extends Vue {
|
||||
version = process.env.NODE_ENV === 'production' ? pkg.version : 'Dev'
|
||||
defaultActive = 'upload'
|
||||
visible = false
|
||||
keyBindingVisible = false
|
||||
customLinkVisible = false
|
||||
os = ''
|
||||
picBed: IPicBedType[] = []
|
||||
qrcodeVisible = false
|
||||
picBedConfigString = ''
|
||||
choosedPicBedForQRCode: string[] = []
|
||||
created () {
|
||||
this.os = process.platform
|
||||
ipcRenderer.send('getPicBeds')
|
||||
ipcRenderer.on('getPicBeds', this.getPicBeds)
|
||||
this.handleGetPicPeds()
|
||||
ipcRenderer.on(SHOW_MAIN_PAGE_QRCODE, () => {
|
||||
this.qrcodeVisible = true
|
||||
})
|
||||
ipcRenderer.on(SHOW_MAIN_PAGE_DONATION, () => {
|
||||
this.visible = true
|
||||
})
|
||||
}
|
||||
|
||||
@Watch('choosedPicBedForQRCode')
|
||||
choosedPicBedForQRCodeChange (val: string[]) {
|
||||
if (val.length > 0) {
|
||||
this.$nextTick(async () => {
|
||||
const picBedConfig = await this.getConfig('picBed')
|
||||
const config = pick(picBedConfig, ...this.choosedPicBedForQRCode)
|
||||
this.picBedConfigString = JSON.stringify(config)
|
||||
})
|
||||
}
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeListener(GET_PICBEDS, getPicBeds)
|
||||
})
|
||||
|
||||
handleGetPicPeds = () => {
|
||||
ipcRenderer.send('getPicBeds')
|
||||
}
|
||||
|
||||
handleSelect (index: string) {
|
||||
const type = index.match(/picbeds-/)
|
||||
if (type === null) {
|
||||
this.$router.push({
|
||||
name: index
|
||||
})
|
||||
} else {
|
||||
const picBed = index.replace(/picbeds-/, '')
|
||||
if (this.$builtInPicBed.includes(picBed)) {
|
||||
this.$router.push({
|
||||
name: picBed
|
||||
})
|
||||
} else {
|
||||
this.$router.push({
|
||||
name: 'others',
|
||||
params: {
|
||||
type: picBed
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
minimizeWindow () {
|
||||
ipcRenderer.send(MINIMIZE_WINDOW)
|
||||
}
|
||||
|
||||
closeWindow () {
|
||||
ipcRenderer.send(CLOSE_WINDOW)
|
||||
}
|
||||
|
||||
openDialog () {
|
||||
ipcRenderer.send(SHOW_MAIN_PAGE_MENU)
|
||||
}
|
||||
|
||||
openMiniWindow () {
|
||||
ipcRenderer.send('openMiniWindow')
|
||||
}
|
||||
|
||||
handleCopyPicBedConfig () {
|
||||
clipboard.writeText(this.picBedConfigString)
|
||||
this.$message.success(this.$T('COPY_PICBED_CONFIG_SUCCEED'))
|
||||
}
|
||||
|
||||
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
|
||||
this.picBed = picBeds
|
||||
}
|
||||
|
||||
beforeRouteEnter (to: any, next: any) {
|
||||
next((vm: this) => {
|
||||
vm.defaultActive = to.name
|
||||
})
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'MainPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
@@ -282,7 +363,7 @@ $darwinBg = transparentify(#172426, #000, 0.7)
|
||||
opacity 0
|
||||
&-enter-active,
|
||||
&-leave-active
|
||||
transition all 100ms linear
|
||||
transition all 150ms linear
|
||||
.view-title
|
||||
color #eee
|
||||
font-size 20px
|
||||
@@ -297,8 +378,6 @@ $darwinBg = transparentify(#172426, #000, 0.7)
|
||||
padding-top 10px
|
||||
.copy-picbed-config
|
||||
margin-left 10px
|
||||
.el-input__inner
|
||||
border-radius 14px
|
||||
.fake-title-bar
|
||||
-webkit-app-region drag
|
||||
height h = 22px
|
||||
@@ -326,17 +405,17 @@ $darwinBg = transparentify(#172426, #000, 0.7)
|
||||
right 4px
|
||||
z-index 10000
|
||||
-webkit-app-region no-drag
|
||||
i
|
||||
.el-icon
|
||||
cursor pointer
|
||||
font-size 16px
|
||||
margin-left 5px
|
||||
.el-icon-minus
|
||||
.el-icon.minus
|
||||
&:hover
|
||||
color #409EFF
|
||||
.el-icon-close
|
||||
.el-icon.close
|
||||
&:hover
|
||||
color #F15140
|
||||
.el-icon-circle-plus-outline
|
||||
.el-icon.plus
|
||||
&:hover
|
||||
color #69C282
|
||||
.main-wrapper
|
||||
@@ -348,7 +427,8 @@ $darwinBg = transparentify(#172426, #000, 0.7)
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
width 170px
|
||||
.el-icon-info.setting-window
|
||||
.info-window
|
||||
cursor pointer
|
||||
position fixed
|
||||
bottom 4px
|
||||
left 4px
|
||||
@@ -378,14 +458,13 @@ $darwinBg = transparentify(#172426, #000, 0.7)
|
||||
right 0
|
||||
top 18px
|
||||
background active-color
|
||||
.el-submenu__title
|
||||
span
|
||||
color #eee
|
||||
.el-sub-menu__title
|
||||
color #eee
|
||||
&:hover
|
||||
background transparent
|
||||
span
|
||||
color #fff
|
||||
.el-submenu
|
||||
.el-sub-menu
|
||||
.el-menu-item
|
||||
min-width 166px
|
||||
&.is-active
|
||||
|
||||
@@ -1,65 +1,105 @@
|
||||
<template>
|
||||
<div id="gallery-view">
|
||||
<div class="view-title">
|
||||
{{ $T('GALLERY') }} - {{ filterList.length }} <i class="el-icon-caret-bottom" @click="toggleHandleBar" :class="{'active': handleBarActive}"></i>
|
||||
{{ $T('GALLERY') }} - {{ filterList.length }}
|
||||
<el-icon
|
||||
style="margin-left: 4px"
|
||||
class="cursor-pointer"
|
||||
@click="toggleHandleBar"
|
||||
>
|
||||
<CaretBottom v-show="!handleBarActive" />
|
||||
<CaretTop v-show="handleBarActive" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<transition name="el-zoom-in-top">
|
||||
<el-row v-show="handleBarActive">
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-row class="handle-bar" :gutter="16">
|
||||
<el-col
|
||||
:span="20"
|
||||
:offset="2"
|
||||
>
|
||||
<el-row
|
||||
class="handle-bar"
|
||||
:gutter="16"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="choosedPicBed"
|
||||
multiple
|
||||
collapse-tags
|
||||
size="mini"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
:placeholder="$T('CHOOSE_SHOWED_PICBED')">
|
||||
:placeholder="$T('CHOOSE_SHOWED_PICBED')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in picBed"
|
||||
:key="item.type"
|
||||
:label="item.name"
|
||||
:value="item.type">
|
||||
</el-option>
|
||||
:value="item.type"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="pasteStyle"
|
||||
size="mini"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
:placeholder="$T('CHOOSE_PASTE_FORMAT')"
|
||||
@change="handlePasteStyleChange"
|
||||
:placeholder="$T('CHOOSE_PASTE_FORMAT')">
|
||||
>
|
||||
<el-option
|
||||
v-for="(value, key) in pasteStyleMap"
|
||||
:key="key"
|
||||
:label="key"
|
||||
:value="value">
|
||||
</el-option>
|
||||
:value="value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="handle-bar" :gutter="16">
|
||||
<el-row
|
||||
class="handle-bar"
|
||||
:gutter="16"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-input
|
||||
v-model="searchText"
|
||||
:placeholder="$T('SEARCH')"
|
||||
size="mini"
|
||||
v-model="searchText">
|
||||
<i slot="suffix" class="el-input__icon el-icon-close" v-if="searchText" @click="cleanSearch" style="cursor: pointer"></i>
|
||||
size="small"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon
|
||||
class="el-input__icon"
|
||||
style="cursor: pointer;"
|
||||
@click="cleanSearch"
|
||||
>
|
||||
<close />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="item-base copy round" :class="{ active: isMultiple(choosedList)}" @click="multiCopy">
|
||||
<div
|
||||
class="item-base copy round"
|
||||
:class="{ active: isMultiple(choosedList)}"
|
||||
@click="multiCopy"
|
||||
>
|
||||
{{ $T('COPY') }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="item-base delete round" :class="{ active: isMultiple(choosedList)}" @click="multiRemove">
|
||||
<div
|
||||
class="item-base delete round"
|
||||
:class="{ active: isMultiple(choosedList)}"
|
||||
@click="multiRemove"
|
||||
>
|
||||
{{ $T('DELETE') }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="item-base all-pick round" :class="{ active: filterList.length > 0}" @click="toggleSelectAll">
|
||||
<div
|
||||
class="item-base all-pick round"
|
||||
:class="{ active: filterList.length > 0}"
|
||||
@click="toggleSelectAll"
|
||||
>
|
||||
{{ isAllSelected ? $T('CANCEL') : $T('SELECT_ALL') }}
|
||||
</div>
|
||||
</el-col>
|
||||
@@ -67,395 +107,469 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</transition>
|
||||
<el-row class="gallery-list" :class="{ small: handleBarActive }">
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-row
|
||||
class="gallery-list"
|
||||
:class="{ small: handleBarActive }"
|
||||
>
|
||||
<el-col
|
||||
:span="20"
|
||||
:offset="2"
|
||||
>
|
||||
<el-row :gutter="16">
|
||||
<gallerys
|
||||
:images="filterList"
|
||||
:index="idx"
|
||||
@close="handleClose"
|
||||
:options="options"
|
||||
></gallerys>
|
||||
<el-col :span="6" v-for="(item, index) in filterList" :key="item.id" class="gallery-list__img">
|
||||
<photo-slider
|
||||
:items="filterList"
|
||||
:visible="gallerySliderControl.visible"
|
||||
:index="gallerySliderControl.index"
|
||||
:should-transition="true"
|
||||
@change-index="zoomImage"
|
||||
@click-mask="handleClose"
|
||||
@close-modal="handleClose"
|
||||
/>
|
||||
<el-col
|
||||
v-for="(item, index) in filterList"
|
||||
:key="item.id"
|
||||
:span="6"
|
||||
class="gallery-list__img"
|
||||
>
|
||||
<div
|
||||
class="gallery-list__item"
|
||||
@click="zoomImage(index)"
|
||||
>
|
||||
<img v-lazy="item.imgUrl" class="gallery-list__item-img">
|
||||
<img
|
||||
v-lazy="item.imgUrl"
|
||||
class="gallery-list__item-img"
|
||||
>
|
||||
</div>
|
||||
<div class="gallery-list__tool-panel">
|
||||
<i class="el-icon-document" @click="copy(item)"></i>
|
||||
<i class="el-icon-edit-outline" @click="openDialog(item)"></i>
|
||||
<i class="el-icon-delete" @click="remove(item.id)"></i>
|
||||
<el-checkbox v-model="choosedList[item.id ? item.id : '']" class="pull-right" @change="(val) => handleChooseImage(val, index)"></el-checkbox>
|
||||
<div
|
||||
class="gallery-list__file-name"
|
||||
:title="item.fileName"
|
||||
>
|
||||
{{ item.fileName }}
|
||||
</div>
|
||||
<el-row
|
||||
class="gallery-list__tool-panel"
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
>
|
||||
<el-row>
|
||||
<el-icon
|
||||
class="cursor-pointer document"
|
||||
@click="copy(item)"
|
||||
>
|
||||
<Document />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
class="cursor-pointer edit"
|
||||
@click="openDialog(item)"
|
||||
>
|
||||
<Edit />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
class="cursor-pointer delete"
|
||||
@click="remove(item.id)"
|
||||
>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</el-row>
|
||||
<el-checkbox
|
||||
v-model="choosedList[item.id ? item.id : '']"
|
||||
@change="(val) => handleChooseImage(val, index)"
|
||||
/>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
v-model="dialogVisible"
|
||||
:title="$T('CHANGE_IMAGE_URL')"
|
||||
width="500px"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<el-input v-model="imgInfo.imgUrl"></el-input>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">{{ $T('CANCEL') }}</el-button>
|
||||
<el-button type="primary" @click="confirmModify">{{ $T('CONFIRM') }}</el-button>
|
||||
</span>
|
||||
<el-input v-model="imgInfo.imgUrl" />
|
||||
<template
|
||||
#footer
|
||||
>
|
||||
<el-button @click="dialogVisible = false">
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="confirmModify"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
// @ts-ignore
|
||||
import gallerys from 'vue-gallery'
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import { IResult } from '@picgo/store/dist/types'
|
||||
import { PASTE_TEXT } from '#/events/constants'
|
||||
<script lang="ts" setup>
|
||||
import type { IResult } from '@picgo/store/dist/types'
|
||||
import { PASTE_TEXT, GET_PICBEDS } from '#/events/constants'
|
||||
import { CheckboxValueType, ElMessageBox } from 'element-plus'
|
||||
import { Close, CaretBottom, Document, Edit, Delete, CaretTop } from '@element-plus/icons-vue'
|
||||
import {
|
||||
ipcRenderer,
|
||||
clipboard,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
@Component({
|
||||
name: 'gallery',
|
||||
components: {
|
||||
gallerys
|
||||
import { computed, nextTick, onActivated, onBeforeUnmount, onBeforeMount, reactive, ref, watch } from 'vue'
|
||||
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { onBeforeRouteUpdate } from 'vue-router'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import $$db from '@/utils/db'
|
||||
const images = ref<ImgInfo[]>([])
|
||||
const dialogVisible = ref(false)
|
||||
const imgInfo = reactive({
|
||||
id: '',
|
||||
imgUrl: ''
|
||||
})
|
||||
const $confirm = ElMessageBox.confirm
|
||||
const choosedList: IObjT<boolean> = reactive({})
|
||||
const gallerySliderControl = reactive({
|
||||
visible: false,
|
||||
index: 0
|
||||
})
|
||||
const choosedPicBed = ref<string[]>([])
|
||||
const lastChoosed = ref<number>(-1)
|
||||
const isShiftKeyPress = ref<boolean>(false)
|
||||
const searchText = ref<string>('')
|
||||
const handleBarActive = ref<boolean>(false)
|
||||
const pasteStyle = ref<string>('')
|
||||
const pasteStyleMap = {
|
||||
Markdown: 'markdown',
|
||||
HTML: 'HTML',
|
||||
URL: 'URL',
|
||||
UBB: 'UBB',
|
||||
Custom: 'Custom'
|
||||
}
|
||||
const picBed = ref<IPicBedType[]>([])
|
||||
onBeforeRouteUpdate((to, from) => {
|
||||
if (from.name === 'gallery') {
|
||||
clearChoosedList()
|
||||
}
|
||||
if (to.name === 'gallery') {
|
||||
updateGallery()
|
||||
}
|
||||
})
|
||||
export default class extends Vue {
|
||||
images: ImgInfo[] = []
|
||||
idx: null | number = null
|
||||
options = {
|
||||
titleProperty: 'fileName',
|
||||
urlProperty: 'imgUrl',
|
||||
closeOnSlideClick: true
|
||||
}
|
||||
|
||||
dialogVisible = false
|
||||
imgInfo = {
|
||||
id: '',
|
||||
imgUrl: ''
|
||||
}
|
||||
|
||||
choosedList: IObjT<boolean> = {}
|
||||
choosedPicBed: string[] = []
|
||||
lastChoosed: number = -1
|
||||
isShiftKeyPress: boolean = false
|
||||
searchText = ''
|
||||
handleBarActive = false
|
||||
pasteStyle = ''
|
||||
pasteStyleMap = {
|
||||
Markdown: 'markdown',
|
||||
HTML: 'HTML',
|
||||
URL: 'URL',
|
||||
UBB: 'UBB',
|
||||
Custom: 'Custom'
|
||||
}
|
||||
|
||||
picBed: IPicBedType[] = []
|
||||
@Watch('$route')
|
||||
handleRouteUpdate (to: any, from: any) {
|
||||
console.log(to, from)
|
||||
if (from.name === 'gallery') {
|
||||
this.clearChoosedList()
|
||||
}
|
||||
if (to.name === 'gallery') {
|
||||
this.updateGallery()
|
||||
}
|
||||
}
|
||||
|
||||
async created () {
|
||||
ipcRenderer.on('updateGallery', () => {
|
||||
this.$nextTick(async () => {
|
||||
this.updateGallery()
|
||||
})
|
||||
onBeforeMount(async () => {
|
||||
ipcRenderer.on('updateGallery', () => {
|
||||
nextTick(async () => {
|
||||
updateGallery()
|
||||
})
|
||||
ipcRenderer.send('getPicBeds')
|
||||
ipcRenderer.on('getPicBeds', this.getPicBeds)
|
||||
this.updateGallery()
|
||||
}
|
||||
})
|
||||
sendToMain(GET_PICBEDS)
|
||||
ipcRenderer.on(GET_PICBEDS, getPicBeds)
|
||||
updateGallery()
|
||||
|
||||
mounted () {
|
||||
document.addEventListener('keydown', this.handleDetectShiftKey)
|
||||
document.addEventListener('keyup', this.handleDetectShiftKey)
|
||||
}
|
||||
document.addEventListener('keydown', handleDetectShiftKey)
|
||||
document.addEventListener('keyup', handleDetectShiftKey)
|
||||
})
|
||||
|
||||
handleDetectShiftKey (event: KeyboardEvent) {
|
||||
if (event.key === 'Shift') {
|
||||
if (event.type === 'keydown') {
|
||||
this.isShiftKeyPress = true
|
||||
} else if (event.type === 'keyup') {
|
||||
this.isShiftKeyPress = false
|
||||
function handleDetectShiftKey (event: KeyboardEvent) {
|
||||
if (event.key === 'Shift') {
|
||||
if (event.type === 'keydown') {
|
||||
isShiftKeyPress.value = true
|
||||
} else if (event.type === 'keyup') {
|
||||
isShiftKeyPress.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const filterList = computed(() => {
|
||||
return getGallery()
|
||||
})
|
||||
|
||||
const isAllSelected = computed(() => {
|
||||
const values = Object.values(choosedList)
|
||||
if (values.length === 0) {
|
||||
return false
|
||||
} else {
|
||||
return filterList.value.every(item => {
|
||||
return choosedList[item.id!]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
|
||||
picBed.value = picBeds
|
||||
}
|
||||
|
||||
function getGallery (): IGalleryItem[] {
|
||||
if (searchText.value || choosedPicBed.value.length > 0) {
|
||||
return images.value
|
||||
.filter(item => {
|
||||
let isInChoosedPicBed = true
|
||||
let isIncludesSearchText = true
|
||||
if (choosedPicBed.value.length > 0) {
|
||||
isInChoosedPicBed = choosedPicBed.value.some(type => type === item.type)
|
||||
}
|
||||
if (searchText.value) {
|
||||
isIncludesSearchText = item.fileName?.includes(searchText.value) || false
|
||||
}
|
||||
return isIncludesSearchText && isInChoosedPicBed
|
||||
}).map(item => {
|
||||
return {
|
||||
...item,
|
||||
src: item.imgUrl || '',
|
||||
key: (item.id || `${Date.now()}`),
|
||||
intro: item.fileName || ''
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return images.value.map(item => {
|
||||
return {
|
||||
...item,
|
||||
src: item.imgUrl || '',
|
||||
key: (item.id || `${Date.now()}`),
|
||||
intro: item.fileName || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function updateGallery () {
|
||||
images.value = (await $$db.get({ orderBy: 'desc' })).data
|
||||
}
|
||||
|
||||
watch(() => filterList, () => {
|
||||
clearChoosedList()
|
||||
})
|
||||
|
||||
function handleChooseImage (val: CheckboxValueType, index: number) {
|
||||
if (val === true) {
|
||||
handleBarActive.value = true
|
||||
if (lastChoosed.value !== -1 && isShiftKeyPress.value) {
|
||||
const min = Math.min(lastChoosed.value, index)
|
||||
const max = Math.max(lastChoosed.value, index)
|
||||
for (let i = min + 1; i < max; i++) {
|
||||
const id = filterList.value[i].id!
|
||||
choosedList[id] = true
|
||||
}
|
||||
}
|
||||
lastChoosed.value = index
|
||||
}
|
||||
}
|
||||
|
||||
get filterList () {
|
||||
return this.getGallery()
|
||||
function clearChoosedList () {
|
||||
isShiftKeyPress.value = false
|
||||
Object.keys(choosedList).forEach(key => {
|
||||
choosedList[key] = false
|
||||
})
|
||||
lastChoosed.value = -1
|
||||
}
|
||||
|
||||
function zoomImage (index: number) {
|
||||
gallerySliderControl.index = index
|
||||
gallerySliderControl.visible = true
|
||||
changeZIndexForGallery(true)
|
||||
}
|
||||
|
||||
function changeZIndexForGallery (isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 101
|
||||
} else {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 10
|
||||
}
|
||||
}
|
||||
|
||||
get isAllSelected () {
|
||||
const values = Object.values(this.choosedList)
|
||||
if (values.length === 0) {
|
||||
return false
|
||||
} else {
|
||||
return this.filterList.every(item => {
|
||||
return this.choosedList[item.id!]
|
||||
})
|
||||
}
|
||||
function handleClose () {
|
||||
gallerySliderControl.index = 0
|
||||
gallerySliderControl.visible = false
|
||||
changeZIndexForGallery(false)
|
||||
}
|
||||
|
||||
async function copy (item: ImgInfo) {
|
||||
const copyLink = await ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
const obj = {
|
||||
title: $T('COPY_LINK_SUCCEED'),
|
||||
body: copyLink
|
||||
// sometimes will cause lagging
|
||||
// icon: item.url || item.imgUrl
|
||||
}
|
||||
|
||||
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
|
||||
this.picBed = picBeds
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
getGallery (): ImgInfo[] {
|
||||
if (this.searchText || this.choosedPicBed.length > 0) {
|
||||
return this.images
|
||||
.filter(item => {
|
||||
let isInChoosedPicBed = true
|
||||
let isIncludesSearchText = true
|
||||
if (this.choosedPicBed.length > 0) {
|
||||
isInChoosedPicBed = this.choosedPicBed.some(type => type === item.type)
|
||||
}
|
||||
if (this.searchText) {
|
||||
isIncludesSearchText = item.fileName?.includes(this.searchText) || false
|
||||
}
|
||||
return isIncludesSearchText && isInChoosedPicBed
|
||||
})
|
||||
} else {
|
||||
return this.images
|
||||
}
|
||||
}
|
||||
|
||||
async updateGallery () {
|
||||
this.images = (await this.$$db.get({ orderBy: 'desc' })).data
|
||||
}
|
||||
|
||||
@Watch('filterList')
|
||||
handleFilterListChange () {
|
||||
this.clearChoosedList()
|
||||
}
|
||||
|
||||
handleChooseImage (val: boolean, index: number) {
|
||||
if (val === true) {
|
||||
this.handleBarActive = true
|
||||
if (this.lastChoosed !== -1 && this.isShiftKeyPress) {
|
||||
const min = Math.min(this.lastChoosed, index)
|
||||
const max = Math.max(this.lastChoosed, index)
|
||||
for (let i = min + 1; i < max; i++) {
|
||||
const id = this.filterList[i].id!
|
||||
this.$set(this.choosedList, id, true)
|
||||
}
|
||||
}
|
||||
this.lastChoosed = index
|
||||
}
|
||||
}
|
||||
|
||||
clearChoosedList () {
|
||||
this.isShiftKeyPress = false
|
||||
Object.keys(this.choosedList).forEach(key => {
|
||||
this.choosedList[key] = false
|
||||
})
|
||||
this.lastChoosed = -1
|
||||
}
|
||||
|
||||
zoomImage (index: number) {
|
||||
this.idx = index
|
||||
this.changeZIndexForGallery(true)
|
||||
}
|
||||
|
||||
changeZIndexForGallery (isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 101
|
||||
} else {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 10
|
||||
}
|
||||
}
|
||||
|
||||
handleClose () {
|
||||
this.idx = null
|
||||
this.changeZIndexForGallery(false)
|
||||
}
|
||||
|
||||
async copy (item: ImgInfo) {
|
||||
const copyLink = await ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
const obj = {
|
||||
title: this.$T('COPY_LINK_SUCCEED'),
|
||||
body: copyLink,
|
||||
icon: item.url || item.imgUrl
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
remove (id?: string) {
|
||||
if (id) {
|
||||
this.$confirm(this.$T('TIPS_REMOVE_LINK'), this.$T('TIPS_NOTICE'), {
|
||||
confirmButtonText: this.$T('CONFIRM'),
|
||||
cancelButtonText: this.$T('CANCEL'),
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const file = await this.$$db.getById(id)
|
||||
await this.$$db.removeById(id)
|
||||
ipcRenderer.send('removeFiles', [file])
|
||||
const obj = {
|
||||
title: this.$T('OPERATION_SUCCEED'),
|
||||
body: ''
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.updateGallery()
|
||||
}).catch((e) => {
|
||||
console.log(e)
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
openDialog (item: ImgInfo) {
|
||||
this.imgInfo.id = item.id!
|
||||
this.imgInfo.imgUrl = item.imgUrl as string
|
||||
this.dialogVisible = true
|
||||
}
|
||||
|
||||
async confirmModify () {
|
||||
await this.$$db.updateById(this.imgInfo.id, {
|
||||
imgUrl: this.imgInfo.imgUrl
|
||||
})
|
||||
const obj = {
|
||||
title: this.$T('CHANGE_IMAGE_URL_SUCCEED'),
|
||||
body: this.imgInfo.imgUrl,
|
||||
icon: this.imgInfo.imgUrl
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.dialogVisible = false
|
||||
this.updateGallery()
|
||||
}
|
||||
|
||||
choosePicBed (type: string) {
|
||||
const idx = this.choosedPicBed.indexOf(type)
|
||||
if (idx !== -1) {
|
||||
this.choosedPicBed.splice(idx, 1)
|
||||
} else {
|
||||
this.choosedPicBed.push(type)
|
||||
}
|
||||
}
|
||||
|
||||
cleanSearch () {
|
||||
this.searchText = ''
|
||||
}
|
||||
|
||||
isMultiple (obj: IObj) {
|
||||
return Object.values(obj).some(item => item)
|
||||
}
|
||||
|
||||
toggleSelectAll () {
|
||||
const result = !this.isAllSelected
|
||||
this.filterList.forEach(item => {
|
||||
this.$set(this.choosedList, item.id!, result)
|
||||
})
|
||||
}
|
||||
|
||||
multiRemove () {
|
||||
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
|
||||
const multiRemoveNumber = Object.values(this.choosedList).filter(item => item).length
|
||||
if (multiRemoveNumber) {
|
||||
this.$confirm(this.$T('TIPS_WILL_REMOVE_CHOOSED_IMAGES', {
|
||||
m: multiRemoveNumber
|
||||
}), this.$T('TIPS_NOTICE'), {
|
||||
confirmButtonText: this.$T('CONFIRM'),
|
||||
cancelButtonText: this.$T('CANCEL'),
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const files: IResult<ImgInfo>[] = []
|
||||
const imageIDList = Object.keys(this.choosedList)
|
||||
for (let i = 0; i < imageIDList.length; i++) {
|
||||
const key = imageIDList[i]
|
||||
if (this.choosedList[key]) {
|
||||
const file = await this.$$db.getById<ImgInfo>(key)
|
||||
if (file) {
|
||||
files.push(file)
|
||||
await this.$$db.removeById(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearChoosedList()
|
||||
this.choosedList = {} // 只有删除才能将这个置空
|
||||
const obj = {
|
||||
title: this.$T('OPERATION_SUCCEED'),
|
||||
body: ''
|
||||
}
|
||||
ipcRenderer.send('removeFiles', files)
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.updateGallery()
|
||||
}).catch(() => {
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async multiCopy () {
|
||||
if (Object.values(this.choosedList).some(item => item)) {
|
||||
const copyString: string[] = []
|
||||
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
|
||||
const imageIDList = Object.keys(this.choosedList)
|
||||
for (let i = 0; i < imageIDList.length; i++) {
|
||||
const key = imageIDList[i]
|
||||
if (this.choosedList[key]) {
|
||||
const item = await this.$$db.getById<ImgInfo>(key)
|
||||
if (item) {
|
||||
const txt = await ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
copyString.push(txt)
|
||||
this.choosedList[key] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
function remove (id?: string) {
|
||||
if (id) {
|
||||
$confirm($T('TIPS_REMOVE_LINK'), $T('TIPS_NOTICE'), {
|
||||
confirmButtonText: $T('CONFIRM'),
|
||||
cancelButtonText: $T('CANCEL'),
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const file = await $$db.getById(id)
|
||||
await $$db.removeById(id)
|
||||
sendToMain('removeFiles', [file])
|
||||
const obj = {
|
||||
title: this.$T('BATCH_COPY_LINK_SUCCEED'),
|
||||
body: copyString.join('\n')
|
||||
title: $T('OPERATION_SUCCEED'),
|
||||
body: ''
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
clipboard.writeText(copyString.join('\n'))
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
updateGallery()
|
||||
}).catch((e) => {
|
||||
console.log(e)
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function openDialog (item: ImgInfo) {
|
||||
imgInfo.id = item.id!
|
||||
imgInfo.imgUrl = item.imgUrl as string
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function confirmModify () {
|
||||
await $$db.updateById(imgInfo.id, {
|
||||
imgUrl: imgInfo.imgUrl
|
||||
})
|
||||
const obj = {
|
||||
title: $T('CHANGE_IMAGE_URL_SUCCEED'),
|
||||
body: imgInfo.imgUrl
|
||||
// icon: this.imgInfo.imgUrl
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
dialogVisible.value = false
|
||||
updateGallery()
|
||||
}
|
||||
|
||||
// function choosePicBed (type: string) {
|
||||
// const idx = choosedPicBed.value.indexOf(type)
|
||||
// if (idx !== -1) {
|
||||
// choosedPicBed.value.splice(idx, 1)
|
||||
// } else {
|
||||
// choosedPicBed.value.push(type)
|
||||
// }
|
||||
// }
|
||||
|
||||
function cleanSearch () {
|
||||
searchText.value = ''
|
||||
}
|
||||
|
||||
function isMultiple (obj: IObj) {
|
||||
return Object.values(obj).some(item => item)
|
||||
}
|
||||
|
||||
function toggleSelectAll () {
|
||||
const result = !isAllSelected.value
|
||||
filterList.value.forEach(item => {
|
||||
choosedList[item.id!] = result
|
||||
})
|
||||
}
|
||||
|
||||
function multiRemove () {
|
||||
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
|
||||
const multiRemoveNumber = Object.values(choosedList).filter(item => item).length
|
||||
if (multiRemoveNumber) {
|
||||
$confirm($T('TIPS_WILL_REMOVE_CHOOSED_IMAGES', {
|
||||
m: multiRemoveNumber
|
||||
}), $T('TIPS_NOTICE'), {
|
||||
confirmButtonText: $T('CONFIRM'),
|
||||
cancelButtonText: $T('CANCEL'),
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const files: IResult<ImgInfo>[] = []
|
||||
const imageIDList = Object.keys(choosedList)
|
||||
for (let i = 0; i < imageIDList.length; i++) {
|
||||
const key = imageIDList[i]
|
||||
if (choosedList[key]) {
|
||||
const file = await $$db.getById<ImgInfo>(key)
|
||||
if (file) {
|
||||
files.push(file)
|
||||
await $$db.removeById(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
clearChoosedList()
|
||||
// TODO: check this
|
||||
// choosedList = {} // 只有删除才能将这个置空
|
||||
const obj = {
|
||||
title: $T('OPERATION_SUCCEED'),
|
||||
body: ''
|
||||
}
|
||||
sendToMain('removeFiles', files)
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
updateGallery()
|
||||
}).catch(() => {
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function multiCopy () {
|
||||
if (Object.values(choosedList).some(item => item)) {
|
||||
const copyString: string[] = []
|
||||
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
|
||||
const imageIDList = Object.keys(choosedList)
|
||||
for (let i = 0; i < imageIDList.length; i++) {
|
||||
const key = imageIDList[i]
|
||||
if (choosedList[key]) {
|
||||
const item = await $$db.getById<ImgInfo>(key)
|
||||
if (item) {
|
||||
const txt = await ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
copyString.push(txt)
|
||||
choosedList[key] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
const obj = {
|
||||
title: $T('BATCH_COPY_LINK_SUCCEED'),
|
||||
body: copyString.join('\n')
|
||||
}
|
||||
const myNotification = new Notification(obj.title, obj)
|
||||
clipboard.writeText(copyString.join('\n'))
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleHandleBar () {
|
||||
this.handleBarActive = !this.handleBarActive
|
||||
}
|
||||
function toggleHandleBar () {
|
||||
handleBarActive.value = !handleBarActive.value
|
||||
}
|
||||
|
||||
// getPasteStyle () {
|
||||
// this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
|
||||
// }
|
||||
async handlePasteStyleChange (val: string) {
|
||||
this.saveConfig('settings.pasteStyle', val)
|
||||
this.pasteStyle = val
|
||||
}
|
||||
async function handlePasteStyleChange (val: string) {
|
||||
saveConfig('settings.pasteStyle', val)
|
||||
pasteStyle.value = val
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('updateGallery')
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeAllListeners('updateGallery')
|
||||
ipcRenderer.removeListener(GET_PICBEDS, getPicBeds)
|
||||
})
|
||||
|
||||
onActivated(async () => {
|
||||
pasteStyle.value = (await getConfig('settings.pasteStyle')) || 'markdown'
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'GalleryPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.PhotoSlider
|
||||
&__BannerIcon
|
||||
&:nth-child(1)
|
||||
display none
|
||||
&__Counter
|
||||
margin-top 20px
|
||||
.view-title
|
||||
color #eee
|
||||
font-size 20px
|
||||
@@ -470,6 +584,8 @@ export default class extends Vue {
|
||||
transform: rotate(180deg)
|
||||
#gallery-view
|
||||
height 100%
|
||||
.cursor-pointer
|
||||
cursor pointer
|
||||
.item-base
|
||||
background #2E2E2E
|
||||
text-align center
|
||||
@@ -520,17 +636,18 @@ export default class extends Vue {
|
||||
height: 287px
|
||||
top: 113px
|
||||
&__img
|
||||
height 150px
|
||||
// height 150px
|
||||
position relative
|
||||
margin-bottom 16px
|
||||
margin-bottom 8px
|
||||
&__item
|
||||
width 100%
|
||||
height 120px
|
||||
transition all .2s ease-in-out
|
||||
cursor pointer
|
||||
margin-bottom 8px
|
||||
margin-bottom 4px
|
||||
overflow hidden
|
||||
display flex
|
||||
margin-bottom 6px
|
||||
&-fake
|
||||
position absolute
|
||||
top 0
|
||||
@@ -545,21 +662,31 @@ export default class extends Vue {
|
||||
object-fit fill
|
||||
&__tool-panel
|
||||
color #ddd
|
||||
margin-bottom 4px
|
||||
display flex
|
||||
.el-checkbox
|
||||
height 16px
|
||||
i
|
||||
cursor pointer
|
||||
transition all .2s ease-in-out
|
||||
&.el-icon-document
|
||||
margin-right 4px
|
||||
&.document
|
||||
&:hover
|
||||
color #49B1F5
|
||||
&.el-icon-edit-outline
|
||||
&.edit
|
||||
&:hover
|
||||
color #69C282
|
||||
&.el-icon-delete
|
||||
&.delete
|
||||
&:hover
|
||||
color #F15140
|
||||
&__file-name
|
||||
overflow hidden
|
||||
text-overflow ellipsis
|
||||
white-space nowrap
|
||||
color #ddd
|
||||
font-size 14px
|
||||
margin-bottom 4px
|
||||
.handle-bar
|
||||
color #ddd
|
||||
margin-bottom 10px
|
||||
.el-input__inner
|
||||
border-radius 14px
|
||||
</style>
|
||||
|
||||
@@ -1,194 +1,203 @@
|
||||
<template>
|
||||
<div id="mini-page"
|
||||
<div
|
||||
id="mini-page"
|
||||
:style="{ backgroundImage: 'url(' + logo + ')' }"
|
||||
:class="{ linux: os === 'linux' }"
|
||||
>
|
||||
<!-- <i class="el-icon-upload2"></i> -->
|
||||
<div
|
||||
id="upload-area"
|
||||
:class="{ 'is-dragover': dragover, uploading: showProgress, linux: os === 'linux' }" @drop.prevent="onDrop" @dragover.prevent="dragover = true" @dragleave.prevent="dragover = false"
|
||||
:style="{ backgroundPosition: '0 ' + progress + '%'}"
|
||||
>
|
||||
<div id="upload-dragger" @dblclick="openUploadWindow">
|
||||
<input type="file" id="file-uploader" @change="onChange" multiple>
|
||||
<div
|
||||
id="upload-area"
|
||||
:class="{ 'is-dragover': dragover, uploading: showProgress, linux: os === 'linux' }"
|
||||
:style="{ backgroundPosition: '0 ' + progress + '%'}"
|
||||
@drop.prevent="onDrop"
|
||||
@dragover.prevent="dragover = true"
|
||||
@dragleave.prevent="dragover = false"
|
||||
>
|
||||
<div
|
||||
id="upload-dragger"
|
||||
@dblclick="openUploadWindow"
|
||||
>
|
||||
<input
|
||||
id="file-uploader"
|
||||
type="file"
|
||||
multiple
|
||||
@change="onChange"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import mixin from '@/utils/mixin'
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
<script lang="ts" setup>
|
||||
// import mixin from '@/utils/mixin'
|
||||
// import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
import { onBeforeUnmount, onBeforeMount, ref, watch } from 'vue'
|
||||
import { SHOW_MINI_PAGE_MENU, SET_MINI_WINDOW_POS } from '~/universal/events/constants'
|
||||
import {
|
||||
isUrl
|
||||
} from '~/universal/utils/common'
|
||||
@Component({
|
||||
name: 'mini-page',
|
||||
mixins: [mixin]
|
||||
})
|
||||
export default class extends Vue {
|
||||
logo = require('../assets/squareLogo.png')
|
||||
dragover = false
|
||||
progress = 0
|
||||
showProgress = false
|
||||
showError = false
|
||||
dragging = false
|
||||
wX: number = -1
|
||||
wY: number = -1
|
||||
screenX: number = -1
|
||||
screenY: number = -1
|
||||
menu: Electron.Menu | null = null
|
||||
os = ''
|
||||
picBed: IPicBedType[] = []
|
||||
created () {
|
||||
this.os = process.platform
|
||||
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => {
|
||||
if (progress !== -1) {
|
||||
this.showProgress = true
|
||||
this.progress = progress
|
||||
} else {
|
||||
this.progress = 100
|
||||
this.showError = true
|
||||
}
|
||||
})
|
||||
this.getPicBeds()
|
||||
}
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
const logo = require('../assets/squareLogo.png')
|
||||
const dragover = ref(false)
|
||||
const progress = ref(0)
|
||||
const showProgress = ref(false)
|
||||
const showError = ref(false)
|
||||
const dragging = ref(false)
|
||||
const wX = ref(-1)
|
||||
const wY = ref(-1)
|
||||
const screenX = ref(-1)
|
||||
const screenY = ref(-1)
|
||||
const os = ref('')
|
||||
|
||||
mounted () {
|
||||
window.addEventListener('mousedown', this.handleMouseDown, false)
|
||||
window.addEventListener('mousemove', this.handleMouseMove, false)
|
||||
window.addEventListener('mouseup', this.handleMouseUp, false)
|
||||
}
|
||||
|
||||
@Watch('progress')
|
||||
onProgressChange (val: number) {
|
||||
if (val === 100) {
|
||||
setTimeout(() => {
|
||||
this.showProgress = false
|
||||
this.showError = false
|
||||
}, 1000)
|
||||
setTimeout(() => {
|
||||
this.progress = 0
|
||||
}, 1200)
|
||||
onBeforeMount(() => {
|
||||
os.value = process.platform
|
||||
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, _progress: number) => {
|
||||
if (_progress !== -1) {
|
||||
showProgress.value = true
|
||||
progress.value = _progress
|
||||
} else {
|
||||
progress.value = 100
|
||||
showError.value = true
|
||||
}
|
||||
}
|
||||
})
|
||||
window.addEventListener('mousedown', handleMouseDown, false)
|
||||
window.addEventListener('mousemove', handleMouseMove, false)
|
||||
window.addEventListener('mouseup', handleMouseUp, false)
|
||||
})
|
||||
|
||||
getPicBeds () {
|
||||
this.picBed = ipcRenderer.sendSync('getPicBeds')
|
||||
watch(progress, (val) => {
|
||||
if (val === 100) {
|
||||
setTimeout(() => {
|
||||
showProgress.value = false
|
||||
showError.value = false
|
||||
}, 1000)
|
||||
setTimeout(() => {
|
||||
progress.value = 0
|
||||
}, 1200)
|
||||
}
|
||||
})
|
||||
|
||||
onDrop (e: DragEvent) {
|
||||
this.dragover = false
|
||||
const items = e.dataTransfer!.items
|
||||
function onDrop (e: DragEvent) {
|
||||
dragover.value = false
|
||||
const items = e.dataTransfer?.items!
|
||||
const files = e.dataTransfer?.files!
|
||||
|
||||
// send files first
|
||||
if (files?.length) {
|
||||
ipcSendFiles(e.dataTransfer?.files!)
|
||||
} else {
|
||||
if (items.length === 2 && items[0].type === 'text/uri-list') {
|
||||
this.handleURLDrag(items, e.dataTransfer!)
|
||||
handleURLDrag(items, e.dataTransfer!)
|
||||
} else if (items[0].type === 'text/plain') {
|
||||
const str = e.dataTransfer!.getData(items[0].type)
|
||||
if (isUrl(str)) {
|
||||
ipcRenderer.send('uploadChoosedFiles', [{ path: str }])
|
||||
sendToMain('uploadChoosedFiles', [{ path: str }])
|
||||
} else {
|
||||
this.$message.error(this.$T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
|
||||
$message.error($T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
|
||||
}
|
||||
} else {
|
||||
this.ipcSendFiles(e.dataTransfer!.files)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) {
|
||||
// text/html
|
||||
// Use this data to get a more precise URL
|
||||
const urlString = dataTransfer.getData(items[1].type)
|
||||
const urlMatch = urlString.match(/<img.*src="(.*?)"/)
|
||||
if (urlMatch) {
|
||||
ipcRenderer.send('uploadChoosedFiles', [
|
||||
{
|
||||
path: urlMatch[1]
|
||||
}
|
||||
])
|
||||
} else {
|
||||
this.$message.error(this.$T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
|
||||
}
|
||||
}
|
||||
|
||||
openUploadWindow () {
|
||||
// @ts-ignore
|
||||
document.getElementById('file-uploader').click()
|
||||
}
|
||||
|
||||
onChange (e: any) {
|
||||
this.ipcSendFiles(e.target.files)
|
||||
// @ts-ignore
|
||||
document.getElementById('file-uploader').value = ''
|
||||
}
|
||||
|
||||
ipcSendFiles (files: FileList) {
|
||||
const sendFiles: IFileWithPath[] = []
|
||||
Array.from(files).forEach((item) => {
|
||||
const obj = {
|
||||
name: item.name,
|
||||
path: item.path
|
||||
function handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) {
|
||||
// text/html
|
||||
// Use this data to get a more precise URL
|
||||
const urlString = dataTransfer.getData(items[1].type)
|
||||
const urlMatch = urlString.match(/<img.*src="(.*?)"/)
|
||||
if (urlMatch) {
|
||||
sendToMain('uploadChoosedFiles', [
|
||||
{
|
||||
path: urlMatch[1]
|
||||
}
|
||||
sendFiles.push(obj)
|
||||
])
|
||||
} else {
|
||||
$message.error($T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
|
||||
}
|
||||
}
|
||||
|
||||
function openUploadWindow () {
|
||||
// @ts-ignore
|
||||
document.getElementById('file-uploader').click()
|
||||
}
|
||||
|
||||
function onChange (e: any) {
|
||||
ipcSendFiles(e.target.files)
|
||||
// @ts-ignore
|
||||
document.getElementById('file-uploader').value = ''
|
||||
}
|
||||
|
||||
function ipcSendFiles (files: FileList) {
|
||||
const sendFiles: IFileWithPath[] = []
|
||||
Array.from(files).forEach((item) => {
|
||||
const obj = {
|
||||
name: item.name,
|
||||
path: item.path
|
||||
}
|
||||
sendFiles.push(obj)
|
||||
})
|
||||
sendToMain('uploadChoosedFiles', sendFiles)
|
||||
}
|
||||
|
||||
function handleMouseDown (e: MouseEvent) {
|
||||
dragging.value = true
|
||||
wX.value = e.pageX
|
||||
wY.value = e.pageY
|
||||
screenX.value = e.screenX
|
||||
screenY.value = e.screenY
|
||||
}
|
||||
|
||||
function handleMouseMove (e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (dragging.value) {
|
||||
const xLoc = e.screenX - wX.value
|
||||
const yLoc = e.screenY - wY.value
|
||||
sendToMain(SET_MINI_WINDOW_POS, {
|
||||
x: xLoc,
|
||||
y: yLoc,
|
||||
width: 64,
|
||||
height: 64
|
||||
})
|
||||
ipcRenderer.send('uploadChoosedFiles', sendFiles)
|
||||
// remote.BrowserWindow.getFocusedWindow()!.setBounds({
|
||||
// x: xLoc,
|
||||
// y: yLoc,
|
||||
// width: 64,
|
||||
// height: 64
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown (e: MouseEvent) {
|
||||
this.dragging = true
|
||||
this.wX = e.pageX
|
||||
this.wY = e.pageY
|
||||
this.screenX = e.screenX
|
||||
this.screenY = e.screenY
|
||||
}
|
||||
|
||||
handleMouseMove (e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (this.dragging) {
|
||||
const xLoc = e.screenX - this.wX
|
||||
const yLoc = e.screenY - this.wY
|
||||
ipcRenderer.send(SET_MINI_WINDOW_POS, {
|
||||
x: xLoc,
|
||||
y: yLoc,
|
||||
width: 64,
|
||||
height: 64
|
||||
})
|
||||
// remote.BrowserWindow.getFocusedWindow()!.setBounds({
|
||||
// x: xLoc,
|
||||
// y: yLoc,
|
||||
// width: 64,
|
||||
// height: 64
|
||||
// })
|
||||
function handleMouseUp (e: MouseEvent) {
|
||||
dragging.value = false
|
||||
if (screenX.value === e.screenX && screenY.value === e.screenY) {
|
||||
if (e.button === 0) { // left mouse
|
||||
openUploadWindow()
|
||||
} else {
|
||||
openContextMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseUp (e: MouseEvent) {
|
||||
this.dragging = false
|
||||
if (this.screenX === e.screenX && this.screenY === e.screenY) {
|
||||
if (e.button === 0) { // left mouse
|
||||
this.openUploadWindow()
|
||||
} else {
|
||||
this.getPicBeds()
|
||||
this.openContextMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
function openContextMenu () {
|
||||
sendToMain(SHOW_MINI_PAGE_MENU)
|
||||
}
|
||||
|
||||
openContextMenu () {
|
||||
ipcRenderer.send(SHOW_MINI_PAGE_MENU)
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeAllListeners('uploadProgress')
|
||||
window.removeEventListener('mousedown', handleMouseDown, false)
|
||||
window.removeEventListener('mousemove', handleMouseMove, false)
|
||||
window.removeEventListener('mouseup', handleMouseUp, false)
|
||||
})
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('uploadProgress')
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
window.removeEventListener('mousedown', this.handleMouseDown, false)
|
||||
window.removeEventListener('mousemove', this.handleMouseMove, false)
|
||||
window.removeEventListener('mouseup', this.handleMouseUp, false)
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'MiniPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
|
||||
@@ -2,72 +2,142 @@
|
||||
<div id="plugin-view">
|
||||
<div class="view-title">
|
||||
{{ $T('PLUGIN_SETTINGS') }} -
|
||||
<el-tooltip :content="pluginListToolTip" placement="right">
|
||||
<i class="el-icon-goods" @click="goAwesomeList"></i>
|
||||
<el-tooltip
|
||||
:content="pluginListToolTip"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon
|
||||
class="el-icon-goods"
|
||||
@click="goAwesomeList"
|
||||
>
|
||||
<Goods />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="importLocalPluginToolTip" placement="left">
|
||||
<i class="el-icon-download" @click="handleImportLocalPlugin"/>
|
||||
<el-tooltip
|
||||
:content="importLocalPluginToolTip"
|
||||
placement="left"
|
||||
>
|
||||
<el-icon
|
||||
class="el-icon-download"
|
||||
@click="handleImportLocalPlugin"
|
||||
>
|
||||
<Download />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-row class="handle-bar" :class="{ 'cut-width': pluginList.length > 6 }">
|
||||
<el-row
|
||||
class="handle-bar"
|
||||
:class="{ 'cut-width': pluginList.length > 6 }"
|
||||
>
|
||||
<el-input
|
||||
v-model="searchText"
|
||||
:placeholder="$T('PLUGIN_SEARCH_PLACEHOLDER')"
|
||||
size="small"
|
||||
>
|
||||
<i slot="suffix" class="el-input__icon el-icon-close" v-if="searchText" @click="cleanSearch" style="cursor: pointer"></i>
|
||||
<template #suffix>
|
||||
<el-icon
|
||||
class="el-input__icon"
|
||||
style="cursor: pointer;"
|
||||
@click="cleanSearch"
|
||||
>
|
||||
<close />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="plugin-list" v-loading="loading">
|
||||
<el-col :span="12" v-for="item in pluginList" :key="item.fullName">
|
||||
<div class="plugin-item" :class="{ 'darwin': os === 'darwin' }">
|
||||
<div class="cli-only-badge" v-if="!item.gui" title="CLI only">CLI</div>
|
||||
<img class="plugin-item__logo" :src="item.logo"
|
||||
<el-row
|
||||
v-loading="loading"
|
||||
:gutter="10"
|
||||
class="plugin-list"
|
||||
>
|
||||
<el-col
|
||||
v-for="item in pluginList"
|
||||
:key="item.fullName"
|
||||
class="plugin-item__container"
|
||||
:span="12"
|
||||
>
|
||||
<div
|
||||
class="plugin-item"
|
||||
:class="{ 'darwin': os === 'darwin' }"
|
||||
>
|
||||
<div
|
||||
v-if="!item.gui"
|
||||
class="cli-only-badge"
|
||||
title="CLI only"
|
||||
>
|
||||
CLI
|
||||
</div>
|
||||
<img
|
||||
class="plugin-item__logo"
|
||||
:src="item.logo"
|
||||
:onerror="defaultLogo"
|
||||
>
|
||||
<div
|
||||
class="plugin-item__content"
|
||||
:class="{ disabled: !item.enabled }"
|
||||
>
|
||||
<div class="plugin-item__name" @click="openHomepage(item.homepage)">
|
||||
<div
|
||||
class="plugin-item__name"
|
||||
@click="openHomepage(item.homepage)"
|
||||
>
|
||||
{{ item.name }} <small>{{ ' ' + item.version }}</small>
|
||||
</div>
|
||||
<div class="plugin-item__desc" :title="item.description">
|
||||
<div
|
||||
class="plugin-item__desc"
|
||||
:title="item.description"
|
||||
>
|
||||
{{ item.description }}
|
||||
</div>
|
||||
<div class="plugin-item__info-bar">
|
||||
<span class="plugin-item__author">
|
||||
{{ item.author }}
|
||||
</span>
|
||||
<span class="plugin-item__config" >
|
||||
<span class="plugin-item__config">
|
||||
<template v-if="searchText">
|
||||
<template v-if="!item.hasInstall">
|
||||
<span class="config-button install" v-if="!item.ing" @click="installPlugin(item)">
|
||||
<span
|
||||
v-if="!item.ing"
|
||||
class="config-button install"
|
||||
@click="installPlugin(item)"
|
||||
>
|
||||
{{ $T('PLUGIN_INSTALL') }}
|
||||
</span>
|
||||
<span v-else-if="item.ing" class="config-button ing">
|
||||
<span
|
||||
v-else-if="item.ing"
|
||||
class="config-button ing"
|
||||
>
|
||||
{{ $T('PLUGIN_INSTALLING') }}
|
||||
</span>
|
||||
</template>
|
||||
<span v-else class="config-button ing">
|
||||
<span
|
||||
v-else
|
||||
class="config-button ing"
|
||||
>
|
||||
{{ $T('PLUGIN_INSTALLED') }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-if="item.ing" class="config-button ing">
|
||||
<span
|
||||
v-if="item.ing"
|
||||
class="config-button ing"
|
||||
>
|
||||
{{ $T('PLUGIN_DOING_SOMETHING') }}
|
||||
</span>
|
||||
<template v-else>
|
||||
<i
|
||||
<el-icon
|
||||
v-if="item.enabled"
|
||||
class="el-icon-setting"
|
||||
@click="buildContextMenu(item)"
|
||||
></i>
|
||||
<i
|
||||
>
|
||||
<Setting />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
v-else
|
||||
class="el-icon-remove-outline"
|
||||
@click="buildContextMenu(item)"
|
||||
></i>
|
||||
>
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</template>
|
||||
</template>
|
||||
</span>
|
||||
@@ -76,11 +146,23 @@
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-show="needReload" class="reload-mask" :class="{ 'cut-width': pluginList.length > 6 }">
|
||||
<el-button type="primary" @click="reloadApp" size="mini" round>{{ $T('TIPS_NEED_RELOAD') }}</el-button>
|
||||
<el-row
|
||||
v-show="needReload"
|
||||
class="reload-mask"
|
||||
:class="{ 'cut-width': pluginList.length > 6 }"
|
||||
justify="center"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
round
|
||||
@click="reloadApp"
|
||||
>
|
||||
{{ $T('TIPS_NEED_RELOAD') }}
|
||||
</el-button>
|
||||
</el-row>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
v-model="dialogVisible"
|
||||
:modal-append-to-body="false"
|
||||
:title="$T('CONFIG_THING', {
|
||||
c: configName
|
||||
@@ -88,27 +170,35 @@
|
||||
width="70%"
|
||||
>
|
||||
<config-form
|
||||
:id="configName"
|
||||
ref="$configForm"
|
||||
:config="config"
|
||||
:type="currentType"
|
||||
:id="configName"
|
||||
ref="configForm"
|
||||
>
|
||||
</config-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="dialogVisible = false" round>{{ $T('CANCEL') }}</el-button>
|
||||
<el-button type="primary" @click="handleConfirmConfig" round>{{ $T('CONFIRM') }}</el-button>
|
||||
</span>
|
||||
color-mode="white"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button
|
||||
round
|
||||
@click="dialogVisible = false"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
@click="handleConfirmConfig"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {
|
||||
Component,
|
||||
Vue,
|
||||
Watch
|
||||
} from 'vue-property-decorator'
|
||||
<script lang="ts" setup>
|
||||
import { Close, Download, Goods, Remove, Setting } from '@element-plus/icons-vue'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import ConfigForm from '@/components/ConfigForm.vue'
|
||||
import { debounce } from 'lodash'
|
||||
import { debounce, DebouncedFunc } from 'lodash'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
@@ -116,340 +206,341 @@ import {
|
||||
import { handleStreamlinePluginName } from '~/universal/utils/common'
|
||||
import {
|
||||
OPEN_URL,
|
||||
RELOAD_APP,
|
||||
PICGO_CONFIG_PLUGIN,
|
||||
PICGO_HANDLE_PLUGIN_ING,
|
||||
PICGO_TOGGLE_PLUGIN,
|
||||
SHOW_PLUGIN_PAGE_MENU,
|
||||
DEFAULT_LOGO
|
||||
GET_PICBEDS,
|
||||
PICGO_HANDLE_PLUGIN_DONE
|
||||
} from '#/events/constants'
|
||||
import { computed, ref, onBeforeMount, onBeforeUnmount, watch } from 'vue'
|
||||
import { getConfig, saveConfig, sendRPC, sendToMain } from '@/utils/dataSender'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
const $confirm = ElMessageBox.confirm
|
||||
const searchText = ref('')
|
||||
const pluginList = ref<IPicGoPlugin[]>([])
|
||||
const config = ref<any[]>([])
|
||||
const currentType = ref<'plugin' | 'uploader' | 'transformer'>('plugin')
|
||||
const configName = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const pluginNameList = ref<string[]>([])
|
||||
const loading = ref(true)
|
||||
const needReload = ref(false)
|
||||
const pluginListToolTip = $T('PLUGIN_LIST')
|
||||
const importLocalPluginToolTip = $T('PLUGIN_IMPORT_LOCAL')
|
||||
// const id = ref('')
|
||||
const os = ref('')
|
||||
const defaultLogo = ref(`this.src="file://${__static.replace(/\\/g, '/')}/roundLogo.png"`)
|
||||
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
|
||||
const npmSearchText = computed(() => {
|
||||
return searchText.value.match('picgo-plugin-')
|
||||
? searchText.value
|
||||
: searchText.value !== ''
|
||||
? `picgo-plugin-${searchText.value}`
|
||||
: searchText.value
|
||||
})
|
||||
let getSearchResult: DebouncedFunc<(val: string) => void>
|
||||
|
||||
@Component({
|
||||
name: 'plugin',
|
||||
components: {
|
||||
ConfigForm
|
||||
watch(npmSearchText, (val: string) => {
|
||||
if (val) {
|
||||
loading.value = true
|
||||
pluginList.value = []
|
||||
getSearchResult(val)
|
||||
} else {
|
||||
getPluginList()
|
||||
}
|
||||
})
|
||||
export default class extends Vue {
|
||||
searchText = ''
|
||||
pluginList: IPicGoPlugin[] = []
|
||||
menu: Electron.Menu | null = null
|
||||
config: any[] = []
|
||||
currentType = ''
|
||||
configName = ''
|
||||
dialogVisible = false
|
||||
pluginNameList: string[] = []
|
||||
loading = true
|
||||
needReload = false
|
||||
pluginListToolTip = this.$T('PLUGIN_LIST')
|
||||
importLocalPluginToolTip = this.$T('PLUGIN_IMPORT_LOCAL')
|
||||
id = ''
|
||||
os = ''
|
||||
defaultLogo: string = ''
|
||||
get npmSearchText () {
|
||||
return this.searchText.match('picgo-plugin-')
|
||||
? this.searchText
|
||||
: this.searchText !== ''
|
||||
? `picgo-plugin-${this.searchText}`
|
||||
: this.searchText
|
||||
}
|
||||
|
||||
@Watch('npmSearchText')
|
||||
onNpmSearchTextChange (val: string) {
|
||||
if (val) {
|
||||
this.loading = true
|
||||
this.pluginList = []
|
||||
this.getSearchResult(val)
|
||||
} else {
|
||||
this.getPluginList()
|
||||
}
|
||||
watch(dialogVisible, (val: boolean) => {
|
||||
if (val) {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 101
|
||||
} else {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 10
|
||||
}
|
||||
})
|
||||
|
||||
@Watch('dialogVisible')
|
||||
onDialogVisible (val: boolean) {
|
||||
if (val) {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 101
|
||||
} else {
|
||||
// @ts-ignore
|
||||
document.querySelector('.main-content.el-row').style.zIndex = 10
|
||||
}
|
||||
}
|
||||
|
||||
async created () {
|
||||
this.os = process.platform
|
||||
ipcRenderer.on('hideLoading', () => {
|
||||
this.loading = false
|
||||
})
|
||||
ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
|
||||
this.pluginList = list
|
||||
this.pluginNameList = list.map(item => item.fullName)
|
||||
this.loading = false
|
||||
})
|
||||
ipcRenderer.on('installPlugin', (evt: IpcRendererEvent, { success, body }: {
|
||||
success: boolean,
|
||||
body: string
|
||||
}) => {
|
||||
this.loading = false
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.fullName === body) {
|
||||
item.ing = false
|
||||
item.hasInstall = success
|
||||
}
|
||||
})
|
||||
})
|
||||
ipcRenderer.on('updateSuccess', (evt: IpcRendererEvent, plugin: string) => {
|
||||
this.loading = false
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.fullName === plugin) {
|
||||
item.ing = false
|
||||
item.hasInstall = true
|
||||
}
|
||||
this.getPicBeds()
|
||||
})
|
||||
this.handleReload()
|
||||
this.getPluginList()
|
||||
})
|
||||
ipcRenderer.on('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => {
|
||||
this.loading = false
|
||||
this.pluginList = this.pluginList.filter(item => {
|
||||
if (item.fullName === plugin) { // restore Uploader & Transformer after uninstalling
|
||||
if (item.config.transformer.name) {
|
||||
this.handleRestoreState('transformer', item.config.transformer.name)
|
||||
}
|
||||
if (item.config.uploader.name) {
|
||||
this.handleRestoreState('uploader', item.config.uploader.name)
|
||||
}
|
||||
this.getPicBeds()
|
||||
}
|
||||
return item.fullName !== plugin
|
||||
})
|
||||
this.pluginNameList = this.pluginNameList.filter(item => item !== plugin)
|
||||
})
|
||||
ipcRenderer.on(PICGO_CONFIG_PLUGIN, (evt: IpcRendererEvent, currentType: string, configName: string, config: any) => {
|
||||
this.currentType = currentType
|
||||
this.configName = configName
|
||||
this.dialogVisible = true
|
||||
this.config = config
|
||||
})
|
||||
ipcRenderer.on(PICGO_HANDLE_PLUGIN_ING, (evt: IpcRendererEvent, fullName: string) => {
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.fullName === fullName || (item.name === fullName)) {
|
||||
item.ing = true
|
||||
}
|
||||
})
|
||||
this.loading = true
|
||||
})
|
||||
ipcRenderer.on(PICGO_TOGGLE_PLUGIN, (evt: IpcRendererEvent, fullName: string, enabled: boolean) => {
|
||||
const plugin = this.pluginList.find(item => item.fullName === fullName)
|
||||
if (plugin) {
|
||||
plugin.enabled = enabled
|
||||
this.getPicBeds()
|
||||
this.needReload = true
|
||||
onBeforeMount(async () => {
|
||||
os.value = process.platform
|
||||
ipcRenderer.on('hideLoading', () => {
|
||||
loading.value = false
|
||||
})
|
||||
ipcRenderer.on(PICGO_HANDLE_PLUGIN_DONE, (evt: IpcRendererEvent, fullName: string) => {
|
||||
pluginList.value.forEach(item => {
|
||||
if (item.fullName === fullName || (item.name === fullName)) {
|
||||
item.ing = false
|
||||
}
|
||||
})
|
||||
ipcRenderer.on(DEFAULT_LOGO, (evt: IpcRendererEvent, logoPath) => {
|
||||
this.defaultLogo = `this.src="${logoPath.replace(/\\/g, '/')}"`
|
||||
loading.value = false
|
||||
})
|
||||
ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
|
||||
pluginList.value = list
|
||||
pluginNameList.value = list.map(item => item.fullName)
|
||||
loading.value = false
|
||||
})
|
||||
ipcRenderer.on('installPlugin', (evt: IpcRendererEvent, { success, body }: {
|
||||
success: boolean,
|
||||
body: string
|
||||
}) => {
|
||||
loading.value = false
|
||||
pluginList.value.forEach(item => {
|
||||
if (item.fullName === body) {
|
||||
item.ing = false
|
||||
item.hasInstall = success
|
||||
}
|
||||
})
|
||||
this.getPluginList()
|
||||
this.getSearchResult = debounce(this.getSearchResult, 50)
|
||||
this.getDefaultLogo()
|
||||
this.needReload = await this.getConfig<boolean>('needReload') || false
|
||||
}
|
||||
|
||||
getDefaultLogo () {
|
||||
ipcRenderer.send(DEFAULT_LOGO)
|
||||
}
|
||||
|
||||
async buildContextMenu (plugin: IPicGoPlugin) {
|
||||
ipcRenderer.send(SHOW_PLUGIN_PAGE_MENU, plugin)
|
||||
}
|
||||
|
||||
getPluginList () {
|
||||
ipcRenderer.send('getPluginList')
|
||||
}
|
||||
|
||||
getPicBeds () {
|
||||
ipcRenderer.send('getPicBeds')
|
||||
}
|
||||
|
||||
installPlugin (item: IPicGoPlugin) {
|
||||
if (!item.gui) {
|
||||
this.$confirm(this.$T('TIPS_PLUGIN_NOT_GUI_IMPLEMENT'), this.$T('TIPS_NOTICE'), {
|
||||
confirmButtonText: this.$T('CONFIRM'),
|
||||
cancelButtonText: this.$T('CANCEL'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
})
|
||||
ipcRenderer.on('updateSuccess', (evt: IpcRendererEvent, plugin: string) => {
|
||||
loading.value = false
|
||||
pluginList.value.forEach(item => {
|
||||
if (item.fullName === plugin) {
|
||||
item.ing = false
|
||||
item.hasInstall = true
|
||||
}
|
||||
getPicBeds()
|
||||
})
|
||||
handleReload()
|
||||
getPluginList()
|
||||
})
|
||||
ipcRenderer.on('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => {
|
||||
loading.value = false
|
||||
pluginList.value = pluginList.value.filter(item => {
|
||||
if (item.fullName === plugin) { // restore Uploader & Transformer after uninstalling
|
||||
if (item.config.transformer.name) {
|
||||
handleRestoreState('transformer', item.config.transformer.name)
|
||||
}
|
||||
if (item.config.uploader.name) {
|
||||
handleRestoreState('uploader', item.config.uploader.name)
|
||||
}
|
||||
getPicBeds()
|
||||
}
|
||||
return item.fullName !== plugin
|
||||
})
|
||||
pluginNameList.value = pluginNameList.value.filter(item => item !== plugin)
|
||||
})
|
||||
ipcRenderer.on(PICGO_CONFIG_PLUGIN, (evt: IpcRendererEvent, _currentType: 'plugin' | 'transformer' | 'uploader', _configName: string, _config: any) => {
|
||||
currentType.value = _currentType
|
||||
configName.value = _configName
|
||||
dialogVisible.value = true
|
||||
config.value = _config
|
||||
})
|
||||
ipcRenderer.on(PICGO_HANDLE_PLUGIN_ING, (evt: IpcRendererEvent, fullName: string) => {
|
||||
pluginList.value.forEach(item => {
|
||||
if (item.fullName === fullName || (item.name === fullName)) {
|
||||
item.ing = true
|
||||
ipcRenderer.send('installPlugin', item.fullName)
|
||||
}).catch(() => {
|
||||
console.log('Install canceled')
|
||||
})
|
||||
} else {
|
||||
}
|
||||
})
|
||||
loading.value = true
|
||||
})
|
||||
ipcRenderer.on(PICGO_TOGGLE_PLUGIN, (evt: IpcRendererEvent, fullName: string, enabled: boolean) => {
|
||||
const plugin = pluginList.value.find(item => item.fullName === fullName)
|
||||
if (plugin) {
|
||||
plugin.enabled = enabled
|
||||
getPicBeds()
|
||||
needReload.value = true
|
||||
}
|
||||
})
|
||||
getPluginList()
|
||||
getSearchResult = debounce(_getSearchResult, 50)
|
||||
needReload.value = await getConfig<boolean>('needReload') || false
|
||||
})
|
||||
|
||||
async function buildContextMenu (plugin: IPicGoPlugin) {
|
||||
sendToMain(SHOW_PLUGIN_PAGE_MENU, plugin)
|
||||
}
|
||||
|
||||
function getPluginList () {
|
||||
sendToMain('getPluginList')
|
||||
}
|
||||
|
||||
function getPicBeds () {
|
||||
sendToMain(GET_PICBEDS)
|
||||
}
|
||||
|
||||
function installPlugin (item: IPicGoPlugin) {
|
||||
if (!item.gui) {
|
||||
$confirm($T('TIPS_PLUGIN_NOT_GUI_IMPLEMENT'), $T('TIPS_NOTICE'), {
|
||||
confirmButtonText: $T('CONFIRM'),
|
||||
cancelButtonText: $T('CANCEL'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
item.ing = true
|
||||
ipcRenderer.send('installPlugin', item.fullName)
|
||||
sendToMain('installPlugin', item.fullName)
|
||||
}).catch(() => {
|
||||
console.log('Install canceled')
|
||||
})
|
||||
} else {
|
||||
item.ing = true
|
||||
sendToMain('installPlugin', item.fullName)
|
||||
}
|
||||
}
|
||||
|
||||
// function uninstallPlugin (val: string) {
|
||||
// pluginList.value.forEach(item => {
|
||||
// if (item.name === val) {
|
||||
// item.ing = true
|
||||
// }
|
||||
// })
|
||||
// loading.value = true
|
||||
// sendToMain('uninstallPlugin', val)
|
||||
// }
|
||||
|
||||
// function updatePlugin (val: string) {
|
||||
// pluginList.value.forEach(item => {
|
||||
// if (item.fullName === val) {
|
||||
// item.ing = true
|
||||
// }
|
||||
// })
|
||||
// loading.value = true
|
||||
// sendToMain('updatePlugin', val)
|
||||
// }
|
||||
|
||||
function reloadApp () {
|
||||
sendRPC(IRPCActionType.RELOAD_APP)
|
||||
}
|
||||
|
||||
async function handleReload () {
|
||||
saveConfig({
|
||||
needReload: true
|
||||
})
|
||||
needReload.value = true
|
||||
const successNotification = new Notification($T('PLUGIN_UPDATE_SUCCEED'), {
|
||||
body: $T('TIPS_NEED_RELOAD')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
reloadApp()
|
||||
}
|
||||
}
|
||||
|
||||
function cleanSearch () {
|
||||
searchText.value = ''
|
||||
}
|
||||
|
||||
async function handleConfirmConfig () {
|
||||
const result = (await $configForm.value?.validate() || false)
|
||||
if (result !== false) {
|
||||
switch (currentType.value) {
|
||||
case 'plugin':
|
||||
saveConfig({
|
||||
[`${configName.value}`]: result
|
||||
})
|
||||
break
|
||||
case 'uploader':
|
||||
saveConfig({
|
||||
[`picBed.${configName.value}`]: result
|
||||
})
|
||||
break
|
||||
case 'transformer':
|
||||
saveConfig({
|
||||
[`transformer.${configName.value}`]: result
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
uninstallPlugin (val: string) {
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.name === val) {
|
||||
item.ing = true
|
||||
}
|
||||
})
|
||||
this.loading = true
|
||||
ipcRenderer.send('uninstallPlugin', val)
|
||||
}
|
||||
|
||||
updatePlugin (val: string) {
|
||||
this.pluginList.forEach(item => {
|
||||
if (item.fullName === val) {
|
||||
item.ing = true
|
||||
}
|
||||
})
|
||||
this.loading = true
|
||||
ipcRenderer.send('updatePlugin', val)
|
||||
}
|
||||
|
||||
reloadApp () {
|
||||
ipcRenderer.send(RELOAD_APP)
|
||||
}
|
||||
|
||||
async handleReload () {
|
||||
this.saveConfig({
|
||||
needReload: true
|
||||
})
|
||||
this.needReload = true
|
||||
const successNotification = new Notification(this.$T('PLUGIN_UPDATE_SUCCEED'), {
|
||||
body: this.$T('TIPS_NEED_RELOAD')
|
||||
const successNotification = new Notification($T('SETTINGS_RESULT'), {
|
||||
body: $T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
this.reloadApp()
|
||||
return true
|
||||
}
|
||||
dialogVisible.value = false
|
||||
getPluginList()
|
||||
}
|
||||
}
|
||||
|
||||
cleanSearch () {
|
||||
this.searchText = ''
|
||||
}
|
||||
|
||||
async handleConfirmConfig () {
|
||||
// @ts-ignore
|
||||
const result = await this.$refs.configForm.validate()
|
||||
if (result !== false) {
|
||||
switch (this.currentType) {
|
||||
case 'plugin':
|
||||
this.saveConfig({
|
||||
[`${this.configName}`]: result
|
||||
})
|
||||
break
|
||||
case 'uploader':
|
||||
this.saveConfig({
|
||||
[`picBed.${this.configName}`]: result
|
||||
})
|
||||
break
|
||||
case 'transformer':
|
||||
this.saveConfig({
|
||||
[`transformer.${this.configName}`]: result
|
||||
})
|
||||
break
|
||||
}
|
||||
const successNotification = new Notification(this.$T('SETTINGS_RESULT'), {
|
||||
body: this.$T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.dialogVisible = false
|
||||
this.getPluginList()
|
||||
}
|
||||
}
|
||||
|
||||
getSearchResult (val: string) {
|
||||
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
|
||||
this.$http.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
|
||||
.then((res: INPMSearchResult) => {
|
||||
this.pluginList = res.data.objects
|
||||
.filter((item:INPMSearchResultObject) => {
|
||||
return item.package.name.includes('picgo-plugin-')
|
||||
})
|
||||
.map((item: INPMSearchResultObject) => {
|
||||
return this.handleSearchResult(item)
|
||||
})
|
||||
this.loading = false
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchResult (item: INPMSearchResultObject) {
|
||||
const name = handleStreamlinePluginName(item.package.name)
|
||||
let gui = false
|
||||
if (item.package.keywords && item.package.keywords.length > 0) {
|
||||
if (item.package.keywords.includes('picgo-gui-plugin')) {
|
||||
gui = true
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
fullName: item.package.name,
|
||||
author: item.package.author.name,
|
||||
description: item.package.description,
|
||||
logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`,
|
||||
config: {},
|
||||
homepage: item.package.links ? item.package.links.homepage : '',
|
||||
hasInstall: this.pluginNameList.some(plugin => plugin === item.package.name),
|
||||
version: item.package.version,
|
||||
gui,
|
||||
ing: false // installing or uninstalling
|
||||
}
|
||||
}
|
||||
|
||||
// restore Uploader & Transformer
|
||||
async handleRestoreState (item: string, name: string) {
|
||||
if (item === 'uploader') {
|
||||
const current = await this.getConfig('picBed.current')
|
||||
if (current === name) {
|
||||
this.saveConfig({
|
||||
'picBed.current': 'smms',
|
||||
'picBed.uploader': 'smms'
|
||||
function _getSearchResult (val: string) {
|
||||
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
|
||||
axios.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
|
||||
.then((res: INPMSearchResult) => {
|
||||
pluginList.value = res.data.objects
|
||||
.filter((item:INPMSearchResultObject) => {
|
||||
return item.package.name.includes('picgo-plugin-')
|
||||
})
|
||||
}
|
||||
}
|
||||
if (item === 'transformer') {
|
||||
const current = await this.getConfig('picBed.transformer')
|
||||
if (current === name) {
|
||||
this.saveConfig({
|
||||
'picBed.transformer': 'path'
|
||||
.map((item: INPMSearchResultObject) => {
|
||||
return handleSearchResult(item)
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleSearchResult (item: INPMSearchResultObject) {
|
||||
const name = handleStreamlinePluginName(item.package.name)
|
||||
let gui = false
|
||||
if (item.package.keywords && item.package.keywords.length > 0) {
|
||||
if (item.package.keywords.includes('picgo-gui-plugin')) {
|
||||
gui = true
|
||||
}
|
||||
}
|
||||
return {
|
||||
name,
|
||||
fullName: item.package.name,
|
||||
author: item.package.author.name,
|
||||
description: item.package.description,
|
||||
logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`,
|
||||
config: {},
|
||||
homepage: item.package.links ? item.package.links.homepage : '',
|
||||
hasInstall: pluginNameList.value.some(plugin => plugin === item.package.name),
|
||||
version: item.package.version,
|
||||
gui,
|
||||
ing: false // installing or uninstalling
|
||||
}
|
||||
}
|
||||
|
||||
openHomepage (url: string) {
|
||||
if (url) {
|
||||
ipcRenderer.send(OPEN_URL, url)
|
||||
// restore Uploader & Transformer
|
||||
async function handleRestoreState (item: string, name: string) {
|
||||
if (item === 'uploader') {
|
||||
const current = await getConfig('picBed.current')
|
||||
if (current === name) {
|
||||
saveConfig({
|
||||
'picBed.current': 'smms',
|
||||
'picBed.uploader': 'smms'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
goAwesomeList () {
|
||||
ipcRenderer.send(OPEN_URL, 'https://github.com/PicGo/Awesome-PicGo')
|
||||
if (item === 'transformer') {
|
||||
const current = await getConfig('picBed.transformer')
|
||||
if (current === name) {
|
||||
saveConfig({
|
||||
'picBed.transformer': 'path'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleImportLocalPlugin () {
|
||||
ipcRenderer.send('importLocalPlugin')
|
||||
this.loading = true
|
||||
function openHomepage (url: string) {
|
||||
if (url) {
|
||||
sendToMain(OPEN_URL, url)
|
||||
}
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('pluginList')
|
||||
ipcRenderer.removeAllListeners('installPlugin')
|
||||
ipcRenderer.removeAllListeners('uninstallSuccess')
|
||||
ipcRenderer.removeAllListeners('updateSuccess')
|
||||
ipcRenderer.removeAllListeners('hideLoading')
|
||||
ipcRenderer.removeAllListeners(DEFAULT_LOGO)
|
||||
}
|
||||
function goAwesomeList () {
|
||||
sendToMain(OPEN_URL, 'https://github.com/PicGo/Awesome-PicGo')
|
||||
}
|
||||
|
||||
function handleImportLocalPlugin () {
|
||||
sendToMain('importLocalPlugin')
|
||||
loading.value = true
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeAllListeners('pluginList')
|
||||
ipcRenderer.removeAllListeners('installPlugin')
|
||||
ipcRenderer.removeAllListeners('uninstallSuccess')
|
||||
ipcRenderer.removeAllListeners('updateSuccess')
|
||||
ipcRenderer.removeAllListeners('hideLoading')
|
||||
ipcRenderer.removeAllListeners(PICGO_HANDLE_PLUGIN_DONE)
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PluginPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
@@ -460,6 +551,7 @@ $darwinBg = #172426
|
||||
.el-loading-mask
|
||||
background-color rgba(0, 0, 0, 0.8)
|
||||
.plugin-list
|
||||
align-content flex-start
|
||||
height: 339px;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 15px;
|
||||
@@ -480,6 +572,7 @@ $darwinBg = #172426
|
||||
margin 10px auto
|
||||
position relative
|
||||
i.el-icon-goods
|
||||
margin-left 4px
|
||||
font-size 20px
|
||||
vertical-align middle
|
||||
cursor pointer
|
||||
@@ -509,8 +602,10 @@ $darwinBg = #172426
|
||||
padding 8px
|
||||
user-select text
|
||||
transition all .2s ease-in-out
|
||||
margin-bottom 10px
|
||||
position relative
|
||||
&__container
|
||||
height 80px
|
||||
margin-bottom 10px
|
||||
.cli-only-badge
|
||||
position absolute
|
||||
right 0px
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div id="rename-page">
|
||||
<el-form
|
||||
@submit.native.prevent
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('FILE_RENAME')"
|
||||
@@ -9,54 +9,67 @@
|
||||
<el-input
|
||||
v-model="fileName"
|
||||
size="small"
|
||||
@keyup.enter.native="confirmName"
|
||||
></el-input>
|
||||
@keyup.enter="confirmName"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row>
|
||||
<div class="pull-right">
|
||||
<el-button @click="cancel" round size="mini">{{ $T('CANCEL') }}</el-button>
|
||||
<el-button type="primary" @click="confirmName" round size="mini">{{ $T('CONFIRM') }}</el-button>
|
||||
<el-button
|
||||
round
|
||||
size="small"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="confirmName"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { RENAME_FILE_NAME } from '#/events/constants'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/mixin'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
@Component({
|
||||
name: 'rename-page',
|
||||
mixins: [mixin]
|
||||
import { onBeforeUnmount, onBeforeMount, ref } from 'vue'
|
||||
const fileName = ref('')
|
||||
const originName = ref('')
|
||||
const id = ref<string | null>(null)
|
||||
onBeforeMount(() => {
|
||||
ipcRenderer.on(RENAME_FILE_NAME, (event: IpcRendererEvent, newName: string, _originName: string, _id: string) => {
|
||||
fileName.value = newName
|
||||
originName.value = _originName
|
||||
id.value = _id
|
||||
})
|
||||
})
|
||||
export default class extends Vue {
|
||||
fileName: string = ''
|
||||
originName: string = ''
|
||||
id: string | null = null
|
||||
created () {
|
||||
ipcRenderer.on(RENAME_FILE_NAME, (event: IpcRendererEvent, newName: string, originName: string, id: string) => {
|
||||
this.fileName = newName
|
||||
this.originName = originName
|
||||
this.id = id
|
||||
})
|
||||
}
|
||||
|
||||
confirmName () {
|
||||
ipcRenderer.send(`${RENAME_FILE_NAME}${this.id}`, this.fileName)
|
||||
}
|
||||
function confirmName () {
|
||||
sendToMain(`${RENAME_FILE_NAME}${id.value}`, fileName.value)
|
||||
}
|
||||
|
||||
cancel () {
|
||||
// if cancel, use origin file name
|
||||
ipcRenderer.send(`${RENAME_FILE_NAME}${this.id}`, this.originName)
|
||||
}
|
||||
function cancel () {
|
||||
// if cancel, use origin file name
|
||||
sendToMain(`${RENAME_FILE_NAME}${id.value}`, originName.value)
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('rename')
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeAllListeners('rename')
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'RenamePage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
|
||||
@@ -4,18 +4,21 @@
|
||||
{{ $T('SETTINGS_SET_SHORTCUT') }}
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-col
|
||||
:span="20"
|
||||
:offset="2"
|
||||
>
|
||||
<el-table
|
||||
class="shortcut-page-table-border"
|
||||
:data="list"
|
||||
size="mini"
|
||||
size="small"
|
||||
header-cell-class-name="shortcut-page-table-border"
|
||||
cell-class-name="shortcut-page-table-border"
|
||||
>
|
||||
<el-table-column
|
||||
:label="$T('SHORTCUT_NAME')"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<template #default="scope">
|
||||
{{ scope.row.label ? scope.row.label : scope.row.name }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -23,14 +26,13 @@
|
||||
width="160px"
|
||||
:label="$T('SHORTCUT_BIND')"
|
||||
prop="key"
|
||||
>
|
||||
</el-table-column>
|
||||
/>
|
||||
<el-table-column
|
||||
:label="$T('SHORTCUT_STATUS')"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
size="mini"
|
||||
size="small"
|
||||
:type="scope.row.enable ? 'success' : 'danger'"
|
||||
>
|
||||
{{ scope.row.enable ? $T('SHORTCUT_ENABLED') : $T('SHORTCUT_DISABLED') }}
|
||||
@@ -41,38 +43,43 @@
|
||||
:label="$T('SHORTCUT_SOURCE')"
|
||||
width="100px"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<template #default="scope">
|
||||
{{ calcOriginShowName(scope.row.from) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$T('SHORTCUT_HANDLE')"
|
||||
width="100px"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@click="toggleEnable(scope.row)"
|
||||
size="mini"
|
||||
:class="{
|
||||
disabled: scope.row.enable
|
||||
}"
|
||||
type="text">
|
||||
{{ scope.row.enable ? $T('SHORTCUT_DISABLE') : $T('SHORTCUT_ENABLE') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="edit"
|
||||
size="mini"
|
||||
@click="openKeyBindingDialog(scope.row, scope.$index)"
|
||||
type="text">
|
||||
{{ $T('SHORTCUT_EDIT') }}
|
||||
</el-button>
|
||||
<template #default="scope">
|
||||
<el-row>
|
||||
<el-button
|
||||
size="small"
|
||||
:class="{
|
||||
disabled: scope.row.enable
|
||||
}"
|
||||
type="text"
|
||||
@click="toggleEnable(scope.row)"
|
||||
>
|
||||
{{ scope.row.enable ? $T('SHORTCUT_DISABLE') : $T('SHORTCUT_ENABLE') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="edit"
|
||||
size="small"
|
||||
type="text"
|
||||
@click="openKeyBindingDialog(scope.row, scope.$index)"
|
||||
>
|
||||
{{ $T('SHORTCUT_EDIT') }}
|
||||
</el-button>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog
|
||||
v-model="keyBindingVisible"
|
||||
:title="$T('SHORTCUT_CHANGE_UPLOAD')"
|
||||
:visible.sync="keyBindingVisible"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<el-form
|
||||
@@ -81,101 +88,109 @@
|
||||
>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
class="align-center"
|
||||
@keydown.native.prevent="keyDetect($event)"
|
||||
v-model="shortKey"
|
||||
class="align-center"
|
||||
:autofocus="true"
|
||||
></el-input>
|
||||
@keydown.prevent="keyDetect($event as KeyboardEvent)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="cancelKeyBinding" round>
|
||||
<template #footer>
|
||||
<el-button
|
||||
round
|
||||
@click="cancelKeyBinding"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="confirmKeyBinding" round>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmKeyBinding"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import keyDetect from '@/utils/key-binding'
|
||||
<script lang="ts" setup>
|
||||
import keyBinding from '@/utils/key-binding'
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import { onBeforeUnmount, onBeforeMount, ref, watch } from 'vue'
|
||||
import { getConfig, sendToMain } from '@/utils/dataSender'
|
||||
|
||||
@Component({
|
||||
name: 'shortkey-page'
|
||||
const list = ref<IShortKeyConfig[]>([])
|
||||
const keyBindingVisible = ref(false)
|
||||
const command = ref('')
|
||||
const shortKey = ref('')
|
||||
const currentIndex = ref(0)
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const shortKeyConfig = (await getConfig<IShortKeyConfigs>('settings.shortKey'))!
|
||||
list.value = Object.keys(shortKeyConfig).map(item => {
|
||||
return {
|
||||
...shortKeyConfig[item],
|
||||
from: calcOrigin(item)
|
||||
}
|
||||
})
|
||||
})
|
||||
export default class extends Vue {
|
||||
list: IShortKeyConfig[] = []
|
||||
keyBindingVisible = false
|
||||
command = ''
|
||||
shortKey = ''
|
||||
currentIndex = 0
|
||||
async created () {
|
||||
const shortKeyConfig = (await this.getConfig<IShortKeyConfigs>('settings.shortKey'))!
|
||||
this.list = Object.keys(shortKeyConfig).map(item => {
|
||||
return {
|
||||
...shortKeyConfig[item],
|
||||
from: this.calcOrigin(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Watch('keyBindingVisible')
|
||||
onKeyBindingVisibleChange (val: boolean) {
|
||||
ipcRenderer.send(TOGGLE_SHORTKEY_MODIFIED_MODE, val)
|
||||
}
|
||||
watch(keyBindingVisible, (val: boolean) => {
|
||||
sendToMain(TOGGLE_SHORTKEY_MODIFIED_MODE, val)
|
||||
})
|
||||
|
||||
calcOrigin (item: string) {
|
||||
const [origin] = item.split(':')
|
||||
return origin
|
||||
}
|
||||
function calcOrigin (item: string) {
|
||||
const [origin] = item.split(':')
|
||||
return origin
|
||||
}
|
||||
|
||||
calcOriginShowName (item: string) {
|
||||
return item.replace('picgo-plugin-', '')
|
||||
}
|
||||
function calcOriginShowName (item: string) {
|
||||
return item.replace('picgo-plugin-', '')
|
||||
}
|
||||
|
||||
toggleEnable (item: IShortKeyConfig) {
|
||||
const status = !item.enable
|
||||
item.enable = status
|
||||
ipcRenderer.send('bindOrUnbindShortKey', item, item.from)
|
||||
}
|
||||
function toggleEnable (item: IShortKeyConfig) {
|
||||
const status = !item.enable
|
||||
item.enable = status
|
||||
sendToMain('bindOrUnbindShortKey', item, item.from)
|
||||
}
|
||||
|
||||
keyDetect (event: KeyboardEvent) {
|
||||
this.shortKey = keyDetect(event).join('+')
|
||||
}
|
||||
function keyDetect (event: KeyboardEvent) {
|
||||
shortKey.value = keyBinding(event).join('+')
|
||||
}
|
||||
|
||||
async openKeyBindingDialog (config: IShortKeyConfig, index: number) {
|
||||
this.command = `${config.from}:${config.name}`
|
||||
this.shortKey = await this.getConfig(`settings.shortKey.${this.command}.key`) || ''
|
||||
this.currentIndex = index
|
||||
this.keyBindingVisible = true
|
||||
}
|
||||
async function openKeyBindingDialog (config: IShortKeyConfig, index: number) {
|
||||
command.value = `${config.from}:${config.name}`
|
||||
shortKey.value = await getConfig(`settings.shortKey.${command.value}.key`) || ''
|
||||
currentIndex.value = index
|
||||
keyBindingVisible.value = true
|
||||
}
|
||||
|
||||
async cancelKeyBinding () {
|
||||
this.keyBindingVisible = false
|
||||
this.shortKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`) || ''
|
||||
}
|
||||
async function cancelKeyBinding () {
|
||||
keyBindingVisible.value = false
|
||||
shortKey.value = await getConfig<string>(`settings.shortKey.${command.value}.key`) || ''
|
||||
}
|
||||
|
||||
async confirmKeyBinding () {
|
||||
const oldKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`)
|
||||
const config = Object.assign({}, this.list[this.currentIndex])
|
||||
config.key = this.shortKey
|
||||
ipcRenderer.send('updateShortKey', config, oldKey, config.from)
|
||||
ipcRenderer.once('updateShortKeyResponse', (evt: IpcRendererEvent, result) => {
|
||||
if (result) {
|
||||
this.keyBindingVisible = false
|
||||
this.list[this.currentIndex].key = this.shortKey
|
||||
}
|
||||
})
|
||||
}
|
||||
async function confirmKeyBinding () {
|
||||
const oldKey = await getConfig<string>(`settings.shortKey.${command.value}.key`)
|
||||
const config = Object.assign({}, list.value[currentIndex.value])
|
||||
config.key = shortKey.value
|
||||
sendToMain('updateShortKey', config, oldKey, config.from)
|
||||
ipcRenderer.once('updateShortKeyResponse', (evt: IpcRendererEvent, result) => {
|
||||
if (result) {
|
||||
keyBindingVisible.value = false
|
||||
list.value[currentIndex.value].key = shortKey.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.send(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
sendToMain(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
|
||||
})
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShortkeyPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
@@ -191,6 +206,9 @@ export default class extends Vue {
|
||||
color: #F56C6C
|
||||
&.edit
|
||||
color: #67C23A
|
||||
&--text
|
||||
padding-left 4px
|
||||
padding-right 4px
|
||||
.el-table
|
||||
background-color: transparent
|
||||
color #ddd
|
||||
@@ -209,4 +227,6 @@ export default class extends Vue {
|
||||
tr:hover
|
||||
&>td
|
||||
background #333
|
||||
.el-button+.el-button
|
||||
margin-left 4px
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<div class="toolbox">
|
||||
<el-row>
|
||||
<el-row
|
||||
class="toolbox-header"
|
||||
>
|
||||
<el-row>
|
||||
<img
|
||||
class="toolbox-header__logo"
|
||||
:src="defaultLogo"
|
||||
>
|
||||
<el-row class="toolbox-header__text">
|
||||
<el-row class="toolbox-header__title">
|
||||
{{ $T('TOOLBOX_TITLE') }}
|
||||
</el-row>
|
||||
<el-row class="toolbox-header__sub-title">
|
||||
{{ $T('TOOLBOX_SUB_TITLE') }}
|
||||
</el-row>
|
||||
</el-row>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<template v-if="progress !== 100">
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
:disabled="isLoading"
|
||||
@click="handleCheck"
|
||||
>
|
||||
{{ $T('TOOLBOX_START_SCAN') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else-if="isAllSuccess">
|
||||
<div class="toolbox-tips">
|
||||
{{ $T('TOOLBOX_SUCCESS_TIPS') }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="!isAllSuccess">
|
||||
<template v-if="canFixLength !== 0">
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
@click="handleFix"
|
||||
>
|
||||
{{ $T('TOOLBOX_START_FIX') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="toolbox-cant-fix toolbox-tips">
|
||||
{{ $T('TOOLBOX_CANT_AUTO_FIX') }}
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
class="toolbox-cant-fix__btn"
|
||||
@click="handleCheck"
|
||||
>
|
||||
{{ $T('TOOLBOX_RE_SCAN') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</el-row>
|
||||
</el-row>
|
||||
</el-row>
|
||||
<el-row class="progress">
|
||||
<el-progress
|
||||
:percentage="progress"
|
||||
:format="format"
|
||||
/>
|
||||
</el-row>
|
||||
<el-collapse
|
||||
v-model="activeTypes"
|
||||
accordion
|
||||
>
|
||||
<el-collapse-item
|
||||
v-for="(item, key) in fixList"
|
||||
:key="key"
|
||||
:name="key"
|
||||
>
|
||||
<template #title>
|
||||
{{ item.title }} <toolbox-status-icon :status="item.status" />
|
||||
</template>
|
||||
<div class="toolbox-item-msg">
|
||||
{{ item.msg || '' }}
|
||||
<template v-if="item.handler && item.handlerText && item.value">
|
||||
<toolbox-handler
|
||||
:value="item.value"
|
||||
:status="item.status"
|
||||
:handler="item.handler"
|
||||
:handler-text="item.handlerText"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useIPC } from '@/hooks/useIPC'
|
||||
import { sendRPC, triggerRPC } from '@/utils/dataSender'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { IToolboxItemType, IToolboxItemCheckStatus, IRPCActionType } from '~/universal/types/enum'
|
||||
import { T as $T } from '@/i18n'
|
||||
import ToolboxStatusIcon from '@/components/ToolboxStatusIcon.vue'
|
||||
import ToolboxHandler from '@/components/ToolboxHandler.vue'
|
||||
|
||||
const $confirm = ElMessageBox.confirm
|
||||
const defaultLogo = ref(`file://${__static.replace(/\\/g, '/')}/roundLogo.png`)
|
||||
const activeTypes = ref<IToolboxItemType[]>([])
|
||||
const fixList = reactive<IToolboxMap>({
|
||||
[IToolboxItemType.IS_CONFIG_FILE_BROKEN]: {
|
||||
title: $T('TOOLBOX_CHECK_CONFIG_FILE_BROKEN'),
|
||||
status: IToolboxItemCheckStatus.INIT,
|
||||
handlerText: $T('SETTINGS_OPEN_CONFIG_FILE'),
|
||||
handler (value: string) {
|
||||
sendRPC(IRPCActionType.OPEN_FILE, value)
|
||||
}
|
||||
},
|
||||
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: {
|
||||
title: $T('TOOLBOX_CHECK_GALLERY_FILE_BROKEN'),
|
||||
status: IToolboxItemCheckStatus.INIT
|
||||
},
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: {
|
||||
title: $T('TOOLBOX_CHECK_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD'), // picgo-image-clipboard folder
|
||||
status: IToolboxItemCheckStatus.INIT,
|
||||
handlerText: $T('OPEN_FILE_PATH'),
|
||||
handler (value: string) {
|
||||
sendRPC(IRPCActionType.OPEN_FILE, value)
|
||||
}
|
||||
},
|
||||
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: {
|
||||
title: $T('TOOLBOX_CHECK_PROBLEM_WITH_PROXY'),
|
||||
status: IToolboxItemCheckStatus.INIT,
|
||||
hasNoFixMethod: true
|
||||
}
|
||||
})
|
||||
|
||||
const progress = computed(() => {
|
||||
const total = Object.keys(fixList).length
|
||||
const done = Object.keys(fixList).filter(key => {
|
||||
const status = fixList[key as IToolboxItemType].status
|
||||
return status !== IToolboxItemCheckStatus.INIT && status !== IToolboxItemCheckStatus.LOADING
|
||||
}).length
|
||||
return done / total * 100
|
||||
})
|
||||
|
||||
const isAllSuccess = computed(() => {
|
||||
return Object.keys(fixList).every(key => {
|
||||
const status = fixList[key as IToolboxItemType].status
|
||||
return status === IToolboxItemCheckStatus.SUCCESS
|
||||
})
|
||||
})
|
||||
|
||||
const isLoading = computed(() => {
|
||||
return Object.keys(fixList).some(key => {
|
||||
const status = fixList[key as IToolboxItemType].status
|
||||
return status === IToolboxItemCheckStatus.LOADING
|
||||
})
|
||||
})
|
||||
|
||||
const canFixLength = computed(() => {
|
||||
return Object.keys(fixList).filter(key => {
|
||||
const status = fixList[key as IToolboxItemType].status
|
||||
return status === IToolboxItemCheckStatus.ERROR && !fixList[key as IToolboxItemType].hasNoFixMethod
|
||||
}).length
|
||||
})
|
||||
|
||||
const format = (percentage: number) => ''
|
||||
|
||||
const ipc = useIPC()
|
||||
|
||||
ipc.on(IRPCActionType.TOOLBOX_CHECK_RES, (event, { type, msg = '', status, value = '' }: IToolboxCheckRes) => {
|
||||
fixList[type].status = status
|
||||
fixList[type].msg = msg
|
||||
fixList[type].value = value
|
||||
if (status === IToolboxItemCheckStatus.ERROR) {
|
||||
activeTypes.value.push(type)
|
||||
}
|
||||
})
|
||||
|
||||
const handleCheck = () => {
|
||||
activeTypes.value = []
|
||||
Object.keys(fixList).forEach(key => {
|
||||
fixList[key as IToolboxItemType].status = IToolboxItemCheckStatus.LOADING
|
||||
fixList[key as IToolboxItemType].msg = ''
|
||||
fixList[key as IToolboxItemType].value = ''
|
||||
})
|
||||
sendRPC(IRPCActionType.TOOLBOX_CHECK)
|
||||
}
|
||||
|
||||
const handleFix = async () => {
|
||||
const fixRes = await Promise.all(Object.keys(fixList).filter(key => {
|
||||
const status = fixList[key as IToolboxItemType].status
|
||||
return status === IToolboxItemCheckStatus.ERROR && !fixList[key as IToolboxItemType].hasNoFixMethod
|
||||
}).map(async key => {
|
||||
return triggerRPC<IToolboxCheckRes>(IRPCActionType.TOOLBOX_CHECK_FIX, key as IToolboxItemType)
|
||||
}))
|
||||
|
||||
fixRes.filter(item => item !== null).forEach(item => {
|
||||
if (item) {
|
||||
fixList[item.type].status = item.status
|
||||
fixList[item.type].msg = item.msg
|
||||
fixList[item.type].value = item.value
|
||||
}
|
||||
})
|
||||
|
||||
$confirm($T('TOOLBOX_FIX_DONE_NEED_RELOAD'), $T('TIPS_NOTICE'), {
|
||||
confirmButtonText: $T('CONFIRM'),
|
||||
cancelButtonText: $T('CANCEL'),
|
||||
type: 'info'
|
||||
}).then(() => {
|
||||
sendRPC(IRPCActionType.RELOAD_APP)
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ToolBoxPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.toolbox
|
||||
padding 0 40px
|
||||
&-header
|
||||
width 100%
|
||||
color #eee
|
||||
justify-content space-between
|
||||
align-items center
|
||||
padding 20px 0px
|
||||
&__logo
|
||||
width 64px
|
||||
height 64px
|
||||
margin-right 20px
|
||||
&__text
|
||||
flex-direction column
|
||||
justify-content center
|
||||
&__title
|
||||
color #ddd
|
||||
font-size 20px
|
||||
margin-bottom 4px
|
||||
&__sub-title
|
||||
color #aaa
|
||||
font-size 16px
|
||||
.progress
|
||||
width 100%
|
||||
.el-progress--line
|
||||
width 100%
|
||||
.el-progress__text
|
||||
min-width 0
|
||||
.el-collapse
|
||||
margin-top 20px
|
||||
--el-collapse-border-color: #777;
|
||||
--el-collapse-header-height: 48px;
|
||||
--el-collapse-header-bg-color: transparent;
|
||||
--el-collapse-header-text-color: #ddd;
|
||||
--el-collapse-header-font-size: 13px;
|
||||
--el-collapse-content-bg-color: transparent;
|
||||
--el-collapse-content-font-size: 13px;
|
||||
--el-collapse-content-text-color: #ddd;
|
||||
&-item__content
|
||||
padding-bottom: 12px
|
||||
&-item-msg
|
||||
color: #aaa
|
||||
&-tips
|
||||
padding: 12px 0
|
||||
&-cant-fix
|
||||
display flex
|
||||
justify-content center
|
||||
align-items center
|
||||
&__btn
|
||||
margin-left: 8px
|
||||
</style>
|
||||
@@ -1,23 +1,59 @@
|
||||
<template>
|
||||
<div id="tray-page">
|
||||
<div class="open-main-window" @click="openSettingWindow">{{ $T('OPEN_MAIN_WINDOW') }}</div>
|
||||
<div
|
||||
class="open-main-window"
|
||||
@click="openSettingWindow"
|
||||
>
|
||||
{{ $T('OPEN_MAIN_WINDOW') }}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="wait-upload-img" v-if="clipboardFiles.length > 0">
|
||||
<div class="list-title">{{ $T('WAIT_TO_UPLOAD') }}</div>
|
||||
<div v-for="(item, index) in clipboardFiles" :key="index" class="img-list">
|
||||
<div
|
||||
v-if="clipboardFiles.length > 0"
|
||||
class="wait-upload-img"
|
||||
>
|
||||
<div class="list-title">
|
||||
{{ $T('WAIT_TO_UPLOAD') }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in clipboardFiles"
|
||||
:key="index"
|
||||
class="img-list"
|
||||
>
|
||||
<div
|
||||
class="upload-img__container"
|
||||
:class="{ upload: uploadFlag }"
|
||||
@click="uploadClipboardFiles">
|
||||
<img :src="item.imgUrl" class="upload-img">
|
||||
@click="uploadClipboardFiles"
|
||||
>
|
||||
<img
|
||||
:src="item.imgUrl"
|
||||
class="upload-img"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uploaded-img">
|
||||
<div class="list-title">{{ $T('ALREADY_UPLOAD') }}</div>
|
||||
<div v-for="item in files" :key="item.imgUrl" class="img-list">
|
||||
<div class="upload-img__container" @click="copyTheLink(item)">
|
||||
<img :src="item.imgUrl" class="upload-img">
|
||||
<div class="list-title">
|
||||
{{ $T('ALREADY_UPLOAD') }}
|
||||
</div>
|
||||
<div
|
||||
v-for="item in files"
|
||||
:key="item.imgUrl"
|
||||
class="img-list"
|
||||
>
|
||||
<div
|
||||
class="upload-img__container"
|
||||
@click="copyTheLink(item)"
|
||||
>
|
||||
<img
|
||||
v-lazy="item.imgUrl"
|
||||
class="upload-img"
|
||||
>
|
||||
<div
|
||||
class="upload-img__title"
|
||||
:title="item.fileName"
|
||||
>
|
||||
{{ item.fileName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,102 +61,100 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/mixin'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, onBeforeUnmount, onBeforeMount } from 'vue'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import $$db from '@/utils/db'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { IResult } from '@picgo/store/dist/types'
|
||||
import { PASTE_TEXT, OPEN_WINDOW } from '#/events/constants'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
|
||||
@Component({
|
||||
name: 'tray-page',
|
||||
mixins: [mixin]
|
||||
const files = ref<IResult<ImgInfo>[]>([])
|
||||
const notification = reactive({
|
||||
title: $T('COPY_LINK_SUCCEED'),
|
||||
body: ''
|
||||
})
|
||||
export default class extends Vue {
|
||||
files: IResult<ImgInfo>[] = []
|
||||
notification = {
|
||||
title: this.$T('COPY_LINK_SUCCEED'),
|
||||
body: '',
|
||||
icon: ''
|
||||
}
|
||||
|
||||
clipboardFiles: ImgInfo[] = []
|
||||
uploadFlag = false
|
||||
get reverseList () {
|
||||
return this.files.slice().reverse()
|
||||
}
|
||||
const clipboardFiles = ref<ImgInfo[]>([])
|
||||
const uploadFlag = ref(false)
|
||||
|
||||
openSettingWindow () {
|
||||
this.sendToMain(OPEN_WINDOW, IWindowList.SETTING_WINDOW)
|
||||
}
|
||||
// const reverseList = computed(() => files.value.slice().reverse())
|
||||
|
||||
async getData () {
|
||||
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
|
||||
}
|
||||
function openSettingWindow () {
|
||||
sendToMain(OPEN_WINDOW, IWindowList.SETTING_WINDOW)
|
||||
}
|
||||
|
||||
async copyTheLink (item: ImgInfo) {
|
||||
this.notification.body = item.imgUrl!
|
||||
this.notification.icon = item.imgUrl!
|
||||
const myNotification = new Notification(this.notification.title, this.notification)
|
||||
ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
async function getData () {
|
||||
files.value = (await $$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
|
||||
}
|
||||
|
||||
async function copyTheLink (item: ImgInfo) {
|
||||
notification.body = item.imgUrl!
|
||||
const myNotification = new Notification(notification.title, notification)
|
||||
ipcRenderer.invoke(PASTE_TEXT, item)
|
||||
myNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// function calcHeight (width: number, height: number): number {
|
||||
// return height * 160 / width
|
||||
// }
|
||||
|
||||
function disableDragFile () {
|
||||
window.addEventListener('dragover', (e) => {
|
||||
e = e || event
|
||||
e.preventDefault()
|
||||
}, false)
|
||||
window.addEventListener('drop', (e) => {
|
||||
e = e || event
|
||||
e.preventDefault()
|
||||
}, false)
|
||||
}
|
||||
|
||||
function uploadClipboardFiles () {
|
||||
if (uploadFlag.value) {
|
||||
return
|
||||
}
|
||||
uploadFlag.value = true
|
||||
sendToMain('uploadClipboardFiles')
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
disableDragFile()
|
||||
getData()
|
||||
ipcRenderer.on('dragFiles', async (event: Event, _files: string[]) => {
|
||||
for (let i = 0; i < _files.length; i++) {
|
||||
const item = _files[i]
|
||||
await $$db.insert(item)
|
||||
}
|
||||
}
|
||||
files.value = (await $$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
|
||||
})
|
||||
ipcRenderer.on('clipboardFiles', (event: Event, files: ImgInfo[]) => {
|
||||
clipboardFiles.value = files
|
||||
})
|
||||
ipcRenderer.on('uploadFiles', async () => {
|
||||
files.value = (await $$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
|
||||
uploadFlag.value = false
|
||||
})
|
||||
ipcRenderer.on('updateFiles', () => {
|
||||
getData()
|
||||
})
|
||||
})
|
||||
|
||||
calcHeight (width: number, height: number): number {
|
||||
return height * 160 / width
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeAllListeners('dragFiles')
|
||||
ipcRenderer.removeAllListeners('clipboardFiles')
|
||||
ipcRenderer.removeAllListeners('uploadClipboardFiles')
|
||||
ipcRenderer.removeAllListeners('updateFiles')
|
||||
})
|
||||
</script>
|
||||
|
||||
disableDragFile () {
|
||||
window.addEventListener('dragover', (e) => {
|
||||
e = e || event
|
||||
e.preventDefault()
|
||||
}, false)
|
||||
window.addEventListener('drop', (e) => {
|
||||
e = e || event
|
||||
e.preventDefault()
|
||||
}, false)
|
||||
}
|
||||
|
||||
uploadClipboardFiles () {
|
||||
if (this.uploadFlag) {
|
||||
return
|
||||
}
|
||||
this.uploadFlag = true
|
||||
ipcRenderer.send('uploadClipboardFiles')
|
||||
}
|
||||
|
||||
mounted () {
|
||||
this.disableDragFile()
|
||||
this.getData()
|
||||
ipcRenderer.on('dragFiles', async (event: Event, files: string[]) => {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const item = files[i]
|
||||
await this.$$db.insert(item)
|
||||
}
|
||||
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
|
||||
})
|
||||
ipcRenderer.on('clipboardFiles', (event: Event, files: ImgInfo[]) => {
|
||||
this.clipboardFiles = files
|
||||
})
|
||||
ipcRenderer.on('uploadFiles', async () => {
|
||||
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
|
||||
console.log(this.files)
|
||||
this.uploadFlag = false
|
||||
})
|
||||
ipcRenderer.on('updateFiles', () => {
|
||||
this.getData()
|
||||
})
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('dragFiles')
|
||||
ipcRenderer.removeAllListeners('clipboardFiles')
|
||||
ipcRenderer.removeAllListeners('uploadClipboardFiles')
|
||||
ipcRenderer.removeAllListeners('updateFiles')
|
||||
}
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'TrayPage'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -170,7 +204,7 @@ body::-webkit-scrollbar
|
||||
top 20px
|
||||
width 100%
|
||||
.img-list
|
||||
padding 8px 8px
|
||||
padding 4px 8px
|
||||
display flex
|
||||
justify-content space-between
|
||||
align-items center
|
||||
@@ -181,14 +215,31 @@ body::-webkit-scrollbar
|
||||
background #49B1F5
|
||||
.upload-img__index
|
||||
color #fff
|
||||
.upload-img__container
|
||||
display flex
|
||||
flex-direction column
|
||||
justify-content center
|
||||
align-items center
|
||||
.upload-img
|
||||
width 100%
|
||||
max-width 100%
|
||||
object-fit scale-down
|
||||
margin 0 auto
|
||||
&__container
|
||||
display flex
|
||||
flex-direction column
|
||||
justify-content center
|
||||
align-items center
|
||||
width 100%
|
||||
padding 8px 10px
|
||||
padding 8px 8px 4px
|
||||
height 100%
|
||||
&.upload
|
||||
cursor not-allowed
|
||||
&__title
|
||||
text-align center
|
||||
overflow hidden
|
||||
text-overflow ellipsis
|
||||
white-space nowrap
|
||||
color #ddd
|
||||
font-size 14px
|
||||
margin-top 4px
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
<template>
|
||||
<div id="upload-view">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-col
|
||||
:span="20"
|
||||
:offset="2"
|
||||
>
|
||||
<div class="view-title">
|
||||
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }} <i class="el-icon-caret-bottom" @click="handleChangePicBed"></i>
|
||||
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }}
|
||||
<el-icon
|
||||
style="cursor: pointer; margin-left: 4px;"
|
||||
@click="handleChangePicBed"
|
||||
>
|
||||
<CaretBottom />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div
|
||||
id="upload-area"
|
||||
@@ -12,12 +21,22 @@
|
||||
@dragover.prevent="dragover = true"
|
||||
@dragleave.prevent="dragover = false"
|
||||
>
|
||||
<div id="upload-dragger" @click="openUplodWindow">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div
|
||||
id="upload-dragger"
|
||||
@click="openUplodWindow"
|
||||
>
|
||||
<el-icon>
|
||||
<UploadFilled />
|
||||
</el-icon>
|
||||
<div class="upload-dragger__text">
|
||||
{{ $T('DRAG_FILE_TO_HERE') }} <span>{{ $T('CLICK_TO_UPLOAD') }}</span>
|
||||
</div>
|
||||
<input type="file" id="file-uploader" @change="onChange" multiple>
|
||||
<input
|
||||
id="file-uploader"
|
||||
type="file"
|
||||
multiple
|
||||
@change="onChange"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<el-progress
|
||||
@@ -26,214 +45,253 @@
|
||||
class="upload-progress"
|
||||
:class="{ 'show': showProgress }"
|
||||
:status="showError ? 'exception' : undefined"
|
||||
></el-progress>
|
||||
/>
|
||||
<div class="paste-style">
|
||||
<div class="el-col-16">
|
||||
<div class="paste-style__text">
|
||||
{{ $T('LINK_FORMAT') }}
|
||||
</div>
|
||||
<el-radio-group v-model="pasteStyle" size="mini"
|
||||
<el-radio-group
|
||||
v-model="pasteStyle"
|
||||
size="small"
|
||||
@change="handlePasteStyleChange"
|
||||
>
|
||||
<el-radio-button label="markdown">
|
||||
Markdown
|
||||
</el-radio-button>
|
||||
<el-radio-button label="HTML"></el-radio-button>
|
||||
<el-radio-button label="URL"></el-radio-button>
|
||||
<el-radio-button label="UBB"></el-radio-button>
|
||||
<el-radio-button label="Custom" :title="$T('CUSTOM')"></el-radio-button>
|
||||
<el-radio-button label="HTML" />
|
||||
<el-radio-button label="URL" />
|
||||
<el-radio-button label="UBB" />
|
||||
<el-radio-button
|
||||
label="Custom"
|
||||
:title="$T('CUSTOM')"
|
||||
/>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="el-col-8">
|
||||
<div class="paste-style__text">
|
||||
{{ $T('QUICK_UPLOAD') }}
|
||||
</div>
|
||||
<el-button type="primary" round size="mini" @click="uploadClipboardFiles" class="quick-upload" style="width: 50%">{{ $T('CLIPBOARD_PICTURE') }}</el-button>
|
||||
<el-button type="primary" round size="mini" @click="uploadURLFiles" class="quick-upload" style="width: 46%; margin-left: 6px">URL</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
class="quick-upload"
|
||||
style="width: 50%"
|
||||
@click="uploadClipboardFiles"
|
||||
>
|
||||
{{ $T('CLIPBOARD_PICTURE') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
class="quick-upload"
|
||||
style="width: 46%; margin-left: 6px"
|
||||
@click="uploadURLFiles"
|
||||
>
|
||||
URL
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
<script lang="ts" setup>
|
||||
// import { Component, Vue, Watch } from 'vue-property-decorator'
|
||||
import { UploadFilled, CaretBottom } from '@element-plus/icons-vue'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
import { ref, onBeforeMount, onBeforeUnmount, watch } from 'vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import $bus from '@/utils/bus'
|
||||
import {
|
||||
SHOW_INPUT_BOX,
|
||||
SHOW_INPUT_BOX_RESPONSE,
|
||||
SHOW_UPLOAD_PAGE_MENU
|
||||
SHOW_UPLOAD_PAGE_MENU,
|
||||
GET_PICBEDS
|
||||
} from '~/universal/events/constants'
|
||||
import {
|
||||
isUrl
|
||||
} from '~/universal/utils/common'
|
||||
@Component({
|
||||
name: 'upload'
|
||||
})
|
||||
export default class extends Vue {
|
||||
dragover = false
|
||||
progress = 0
|
||||
showProgress = false
|
||||
showError = false
|
||||
pasteStyle = ''
|
||||
picBed: IPicBedType[] = []
|
||||
picBedName = ''
|
||||
mounted () {
|
||||
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => {
|
||||
if (progress !== -1) {
|
||||
this.showProgress = true
|
||||
this.progress = progress
|
||||
} else {
|
||||
this.progress = 100
|
||||
this.showError = true
|
||||
}
|
||||
})
|
||||
this.getPasteStyle()
|
||||
this.getDefaultPicBed()
|
||||
ipcRenderer.on('syncPicBed', () => {
|
||||
this.getDefaultPicBed()
|
||||
})
|
||||
ipcRenderer.send('getPicBeds')
|
||||
ipcRenderer.on('getPicBeds', this.getPicBeds)
|
||||
this.$bus.$on(SHOW_INPUT_BOX_RESPONSE, this.handleInputBoxValue)
|
||||
}
|
||||
|
||||
@Watch('progress')
|
||||
onProgressChange (val: number) {
|
||||
if (val === 100) {
|
||||
setTimeout(() => {
|
||||
this.showProgress = false
|
||||
this.showError = false
|
||||
}, 1000)
|
||||
setTimeout(() => {
|
||||
this.progress = 0
|
||||
}, 1200)
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
const dragover = ref(false)
|
||||
const progress = ref(0)
|
||||
const showProgress = ref(false)
|
||||
const showError = ref(false)
|
||||
const pasteStyle = ref('')
|
||||
const picBed = ref<IPicBedType[]>([])
|
||||
const picBedName = ref('')
|
||||
onBeforeMount(() => {
|
||||
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, _progress: number) => {
|
||||
if (_progress !== -1) {
|
||||
showProgress.value = true
|
||||
progress.value = _progress
|
||||
} else {
|
||||
progress.value = 100
|
||||
showError.value = true
|
||||
}
|
||||
}
|
||||
})
|
||||
getPasteStyle()
|
||||
getDefaultPicBed()
|
||||
ipcRenderer.on('syncPicBed', () => {
|
||||
getDefaultPicBed()
|
||||
})
|
||||
sendToMain(GET_PICBEDS)
|
||||
ipcRenderer.on(GET_PICBEDS, getPicBeds)
|
||||
$bus.on(SHOW_INPUT_BOX_RESPONSE, handleInputBoxValue)
|
||||
})
|
||||
|
||||
beforeDestroy () {
|
||||
this.$bus.$off(SHOW_INPUT_BOX_RESPONSE)
|
||||
ipcRenderer.removeAllListeners('uploadProgress')
|
||||
ipcRenderer.removeAllListeners('syncPicBed')
|
||||
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
|
||||
}
|
||||
watch(progress, onProgressChange)
|
||||
|
||||
onDrop (e: DragEvent) {
|
||||
this.dragover = false
|
||||
const items = e.dataTransfer!.items
|
||||
function onProgressChange (val: number) {
|
||||
if (val === 100) {
|
||||
setTimeout(() => {
|
||||
showProgress.value = false
|
||||
showError.value = false
|
||||
}, 1000)
|
||||
setTimeout(() => {
|
||||
progress.value = 0
|
||||
}, 1200)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
$bus.off(SHOW_INPUT_BOX_RESPONSE)
|
||||
ipcRenderer.removeAllListeners('uploadProgress')
|
||||
ipcRenderer.removeAllListeners('syncPicBed')
|
||||
ipcRenderer.removeListener(GET_PICBEDS, getPicBeds)
|
||||
})
|
||||
|
||||
function onDrop (e: DragEvent) {
|
||||
dragover.value = false
|
||||
const items = e.dataTransfer?.items!
|
||||
const files = e.dataTransfer?.files!
|
||||
|
||||
// send files first
|
||||
if (files?.length) {
|
||||
ipcSendFiles(e.dataTransfer?.files!)
|
||||
} else {
|
||||
if (items.length === 2 && items[0].type === 'text/uri-list') {
|
||||
this.handleURLDrag(items, e.dataTransfer!)
|
||||
handleURLDrag(items, e.dataTransfer!)
|
||||
} else if (items[0].type === 'text/plain') {
|
||||
const str = e.dataTransfer!.getData(items[0].type)
|
||||
if (isUrl(str)) {
|
||||
ipcRenderer.send('uploadChoosedFiles', [{ path: str }])
|
||||
sendToMain('uploadChoosedFiles', [{ path: str }])
|
||||
} else {
|
||||
this.$message.error(this.$T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
|
||||
$message.error($T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
|
||||
}
|
||||
} else {
|
||||
this.ipcSendFiles(e.dataTransfer!.files)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) {
|
||||
// text/html
|
||||
// Use this data to get a more precise URL
|
||||
const urlString = dataTransfer.getData(items[1].type)
|
||||
const urlMatch = urlString.match(/<img.*src="(.*?)"/)
|
||||
if (urlMatch) {
|
||||
ipcRenderer.send('uploadChoosedFiles', [
|
||||
{
|
||||
path: urlMatch[1]
|
||||
}
|
||||
])
|
||||
} else {
|
||||
this.$message.error(this.$T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
|
||||
}
|
||||
}
|
||||
|
||||
openUplodWindow () {
|
||||
document.getElementById('file-uploader')!.click()
|
||||
}
|
||||
|
||||
onChange (e: any) {
|
||||
this.ipcSendFiles(e.target.files);
|
||||
(document.getElementById('file-uploader') as HTMLInputElement).value = ''
|
||||
}
|
||||
|
||||
ipcSendFiles (files: FileList) {
|
||||
const sendFiles: IFileWithPath[] = []
|
||||
Array.from(files).forEach((item) => {
|
||||
const obj = {
|
||||
name: item.name,
|
||||
path: item.path
|
||||
function handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) {
|
||||
// text/html
|
||||
// Use this data to get a more precise URL
|
||||
const urlString = dataTransfer.getData(items[1].type)
|
||||
const urlMatch = urlString.match(/<img.*src="(.*?)"/)
|
||||
if (urlMatch) {
|
||||
sendToMain('uploadChoosedFiles', [
|
||||
{
|
||||
path: urlMatch[1]
|
||||
}
|
||||
sendFiles.push(obj)
|
||||
})
|
||||
ipcRenderer.send('uploadChoosedFiles', sendFiles)
|
||||
])
|
||||
} else {
|
||||
$message.error($T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
|
||||
}
|
||||
}
|
||||
|
||||
async getPasteStyle () {
|
||||
this.pasteStyle = await this.getConfig('settings.pasteStyle') || 'markdown'
|
||||
}
|
||||
function openUplodWindow () {
|
||||
document.getElementById('file-uploader')!.click()
|
||||
}
|
||||
|
||||
handlePasteStyleChange (val: string) {
|
||||
this.saveConfig({
|
||||
'settings.pasteStyle': val
|
||||
})
|
||||
}
|
||||
function onChange (e: any) {
|
||||
ipcSendFiles(e.target.files);
|
||||
(document.getElementById('file-uploader') as HTMLInputElement).value = ''
|
||||
}
|
||||
|
||||
uploadClipboardFiles () {
|
||||
ipcRenderer.send('uploadClipboardFilesFromUploadPage')
|
||||
}
|
||||
|
||||
async uploadURLFiles () {
|
||||
const str = await navigator.clipboard.readText()
|
||||
this.$bus.$emit(SHOW_INPUT_BOX, {
|
||||
value: isUrl(str) ? str : '',
|
||||
title: this.$T('TIPS_INPUT_URL'),
|
||||
placeholder: this.$T('TIPS_HTTP_PREFIX')
|
||||
})
|
||||
}
|
||||
|
||||
handleInputBoxValue (val: string) {
|
||||
if (val === '') return false
|
||||
if (isUrl(val)) {
|
||||
ipcRenderer.send('uploadChoosedFiles', [{
|
||||
path: val
|
||||
}])
|
||||
} else {
|
||||
this.$message.error(this.$T('TIPS_INPUT_VALID_URL'))
|
||||
function ipcSendFiles (files: FileList) {
|
||||
const sendFiles: IFileWithPath[] = []
|
||||
Array.from(files).forEach((item) => {
|
||||
const obj = {
|
||||
name: item.name,
|
||||
path: item.path
|
||||
}
|
||||
}
|
||||
sendFiles.push(obj)
|
||||
})
|
||||
sendToMain('uploadChoosedFiles', sendFiles)
|
||||
}
|
||||
|
||||
async getDefaultPicBed () {
|
||||
const currentPicBed = await this.getConfig<string>('picBed.current')
|
||||
this.picBed.forEach(item => {
|
||||
if (item.type === currentPicBed) {
|
||||
this.picBedName = item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
async function getPasteStyle () {
|
||||
pasteStyle.value = await getConfig('settings.pasteStyle') || 'markdown'
|
||||
}
|
||||
|
||||
getPicBeds (event: Event, picBeds: IPicBedType[]) {
|
||||
this.picBed = picBeds
|
||||
this.getDefaultPicBed()
|
||||
}
|
||||
function handlePasteStyleChange (val: string | number | boolean) {
|
||||
saveConfig({
|
||||
'settings.pasteStyle': val
|
||||
})
|
||||
}
|
||||
|
||||
async handleChangePicBed () {
|
||||
ipcRenderer.send(SHOW_UPLOAD_PAGE_MENU)
|
||||
function uploadClipboardFiles () {
|
||||
sendToMain('uploadClipboardFilesFromUploadPage')
|
||||
}
|
||||
|
||||
async function uploadURLFiles () {
|
||||
const str = await navigator.clipboard.readText()
|
||||
$bus.emit(SHOW_INPUT_BOX, {
|
||||
value: isUrl(str) ? str : '',
|
||||
title: $T('TIPS_INPUT_URL'),
|
||||
placeholder: $T('TIPS_HTTP_PREFIX')
|
||||
})
|
||||
}
|
||||
|
||||
function handleInputBoxValue (val: string) {
|
||||
if (val === '') return
|
||||
if (isUrl(val)) {
|
||||
sendToMain('uploadChoosedFiles', [{
|
||||
path: val
|
||||
}])
|
||||
} else {
|
||||
$message.error($T('TIPS_INPUT_VALID_URL'))
|
||||
}
|
||||
}
|
||||
|
||||
async function getDefaultPicBed () {
|
||||
const currentPicBed = await getConfig<string>('picBed.current')
|
||||
picBed.value.forEach(item => {
|
||||
if (item.type === currentPicBed) {
|
||||
picBedName.value = item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getPicBeds (event: Event, picBeds: IPicBedType[]) {
|
||||
picBed.value = picBeds
|
||||
getDefaultPicBed()
|
||||
}
|
||||
|
||||
async function handleChangePicBed () {
|
||||
sendToMain(SHOW_UPLOAD_PAGE_MENU)
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'UploadPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.view-title
|
||||
display flex
|
||||
color #eee
|
||||
font-size 20px
|
||||
text-align center
|
||||
margin 10px auto
|
||||
align-items center
|
||||
justify-content center
|
||||
#upload-view
|
||||
.view-title
|
||||
margin 20px auto
|
||||
@@ -274,6 +332,8 @@ export default class extends Vue {
|
||||
.paste-style
|
||||
text-align center
|
||||
margin-top 16px
|
||||
display flex
|
||||
align-items flex-end
|
||||
&__text
|
||||
font-size 12px
|
||||
color #eeeeee
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<div id="config-list-view">
|
||||
<div class="view-title">
|
||||
{{ $T('SETTINGS') }}
|
||||
</div>
|
||||
<el-row
|
||||
:gutter="15"
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
type="flex"
|
||||
class="config-list"
|
||||
>
|
||||
<el-col
|
||||
v-for="item in curConfigList"
|
||||
:key="item._id"
|
||||
class="config-item-col"
|
||||
:span="11"
|
||||
:offset="1"
|
||||
>
|
||||
<div
|
||||
:class="`config-item ${defaultConfigId === item._id ? 'selected' : ''}`"
|
||||
@click="() => selectItem(item._id)"
|
||||
>
|
||||
<div class="config-name">
|
||||
{{ item._configName }}
|
||||
</div>
|
||||
<div class="config-update-time">
|
||||
{{ formatTime(item._updatedAt) }}
|
||||
</div>
|
||||
<div
|
||||
v-if="defaultConfigId === item._id"
|
||||
class="default-text"
|
||||
>
|
||||
{{ $T('SELECTED_SETTING_HINT') }}
|
||||
</div>
|
||||
<div class="operation-container">
|
||||
<el-icon
|
||||
class="el-icon-edit"
|
||||
@click="openEditPage(item._id)"
|
||||
>
|
||||
<Edit />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
class="el-icon-delete"
|
||||
:class="curConfigList.length <= 1 ? 'disabled' : ''"
|
||||
@click.stop="() => deleteConfig(item._id)"
|
||||
>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col
|
||||
class="config-item-col"
|
||||
:span="11"
|
||||
:offset="1"
|
||||
>
|
||||
<div
|
||||
class="config-item config-item-add"
|
||||
@click="addNewConfig"
|
||||
>
|
||||
<el-icon
|
||||
class="el-icon-plus"
|
||||
>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row
|
||||
type="flex"
|
||||
justify="center"
|
||||
:span="24"
|
||||
class="set-default-container"
|
||||
>
|
||||
<el-button
|
||||
class="set-default-btn"
|
||||
type="success"
|
||||
round
|
||||
:disabled="store?.state.defaultPicBed === type"
|
||||
@click="setDefaultPicBed(type)"
|
||||
>
|
||||
{{ $T('SETTINGS_SET_DEFAULT_PICBED') }}
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Edit, Delete, Plus } from '@element-plus/icons-vue'
|
||||
import { saveConfig, triggerRPC } from '@/utils/dataSender'
|
||||
import dayjs from 'dayjs'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { useRouter, useRoute, onBeforeRouteUpdate } from 'vue-router'
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
import { PICBEDS_PAGE, UPLOADER_CONFIG_PAGE } from '@/router/config'
|
||||
import { useStore } from '@/hooks/useStore'
|
||||
const $router = useRouter()
|
||||
const $route = useRoute()
|
||||
|
||||
const type = ref('')
|
||||
const curConfigList = ref<IStringKeyMap[]>([])
|
||||
const defaultConfigId = ref('')
|
||||
const store = useStore()
|
||||
|
||||
async function selectItem (id: string) {
|
||||
await triggerRPC<void>(IRPCActionType.SELECT_UPLOADER, type.value, id)
|
||||
defaultConfigId.value = id
|
||||
}
|
||||
|
||||
onBeforeRouteUpdate((to, from, next) => {
|
||||
if (to.params.type && (to.name === UPLOADER_CONFIG_PAGE)) {
|
||||
type.value = to.params.type as string
|
||||
getCurrentConfigList()
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
type.value = $route.params.type as string
|
||||
getCurrentConfigList()
|
||||
})
|
||||
|
||||
async function getCurrentConfigList () {
|
||||
const configList = await triggerRPC<IUploaderConfigItem>(IRPCActionType.GET_PICBED_CONFIG_LIST, type.value)
|
||||
curConfigList.value = configList?.configList ?? []
|
||||
defaultConfigId.value = configList?.defaultId ?? ''
|
||||
}
|
||||
|
||||
function openEditPage (configId: string) {
|
||||
$router.push({
|
||||
name: PICBEDS_PAGE,
|
||||
params: {
|
||||
type: type.value,
|
||||
configId
|
||||
},
|
||||
query: {
|
||||
defaultConfigId: defaultConfigId.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function formatTime (time: number): string {
|
||||
return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
async function deleteConfig (id: string) {
|
||||
const res = await triggerRPC<IUploaderConfigItem | undefined>(IRPCActionType.DELETE_PICBED_CONFIG, type.value, id)
|
||||
if (!res) return
|
||||
curConfigList.value = res.configList
|
||||
defaultConfigId.value = res.defaultId
|
||||
}
|
||||
|
||||
function addNewConfig () {
|
||||
$router.push({
|
||||
name: PICBEDS_PAGE,
|
||||
params: {
|
||||
type: type.value,
|
||||
configId: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setDefaultPicBed (type: string) {
|
||||
saveConfig({
|
||||
'picBed.current': type,
|
||||
'picBed.uploader': type
|
||||
})
|
||||
|
||||
store?.setDefaultPicBed(type)
|
||||
const successNotification = new Notification($T('SETTINGS_DEFAULT_PICBED'), {
|
||||
body: $T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'UploaderConfigPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#config-list-view
|
||||
position relative
|
||||
min-height 100%
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
padding-bottom 50px
|
||||
box-sizing border-box
|
||||
.config-list
|
||||
flex-wrap wrap
|
||||
width: 98%
|
||||
.config-item
|
||||
height 85px
|
||||
margin-bottom 20px
|
||||
border-radius 4px
|
||||
cursor pointer
|
||||
box-sizing border-box
|
||||
padding 8px
|
||||
background rgba(130, 130, 130, .2)
|
||||
border 1px solid transparent
|
||||
box-shadow 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)
|
||||
position relative
|
||||
.config-name
|
||||
color #eee
|
||||
font-size 16px
|
||||
.config-update-time
|
||||
color #aaa
|
||||
font-size 14px
|
||||
margin-top 10px
|
||||
.default-text
|
||||
color #67C23A
|
||||
font-size 12px
|
||||
margin-top 5px
|
||||
.operation-container
|
||||
position absolute
|
||||
right 5px
|
||||
top 8px
|
||||
font-size 18pxc
|
||||
display flex
|
||||
align-items center
|
||||
color #eee
|
||||
.el-icon-edit
|
||||
.el-icon-delete
|
||||
cursor pointer
|
||||
.el-icon-edit
|
||||
margin-right 10px
|
||||
.disabled
|
||||
cursor not-allowed
|
||||
color #aaa
|
||||
.config-item-add
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
color: #eee
|
||||
font-size: 28px
|
||||
.selected
|
||||
border 1px solid #409EFF
|
||||
.set-default-container
|
||||
position absolute
|
||||
bottom 10px
|
||||
width 100%
|
||||
.set-default-btn
|
||||
width 250px
|
||||
</style>
|
||||
@@ -1,156 +0,0 @@
|
||||
<template>
|
||||
<div id="aliyun-view">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
阿里云OSS设置
|
||||
</div>
|
||||
<el-form
|
||||
ref="aliyun"
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
:model="form"
|
||||
size="mini">
|
||||
<el-form-item
|
||||
label="设定KeyId"
|
||||
prop="accessKeyId"
|
||||
:rules="{
|
||||
required: true, message: 'AccessKeyId不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.accessKeyId" placeholder="AccessKeyId" @keyup.native.enter="confirm"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定KeySecret"
|
||||
prop="accessKeySecret"
|
||||
:rules="{
|
||||
required: true, message: 'AccessKeySecret不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.accessKeySecret" type="password" @keyup.native.enter="confirm" placeholder="AccessKeySecret"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定存储空间名"
|
||||
prop="bucket"
|
||||
:rules="{
|
||||
required: true, message: 'Bucket不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.bucket" @keyup.native.enter="confirm" placeholder="Bucket"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="确认存储区域"
|
||||
prop="area"
|
||||
:rules="{
|
||||
required: true, message: '区域代码不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.area" @keyup.native.enter="confirm" placeholder="例如oss-cn-beijing"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="指定存储路径"
|
||||
>
|
||||
<el-input v-model="form.path" @keyup.native.enter="confirm" placeholder="例如img/"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定网址后缀"
|
||||
>
|
||||
<el-input v-model="form.options" @keyup.native.enter="confirm" placeholder="例如?x-oss-process=xxx"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定自定义域名"
|
||||
>
|
||||
<el-input v-model="form.customUrl" @keyup.native.enter="confirm" placeholder="例如https://xxxx.com"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="confirm" round>确定</el-button>
|
||||
<el-button type="success" @click="setDefaultPicBed('aliyun')" round :disabled="defaultPicBed === 'aliyun'">设为默认图床</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import { trimValues } from '@/utils/common'
|
||||
@Component({
|
||||
name: 'aliyun',
|
||||
mixins: [mixin]
|
||||
})
|
||||
export default class extends Vue {
|
||||
form: IAliYunConfig = {
|
||||
accessKeyId: '',
|
||||
accessKeySecret: '',
|
||||
bucket: '',
|
||||
area: '',
|
||||
path: '',
|
||||
customUrl: '',
|
||||
options: ''
|
||||
}
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IAliYunConfig>('picBed.aliyun')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.aliyun.validate((valid) => {
|
||||
if (valid) {
|
||||
this.saveConfig({
|
||||
'picBed.aliyun': trimValues(this.form)
|
||||
})
|
||||
const successNotification = new window.Notification('设置结果', {
|
||||
body: '设置成功'
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#aliyun-view
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
&-item
|
||||
margin-bottom 10.5px
|
||||
.el-radio-group
|
||||
width 100%
|
||||
label
|
||||
width 25%
|
||||
.el-radio-button__inner
|
||||
width 100%
|
||||
.el-radio-button:first-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 14px 0 0 14px
|
||||
.el-radio-button:last-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 0 14px 14px 0
|
||||
.el-switch__label
|
||||
color #eee
|
||||
&.is-active
|
||||
color #409EFF
|
||||
.el-icon-question
|
||||
font-size 20px
|
||||
float right
|
||||
margin-top 9px
|
||||
color #eee
|
||||
cursor pointer
|
||||
transition .2s color ease-in-out
|
||||
&:hover
|
||||
color #409EFF
|
||||
</style>
|
||||
@@ -1,126 +0,0 @@
|
||||
<template>
|
||||
<div id="github-view">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
GitHub设置
|
||||
</div>
|
||||
<el-form
|
||||
ref="github"
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
:model="form"
|
||||
size="mini">
|
||||
<el-form-item
|
||||
label="设定仓库名"
|
||||
prop="repo"
|
||||
:rules="{
|
||||
required: true, message: '仓库名不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.repo" @keyup.native.enter="confirm" placeholder="格式:username/repo"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定分支名"
|
||||
prop="branch"
|
||||
:rules="{
|
||||
required: true, message: '分支名不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.branch" @keyup.native.enter="confirm" placeholder="例如:main"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定Token"
|
||||
prop="token"
|
||||
:rules="{
|
||||
required: true, message: 'Token不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.token" @keyup.native.enter="confirm" placeholder="token" type="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="指定存储路径"
|
||||
>
|
||||
<el-input v-model="form.path" @keyup.native.enter="confirm" placeholder="例如img/"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定自定义域名"
|
||||
>
|
||||
<el-input v-model="form.customUrl" @keyup.native.enter="confirm" placeholder="例如https://xxxx.com"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="confirm" round>确定</el-button>
|
||||
<el-button type="success" @click="setDefaultPicBed('github')" round :disabled="defaultPicBed === 'github'">设为默认图床</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import { trimValues } from '@/utils/common'
|
||||
@Component({
|
||||
name: 'github',
|
||||
mixins: [mixin]
|
||||
})
|
||||
export default class extends Vue {
|
||||
form: IGitHubConfig = {
|
||||
repo: '',
|
||||
token: '',
|
||||
path: '',
|
||||
customUrl: '',
|
||||
branch: ''
|
||||
}
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IGitHubConfig>('picBed.github')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.github.validate((valid) => {
|
||||
if (valid) {
|
||||
this.saveConfig({
|
||||
'picBed.github': trimValues(this.form)
|
||||
})
|
||||
const successNotification = new Notification('设置结果', {
|
||||
body: '设置成功'
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#github-view
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
.el-radio-group
|
||||
width 100%
|
||||
label
|
||||
width 25%
|
||||
.el-radio-button__inner
|
||||
width 100%
|
||||
.el-radio-button:first-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 14px 0 0 14px
|
||||
.el-radio-button:last-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 0 14px 14px 0
|
||||
</style>
|
||||
@@ -1,118 +0,0 @@
|
||||
<template>
|
||||
<div id="imgur-view">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
Imgur图床设置
|
||||
</div>
|
||||
<el-form
|
||||
ref="imgur"
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
:model="form"
|
||||
size="mini">
|
||||
<el-form-item
|
||||
label="设定ClientId"
|
||||
prop="clientId"
|
||||
:rules="{
|
||||
required: true, message: 'ClientId不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.clientId" placeholder="ClientId" @keyup.native.enter="confirm"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定代理"
|
||||
prop="proxy"
|
||||
>
|
||||
<el-input v-model="form.proxy" placeholder="例如:http://127.0.0.1:1080" @keyup.native.enter="confirm"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="confirm" round>确定</el-button>
|
||||
<el-button type="success" @click="setDefaultPicBed('imgur')" round :disabled="defaultPicBed === 'imgur'">设为默认图床</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import { trimValues } from '@/utils/common'
|
||||
@Component({
|
||||
name: 'imgur',
|
||||
mixins: [mixin]
|
||||
})
|
||||
export default class extends Vue {
|
||||
form: IImgurConfig = {
|
||||
clientId: '',
|
||||
proxy: ''
|
||||
}
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IImgurConfig>('picBed.imgur')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.imgur.validate((valid) => {
|
||||
if (valid) {
|
||||
this.saveConfig({
|
||||
'picBed.imgur': trimValues(this.form)
|
||||
})
|
||||
const successNotification = new Notification('设置结果', {
|
||||
body: '设置成功'
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#imgur-view
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
&-item
|
||||
margin-bottom 10.5px
|
||||
.el-radio-group
|
||||
width 100%
|
||||
label
|
||||
width 25%
|
||||
.el-radio-button__inner
|
||||
width 100%
|
||||
.el-radio-button:first-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 14px 0 0 14px
|
||||
.el-radio-button:last-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 0 14px 14px 0
|
||||
.el-switch__label
|
||||
color #eee
|
||||
&.is-active
|
||||
color #409EFF
|
||||
.el-icon-question
|
||||
font-size 20px
|
||||
float right
|
||||
margin-top 9px
|
||||
color #eee
|
||||
cursor pointer
|
||||
transition .2s color ease-in-out
|
||||
&:hover
|
||||
color #409EFF
|
||||
</style>
|
||||
@@ -1,127 +0,0 @@
|
||||
<template>
|
||||
<div id="others-view">
|
||||
<el-row :gutter="16" class="setting-list">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
{{ picBedName }}{{ $T('SETTINGS') }}
|
||||
</div>
|
||||
<config-form
|
||||
v-if="config.length > 0"
|
||||
:config="config"
|
||||
type="uploader"
|
||||
ref="configForm"
|
||||
:id="type"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="handleConfirm" round>{{ $T('CONFIRM') }}</el-button>
|
||||
<el-button type="success" @click="setDefaultPicBed(type)" round :disabled="defaultPicBed === type">{{ $T('SETTINGS_SET_DEFAULT_PICBED') }}</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</config-form>
|
||||
<div v-else class="single">
|
||||
<div class="notice">{{ $T('SETTINGS_NOT_CONFIG_OPTIONS') }}</div>
|
||||
<el-button type="success" @click="setDefaultPicBed(type)" round :disabled="defaultPicBed === type" size="mini">{{ $T('SETTINGS_SET_DEFAULT_PICBED') }}</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import ConfigForm from '@/components/ConfigForm.vue'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
import { trimValues } from '@/utils/common'
|
||||
|
||||
@Component({
|
||||
name: 'OtherPicBed',
|
||||
mixins: [mixin],
|
||||
components: {
|
||||
ConfigForm
|
||||
}
|
||||
})
|
||||
export default class extends Vue {
|
||||
type: string = ''
|
||||
config: any[] = []
|
||||
picBedName: string = ''
|
||||
created () {
|
||||
this.type = this.$route.params.type
|
||||
ipcRenderer.send('getPicBedConfig', this.$route.params.type)
|
||||
ipcRenderer.on('getPicBedConfig', this.getPicBeds)
|
||||
}
|
||||
|
||||
async handleConfirm () {
|
||||
// @ts-ignore
|
||||
const result = await this.$refs.configForm.validate()
|
||||
if (result !== false) {
|
||||
this.saveConfig({
|
||||
[`picBed.${this.type}`]: trimValues(result)
|
||||
})
|
||||
const successNotification = new Notification(this.$T('SETTINGS_RESULT'), {
|
||||
body: this.$T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDefaultPicBed (type: string) {
|
||||
this.saveConfig({
|
||||
'picBed.current': type,
|
||||
'picBed.uploader': type
|
||||
})
|
||||
// @ts-ignore 来自mixin的数据
|
||||
this.defaultPicBed = type
|
||||
const successNotification = new Notification(this.$T('SETTINGS_DEFAULT_PICBED'), {
|
||||
body: this.$T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
getPicBeds (event: IpcRendererEvent, config: any[], name: string) {
|
||||
this.config = config
|
||||
this.picBedName = name
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener('getPicBedConfig', this.getPicBeds)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#others-view
|
||||
.setting-list
|
||||
height 425px
|
||||
overflow-y auto
|
||||
overflow-x hidden
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
.el-button-group
|
||||
width 100%
|
||||
.el-button
|
||||
width 50%
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
.el-radio-group
|
||||
margin-left 25px
|
||||
.el-switch__label
|
||||
color #eee
|
||||
&.is-active
|
||||
color #409EFF
|
||||
.notice
|
||||
color #eee
|
||||
text-align center
|
||||
margin-bottom 10px
|
||||
.single
|
||||
text-align center
|
||||
</style>
|
||||
@@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<div id="qiniu-view">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
七牛图床设置
|
||||
</div>
|
||||
<el-form
|
||||
ref="qiniu"
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
:model="form"
|
||||
size="mini">
|
||||
<el-form-item
|
||||
label="设定AccessKey"
|
||||
prop="accessKey"
|
||||
:rules="{
|
||||
required: true, message: 'AccessKey不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.accessKey" placeholder="AccessKey" @keyup.native.enter="confirm"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定SecretKey"
|
||||
prop="secretKey"
|
||||
:rules="{
|
||||
required: true, message: 'SecretKey不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.secretKey" type="password" @keyup.native.enter="confirm" placeholder="SecretKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定存储空间名"
|
||||
prop="bucket"
|
||||
:rules="{
|
||||
required: true, message: 'Bucket不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.bucket" @keyup.native.enter="confirm" placeholder="Bucket"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定访问网址"
|
||||
prop="url"
|
||||
:rules="{
|
||||
required: true, message: '网址不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.url" @keyup.native.enter="confirm" placeholder="例如:http://xxx.yyy.glb.clouddn.com"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="确认存储区域"
|
||||
:rules="{
|
||||
required: true, message: '区域代码不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.area" placeholder="例如z0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定网址后缀"
|
||||
>
|
||||
<el-input v-model="form.options" @keyup.native.enter="confirm" placeholder="例如?imageslim"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="指定存储路径"
|
||||
>
|
||||
<el-input v-model="form.path" @keyup.native.enter="confirm" placeholder="例如img/"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="confirm" round>确定</el-button>
|
||||
<el-button type="success" @click="setDefaultPicBed('qiniu')" round :disabled="defaultPicBed === 'qiniu'">设为默认图床</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import { trimValues } from '@/utils/common'
|
||||
@Component({
|
||||
name: 'qiniu',
|
||||
mixins: [mixin]
|
||||
})
|
||||
export default class extends Vue {
|
||||
form: IQiniuConfig = {
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
bucket: '',
|
||||
url: '',
|
||||
area: '',
|
||||
options: '',
|
||||
path: ''
|
||||
}
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IQiniuConfig>('picBed.qiniu')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.qiniu.validate((valid) => {
|
||||
if (valid) {
|
||||
this.saveConfig({
|
||||
'picBed.qiniu': trimValues(this.form)
|
||||
})
|
||||
const successNotification = new Notification('设置结果', {
|
||||
body: '设置成功'
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#qiniu-view
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
&-item
|
||||
margin-bottom 10.5px
|
||||
.el-radio-group
|
||||
width 100%
|
||||
label
|
||||
width 25%
|
||||
.el-radio-button__inner
|
||||
width 100%
|
||||
.el-radio-button:first-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 14px 0 0 14px
|
||||
.el-radio-button:last-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 0 14px 14px 0
|
||||
</style>
|
||||
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<div id="smms-view">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
SM.MS设置
|
||||
</div>
|
||||
<el-form
|
||||
ref="smms"
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
:model="form"
|
||||
size="mini">
|
||||
<el-form-item
|
||||
label="设定Token"
|
||||
prop="token"
|
||||
:rules="{
|
||||
required: true, message: 'Token不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.token" type="password" placeholder="token" @keyup.native.enter="confirm"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="confirm" round>确定</el-button>
|
||||
<el-button type="success" @click="setDefaultPicBed('smms')" round :disabled="defaultPicBed === 'smms'">设为默认图床</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import { trimValues } from '@/utils/common'
|
||||
@Component({
|
||||
name: 'smms',
|
||||
mixins: [mixin]
|
||||
})
|
||||
export default class extends Vue {
|
||||
form: ISMMSConfig = {
|
||||
token: ''
|
||||
}
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<string | boolean>('picBed.smms.token')
|
||||
if (typeof config !== 'boolean') {
|
||||
this.form.token = config || ''
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.smms.validate((valid) => {
|
||||
if (valid) {
|
||||
this.saveConfig({
|
||||
'picBed.smms': trimValues(this.form)
|
||||
})
|
||||
const successNotification = new window.Notification('设置结果', {
|
||||
body: '设置成功'
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#smms-view
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
&-item
|
||||
margin-bottom 10.5px
|
||||
.el-radio-group
|
||||
width 100%
|
||||
label
|
||||
width 25%
|
||||
.el-radio-button__inner
|
||||
width 100%
|
||||
.el-radio-button:first-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 14px 0 0 14px
|
||||
.el-radio-button:last-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 0 14px 14px 0
|
||||
</style>
|
||||
@@ -1,186 +0,0 @@
|
||||
<template>
|
||||
<div id="tcyun-view">
|
||||
<el-row :gutter="16" class="setting-list-scroll">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
腾讯云COS设置
|
||||
</div>
|
||||
<el-form
|
||||
ref="tcyun"
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
:model="form"
|
||||
size="mini">
|
||||
<el-form-item
|
||||
label="COS版本"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.version"
|
||||
active-text="v4"
|
||||
inactive-text="v5"
|
||||
active-value="v4"
|
||||
inactive-value="v5"
|
||||
inactive-color="#67C23A"
|
||||
></el-switch>
|
||||
<i class="el-icon-question" @click="openWiki"></i>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定SecretId"
|
||||
prop="secretId"
|
||||
:rules="{
|
||||
required: true, message: 'SecretId不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.secretId" placeholder="SecretId" @keyup.native.enter="confirm"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定SecretKey"
|
||||
prop="secretKey"
|
||||
:rules="{
|
||||
required: true, message: 'SecretKey不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.secretKey" type="password" @keyup.native.enter="confirm" placeholder="SecretKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定APPID"
|
||||
prop="appId"
|
||||
:rules="{
|
||||
required: true, message: 'APPID不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.appId" @keyup.native.enter="confirm" placeholder="例如1234567890"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定存储空间名"
|
||||
prop="bucket"
|
||||
:rules="{
|
||||
required: true, message: 'Bucket不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.bucket" @keyup.native.enter="confirm" placeholder="Bucket"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="确认存储区域"
|
||||
prop="area"
|
||||
:rules="{
|
||||
required: true, message: '区域代码不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.area" @keyup.native.enter="confirm" placeholder="例如tj"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="指定存储路径"
|
||||
>
|
||||
<el-input v-model="form.path" @keyup.native.enter="confirm" placeholder="例如img/"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定自定义域名"
|
||||
>
|
||||
<el-input v-model="form.customUrl" @keyup.native.enter="confirm" placeholder="例如https://xxxx.com"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定网址后缀"
|
||||
>
|
||||
<el-input v-model="form.options" @keyup.native.enter="confirm" placeholder="例如?imageMogr2"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="confirm" round>确定</el-button>
|
||||
<el-button type="success" @click="setDefaultPicBed('tcyun')" round :disabled="defaultPicBed === 'tcyun'">设为默认图床</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import { OPEN_URL } from '#/events/constants'
|
||||
import { ITcyunConfig } from 'picgo/dist/types'
|
||||
import { trimValues } from '@/utils/common'
|
||||
@Component({
|
||||
name: 'tcyun',
|
||||
mixins: [mixin]
|
||||
})
|
||||
export default class extends Vue {
|
||||
form: ITcyunConfig = {
|
||||
secretId: '',
|
||||
secretKey: '',
|
||||
bucket: '',
|
||||
appId: '',
|
||||
area: '',
|
||||
path: '',
|
||||
customUrl: '',
|
||||
version: 'v5',
|
||||
options: ''
|
||||
}
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<ITcyunConfig>('picBed.tcyun')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.tcyun.validate((valid) => {
|
||||
if (valid) {
|
||||
this.saveConfig({
|
||||
'picBed.tcyun': trimValues(this.form)
|
||||
})
|
||||
const successNotification = new window.Notification('设置结果', {
|
||||
body: '设置成功'
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
openWiki () {
|
||||
ipcRenderer.send(OPEN_URL, 'https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E8%85%BE%E8%AE%AF%E4%BA%91cos')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#tcyun-view
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
&-item
|
||||
margin-bottom 10.5px
|
||||
.el-radio-group
|
||||
width 100%
|
||||
label
|
||||
width 25%
|
||||
.el-radio-button__inner
|
||||
width 100%
|
||||
.el-radio-button:first-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 14px 0 0 14px
|
||||
.el-radio-button:last-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 0 14px 14px 0
|
||||
.el-switch__label
|
||||
color #eee
|
||||
&.is-active
|
||||
color #409EFF
|
||||
.el-icon-question
|
||||
font-size 20px
|
||||
float right
|
||||
margin-top 9px
|
||||
color #eee
|
||||
cursor pointer
|
||||
transition .2s color ease-in-out
|
||||
&:hover
|
||||
color #409EFF
|
||||
</style>
|
||||
@@ -1,136 +0,0 @@
|
||||
<template>
|
||||
<div id="tcyun-view">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16" :offset="4">
|
||||
<div class="view-title">
|
||||
又拍云设置
|
||||
</div>
|
||||
<el-form
|
||||
ref="tcyun"
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
:model="form"
|
||||
size="mini">
|
||||
<el-form-item
|
||||
label="设定存储空间名"
|
||||
prop="bucket"
|
||||
:rules="{
|
||||
required: true, message: 'Bucket不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.bucket" @keyup.native.enter="confirm" placeholder="Bucket"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定操作员"
|
||||
prop="operator"
|
||||
:rules="{
|
||||
required: true, message: '操作员不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.operator" @keyup.native.enter="confirm" placeholder="例如:me"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定操作员密码"
|
||||
prop="password"
|
||||
:rules="{
|
||||
required: true, message: '操作员密码不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.password" @keyup.native.enter="confirm" placeholder="输入操作员密码" type="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定加速域名"
|
||||
prop="url"
|
||||
:rules="{
|
||||
required: true, message: '加速域名不能为空', trigger: 'blur'
|
||||
}">
|
||||
<el-input v-model="form.url" placeholder="例如http://xxx.test.upcdn.net" @keyup.native.enter="confirm()"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="设定网址后缀"
|
||||
>
|
||||
<el-input v-model="form.options" @keyup.native.enter="confirm" placeholder="例如!imgslim"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="指定存储路径"
|
||||
>
|
||||
<el-input v-model="form.path" @keyup.native.enter="confirm" placeholder="例如img/"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="confirm" round>确定</el-button>
|
||||
<el-button type="success" @click="setDefaultPicBed('upyun')" round :disabled="defaultPicBed === 'upyun'">设为默认图床</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import { IUpyunConfig } from 'picgo/dist/types'
|
||||
import { trimValues } from '@/utils/common'
|
||||
@Component({
|
||||
name: 'upyun',
|
||||
mixins: [mixin]
|
||||
})
|
||||
export default class extends Vue {
|
||||
form: IUpyunConfig = {
|
||||
bucket: '',
|
||||
operator: '',
|
||||
password: '',
|
||||
options: '',
|
||||
url: '',
|
||||
path: ''
|
||||
}
|
||||
|
||||
async created () {
|
||||
const config = await this.getConfig<IUpyunConfig>('picBed.upyun')
|
||||
if (config) {
|
||||
this.form = Object.assign({}, config)
|
||||
}
|
||||
}
|
||||
|
||||
confirm () {
|
||||
// @ts-ignore
|
||||
this.$refs.tcyun.validate((valid) => {
|
||||
if (valid) {
|
||||
this.saveConfig({
|
||||
'picBed.upyun': trimValues(this.form)
|
||||
})
|
||||
const successNotification = new Notification('设置结果', {
|
||||
body: '设置成功'
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#tcyun-view
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
.el-radio-group
|
||||
width 100%
|
||||
label
|
||||
width 25%
|
||||
.el-radio-button__inner
|
||||
width 100%
|
||||
.el-radio-button:first-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 14px 0 0 14px
|
||||
.el-radio-button:last-child
|
||||
.el-radio-button__inner
|
||||
border-left none
|
||||
border-radius 0 14px 14px 0
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div id="picbeds-page">
|
||||
<el-row
|
||||
:gutter="20"
|
||||
class="setting-list"
|
||||
>
|
||||
<el-col
|
||||
:span="20"
|
||||
:offset="2"
|
||||
>
|
||||
<div class="view-title">
|
||||
{{ picBedName }} {{ $T('SETTINGS') }}
|
||||
</div>
|
||||
<config-form
|
||||
v-if="config.length > 0"
|
||||
:id="type"
|
||||
ref="$configForm"
|
||||
:config="config"
|
||||
type="uploader"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
class="confirm-btn"
|
||||
type="primary"
|
||||
round
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</config-form>
|
||||
<div
|
||||
v-else
|
||||
class="single"
|
||||
>
|
||||
<div class="notice">
|
||||
{{ $T('SETTINGS_NOT_CONFIG_OPTIONS') }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { ref, onBeforeUnmount, onBeforeMount } from 'vue'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { sendToMain, triggerRPC } from '@/utils/dataSender'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import ConfigForm from '@/components/ConfigForm.vue'
|
||||
// import mixin from '@/utils/ConfirmButtonMixin'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
const type = ref('')
|
||||
const config = ref<IPicGoPluginConfig[]>([])
|
||||
const picBedName = ref('')
|
||||
const $route = useRoute()
|
||||
const $router = useRouter()
|
||||
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
|
||||
type.value = $route.params.type as string
|
||||
|
||||
onBeforeMount(() => {
|
||||
sendToMain('getPicBedConfig', $route.params.type)
|
||||
ipcRenderer.on('getPicBedConfig', getPicBeds)
|
||||
})
|
||||
|
||||
const handleConfirm = async () => {
|
||||
const result = (await $configForm.value?.validate()) || false
|
||||
if (result !== false) {
|
||||
await triggerRPC<void>(IRPCActionType.UPDATE_UPLOADER_CONFIG, type.value, result?._id, result)
|
||||
const successNotification = new Notification($T('SETTINGS_RESULT'), {
|
||||
body: $T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
$router.back()
|
||||
}
|
||||
}
|
||||
|
||||
function getPicBeds (event: IpcRendererEvent, _config: IPicGoPluginConfig[], name: string) {
|
||||
config.value = _config
|
||||
picBedName.value = name
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeListener('getPicBedConfig', getPicBeds)
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PicbedsPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#picbeds-page
|
||||
.setting-list
|
||||
height 425px
|
||||
overflow-y auto
|
||||
overflow-x hidden
|
||||
.confirm-btn
|
||||
width: 250px
|
||||
.el-form
|
||||
label
|
||||
line-height 22px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
&-item
|
||||
margin-bottom 16px
|
||||
.el-button-group
|
||||
width 100%
|
||||
.el-button
|
||||
width 50%
|
||||
.el-radio-group
|
||||
margin-left 25px
|
||||
.el-switch__label
|
||||
color #eee
|
||||
&.is-active
|
||||
color #409EFF
|
||||
.notice
|
||||
color #eee
|
||||
text-align center
|
||||
margin-bottom 10px
|
||||
.single
|
||||
text-align center
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
export const GALLERY_PAGE = 'GalleryPage'
|
||||
export const TRAY_PAGE = 'TrayPage'
|
||||
export const RENAME_PAGE = 'RenamePage'
|
||||
export const MINI_PAGE = 'MiniPage'
|
||||
export const MAIN_PAGE = 'MainPage'
|
||||
export const UPLOAD_PAGE = 'UploadPage'
|
||||
export const PICBEDS_PAGE = 'PicbedsPage'
|
||||
export const SETTING_PAGE = 'SettingPage'
|
||||
export const PLUGIN_PAGE = 'PluginPage'
|
||||
export const SHORTKEY_PAGE = 'ShortkeyPage'
|
||||
export const UPLOADER_CONFIG_PAGE = 'UploaderConfigPage'
|
||||
export const TOOLBOX_CONFIG_PAGE = 'ToolBoxPage'
|
||||