mirror of
https://github.com/Molunerfinn/PicGo.git
synced 2024-04-21 10:31:33 +00:00
Compare commits
50
Commits
v2.3.1-beta.8
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e91582adc | ||
|
|
aaec99f466 | ||
|
|
50e0a64519 | ||
|
|
8cede85732 | ||
|
|
050a3dd602 | ||
|
|
cd07b33e6b | ||
|
|
6d4e1c27d5 | ||
|
|
8fc65f4dcc | ||
|
|
0f7b07dd7b | ||
|
|
dca6667a81 | ||
|
|
1b92f20a7d | ||
|
|
a2320c3503 | ||
|
|
2db0fea6ee | ||
|
|
894d0a243e | ||
|
|
536b70832e | ||
|
|
bc2e92836c | ||
|
|
a5ed1d107e | ||
|
|
e3566b5a8e | ||
|
|
64eb832e06 | ||
|
|
10e0584c96 | ||
|
|
8d9a4000f3 | ||
|
|
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 |
+3
-2
@@ -8,7 +8,7 @@ module.exports = {
|
||||
},
|
||||
parser: 'vue-eslint-parser',
|
||||
extends: [
|
||||
'plugin:vue/essential',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'@vue/standard',
|
||||
'@vue/typescript'
|
||||
],
|
||||
@@ -20,7 +20,8 @@ module.exports = {
|
||||
'no-async-promise-executor': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/indent': ['error', 2]
|
||||
'@typescript-eslint/indent': ['error', 2],
|
||||
'vue/no-v-html': 'off'
|
||||
},
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
|
||||
@@ -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
|
||||
Vendored
+5
-3
@@ -20,6 +20,8 @@
|
||||
"stylusSupremacy.sortProperties": "grouped",
|
||||
"stylusSupremacy.quoteChar": "\"",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"prettier.enable": false
|
||||
}
|
||||
|
||||
+123
-3
@@ -1,3 +1,126 @@
|
||||
# :tada: 2.4.0-beta.6 (2023-11-19)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* app.asar directroy copy error ([#1180](https://github.com/Molunerfinn/PicGo/issues/1180)) ([cd07b33](https://github.com/Molunerfinn/PicGo/commit/cd07b33)), closes [#1179](https://github.com/Molunerfinn/PicGo/issues/1179)
|
||||
* can't add new config for picbed ([050a3dd](https://github.com/Molunerfinn/PicGo/commit/050a3dd)), closes [#1184](https://github.com/Molunerfinn/PicGo/issues/1184)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.5 (2023-09-10)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add gallery toolbox menu ([#1177](https://github.com/Molunerfinn/PicGo/issues/1177)) ([0f7b07d](https://github.com/Molunerfinn/PicGo/commit/0f7b07d))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.4 (2023-08-26)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add configName for upload page ([894d0a2](https://github.com/Molunerfinn/PicGo/commit/894d0a2))
|
||||
* support "tips" option for uploader ([1b92f20](https://github.com/Molunerfinn/PicGo/commit/1b92f20))
|
||||
* **tcyun:** add slim section ([#1165](https://github.com/Molunerfinn/PicGo/issues/1165)) ([a2320c3](https://github.com/Molunerfinn/PicGo/commit/a2320c3))
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* open config file bug ([2db0fea](https://github.com/Molunerfinn/PicGo/commit/2db0fea)), closes [#1163](https://github.com/Molunerfinn/PicGo/issues/1163)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.3 (2023-07-09)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* rename page bug ([bc2e928](https://github.com/Molunerfinn/PicGo/commit/bc2e928)), closes [#1130](https://github.com/Molunerfinn/PicGo/issues/1130)
|
||||
* tailwind css bug ([e3566b5](https://github.com/Molunerfinn/PicGo/commit/e3566b5))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.2 (2023-07-09)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* fileName encode bug ([8d9a400](https://github.com/Molunerfinn/PicGo/commit/8d9a400)), closes [#1121](https://github.com/Molunerfinn/PicGo/issues/1121)
|
||||
|
||||
|
||||
|
||||
# :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)
|
||||
|
||||
|
||||
@@ -538,6 +661,3 @@
|
||||
### :package: Chore
|
||||
|
||||
* add picgo bump-version ([37f1d34](https://github.com/Molunerfinn/PicGo/commit/37f1d34))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -92,6 +92,41 @@ 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 的版本发现打开后没反应,请重启电脑即可。
|
||||
|
||||
@@ -59,7 +59,7 @@ PicGo 本体支持如下图床:
|
||||
| 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 的贡献 |
|
||||
| [Scoop](https://scoop.sh/) | `scoop bucket add extras` & `scoop install picgo` | Windows | 感谢 @huangnauh 和 @Gladtbam 的贡献 |
|
||||
| [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 的贡献 |
|
||||
@@ -131,4 +131,4 @@ npm run electron:build
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
Copyright (c) 2017 - 2019 Molunerfinn
|
||||
Copyright (c) 2017 - Now Molunerfinn
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: '',
|
||||
name: 'HomePage',
|
||||
data () {
|
||||
return {
|
||||
version: '',
|
||||
|
||||
+89
-77
@@ -1,27 +1,100 @@
|
||||
{
|
||||
"name": "picgo",
|
||||
"version": "2.3.1-beta.8",
|
||||
"version": "2.4.0-beta.6",
|
||||
"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",
|
||||
"gen-i18n": "node ./scripts/gen-i18n-types.js"
|
||||
"upload-dist": "node ./scripts/upload-dist.js",
|
||||
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/background.ts"
|
||||
},
|
||||
"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.1.0",
|
||||
"axios": "^0.19.0",
|
||||
"compare-versions": "^4.1.3",
|
||||
"core-js": "^3.27.1",
|
||||
"element-plus": "^2.3.7",
|
||||
"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",
|
||||
"marked": "^7.0.4",
|
||||
"mitt": "^3.0.0",
|
||||
"picgo": "^1.5.6",
|
||||
"qrcode.vue": "^3.3.3",
|
||||
"shell-path": "2.1.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.2",
|
||||
"vue3-lazyload": "^0.3.6",
|
||||
"vue3-photo-preview": "^0.3.0",
|
||||
"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/uuid": "^9.0.2",
|
||||
"@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",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"conventional-changelog": "^3.1.18",
|
||||
"cz-customizable": "^6.2.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"dpdm": "^3.13.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",
|
||||
"postcss": "^8.4.23",
|
||||
"stylus": "^0.54.7",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"tailwindcss": "^3.3.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": {
|
||||
@@ -31,71 +104,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.4",
|
||||
"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.15",
|
||||
"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": "npm run lint:dpdm && commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "^16.10.2",
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
autoprefixer: {},
|
||||
tailwindcss: {}
|
||||
}
|
||||
}
|
||||
|
||||
+55
-6
@@ -1,4 +1,4 @@
|
||||
LANG_DISPLAY_LABEL: 'English'
|
||||
LANG_DISPLAY_LABEL: "English"
|
||||
ABOUT: About
|
||||
OPEN_MAIN_WINDOW: Open Main Window
|
||||
CHOOSE_DEFAULT_PICBED: Choose Default Picbed
|
||||
@@ -23,10 +23,12 @@ 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:'
|
||||
CHOOSE_YOUR_DEFAULT_PICBED: "Choose ${d} as your default picbed:"
|
||||
UPLOAD_AREA: Upload Area
|
||||
GALLERY: Gallery
|
||||
PICBEDS_SETTINGS: Picbeds Settings
|
||||
@@ -52,6 +54,10 @@ 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
|
||||
SUCCESS: Success
|
||||
FAILED: Failed
|
||||
|
||||
# settings
|
||||
|
||||
@@ -78,7 +84,8 @@ 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_SUCH_AS: 'Such as: $url/$fileName'
|
||||
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
|
||||
@@ -108,10 +115,19 @@ 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
|
||||
SETTINGS_STARTUP_MODE: Startup Mode
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: Open Main Window
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: Open Mini Window
|
||||
SETTINGS_STARTUP_MODE_HIDE: Silent Startup
|
||||
|
||||
# shortcut-page
|
||||
|
||||
BUILTIN_CLIPBOARD_TIPS: Use builtin clipboard function to upload instead of using scripts
|
||||
SHORTCUT_NAME: Shortcut Name
|
||||
SHORTCUT_BIND: Shortcut Binding
|
||||
SHORTCUT_STATUS: Status
|
||||
@@ -124,6 +140,13 @@ SHORTCUT_DISABLE: Disable
|
||||
SHORTCUT_EDIT: Edit
|
||||
SHORTCUT_CHANGE_UPLOAD: Change Upload Shortcut
|
||||
|
||||
# gallery-page
|
||||
CHANGE_IMAGE_URL_HOST: Change Image URL HOST
|
||||
NEW_IMAGE_URL_HOST: New Image URL HOST
|
||||
SELECTED_IMAGE_URL_HOST: Selected Image URL HOST
|
||||
CHANGE_IMAGE_URL_HOST_RESULT: The result of changing image URL HOST
|
||||
CHANGE_IMAGE_URL_HOST_WARN: You must select at least one picture first
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: Wait to Upload
|
||||
@@ -156,7 +179,7 @@ PLUGIN_IMPORT_LOCAL: Import Local Plugins
|
||||
|
||||
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
|
||||
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
|
||||
@@ -181,6 +204,32 @@ 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
|
||||
@@ -191,7 +240,7 @@ TIPS_PLUGIN_OVERWRITE_GALLERY: Plugin is trying to overwrite the album gallery,
|
||||
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_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
|
||||
|
||||
+51
-2
@@ -23,6 +23,8 @@ CONFIG_THING: 配置${c}
|
||||
FIND_NEW_VERSION: 发现新版本
|
||||
NO_MORE_NOTICE: 以后不再提醒
|
||||
SHOW_DEVTOOLS: 打开开发者工具
|
||||
CURRENT_PICBED: 当前图床
|
||||
OPEN_TOOLBOX: 打开修复工具箱
|
||||
|
||||
# ---renderer i18n begin---
|
||||
|
||||
@@ -52,6 +54,10 @@ CHANGE_IMAGE_URL_SUCCEED: 修改图片URL成功
|
||||
COPY_LINK_SUCCEED: 复制链接成功
|
||||
BATCH_COPY_LINK_SUCCEED: 批量复制链接成功
|
||||
FILE_RENAME: 文件改名
|
||||
COPY_FILE_PATH: 复制文件路径
|
||||
OPEN_FILE_PATH: 打开文件路径
|
||||
SUCCESS: 成功
|
||||
FAILED: 失败
|
||||
|
||||
# settings
|
||||
|
||||
@@ -78,6 +84,7 @@ 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: 插件安装代理
|
||||
@@ -109,6 +116,15 @@ 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 栏图标
|
||||
SETTINGS_STARTUP_MODE: 启动模式
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: 打开主窗口
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: 打开 Mini 窗口
|
||||
SETTINGS_STARTUP_MODE_HIDE: 静默启动
|
||||
|
||||
# shortcut-page
|
||||
|
||||
@@ -124,6 +140,13 @@ SHORTCUT_DISABLE: 禁用
|
||||
SHORTCUT_EDIT: 编辑
|
||||
SHORTCUT_CHANGE_UPLOAD: 修改上传快捷键
|
||||
|
||||
# gallery-page
|
||||
CHANGE_IMAGE_URL_HOST: 修改图片 URL HOST
|
||||
NEW_IMAGE_URL_HOST: 新的图片 URL HOST
|
||||
SELECTED_IMAGE_URL_HOST: 已选中的图片 URL HOST
|
||||
CHANGE_IMAGE_URL_HOST_RESULT: 修改图片 URL HOST 结果
|
||||
CHANGE_IMAGE_URL_HOST_WARN: 你必须先选中至少一张图片
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: 等待上传
|
||||
@@ -156,7 +179,7 @@ PLUGIN_IMPORT_LOCAL: 导入本地插件
|
||||
|
||||
TIPS_REMOVE_LINK: 此操作将把该图片移出相册, 是否继续?
|
||||
TIPS_WILL_REMOVE_CHOOSED_IMAGES: 将在相册中移除刚才选中的 ${m} 张图片,是否继续?
|
||||
TIPS_MUST_CONTAINS_URL: 必须含有$url
|
||||
TIPS_MUST_CONTAINS_URL: 必须含有$url 或 $fileName 或 $extName
|
||||
TIPS_NETWORK_ERROR: 网络错误暂时无法获取
|
||||
TIPS_NEED_RELOAD: 需要重启生效
|
||||
TIPS_PLEASE_CHOOSE_LOG_LEVEL: 请选择日志记录等级
|
||||
@@ -181,6 +204,32 @@ 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: 警告
|
||||
@@ -191,7 +240,7 @@ 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_PICGO_BACKUP_FILE_VERSION: "备份文件版本: ${v}"
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自定义文件解析出错,请检查路径内容是否正确
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷键已经修改成功
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷键冲突,请重新设置
|
||||
|
||||
+52
-3
@@ -23,6 +23,8 @@ CONFIG_THING: 設定${c}
|
||||
FIND_NEW_VERSION: 發現新版本
|
||||
NO_MORE_NOTICE: 以後不再提醒
|
||||
SHOW_DEVTOOLS: 開啟開發者工具
|
||||
CURRENT_PICBED: 當前圖床
|
||||
OPEN_TOOLBOX: 開啟修復工具箱
|
||||
|
||||
# ---renderer i18n begin---
|
||||
|
||||
@@ -51,7 +53,11 @@ CHANGE_IMAGE_URL: 修改圖片URL
|
||||
CHANGE_IMAGE_URL_SUCCEED: 修改圖片URL成功
|
||||
COPY_LINK_SUCCEED: 複製連結成功
|
||||
BATCH_COPY_LINK_SUCCEED: 批量複製連結成功
|
||||
FILE_RENAME: 文件改名
|
||||
FILE_RENAME: 檔案改名
|
||||
COPY_FILE_PATH: 複製檔案路徑
|
||||
OPEN_FILE_PATH: 打開檔案路徑
|
||||
SUCCESS: 成功
|
||||
FAILED: 失敗
|
||||
|
||||
# settings
|
||||
|
||||
@@ -78,6 +84,7 @@ 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
|
||||
@@ -109,6 +116,15 @@ 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 欄圖示
|
||||
SETTINGS_STARTUP_MODE: 啟動模式
|
||||
SETTINGS_STARTUP_MODE_MAIN_WINDOW: 打開主視窗
|
||||
SETTINGS_STARTUP_MODE_MINI_WINDOW: 打開 Mini 視窗
|
||||
SETTINGS_STARTUP_MODE_HIDE: 靜默啟動
|
||||
|
||||
# shortcut-page
|
||||
|
||||
@@ -124,6 +140,13 @@ SHORTCUT_DISABLE: 禁用
|
||||
SHORTCUT_EDIT: 編輯
|
||||
SHORTCUT_CHANGE_UPLOAD: 修改上傳快捷鍵
|
||||
|
||||
# gallery-page
|
||||
CHANGE_IMAGE_URL_HOST: 修改圖片 URL HOST
|
||||
NEW_IMAGE_URL_HOST: 新的圖片 URL HOST
|
||||
SELECTED_IMAGE_URL_HOST: 已選中的圖片 URL HOST
|
||||
CHANGE_IMAGE_URL_HOST_RESULT: 修改圖片 URL HOST 結果
|
||||
CHANGE_IMAGE_URL_HOST_WARN: 你必須先選中至少一張圖片
|
||||
|
||||
# tray-page
|
||||
|
||||
WAIT_TO_UPLOAD: 等待上傳
|
||||
@@ -156,7 +179,7 @@ PLUGIN_IMPORT_LOCAL: 導入本地插件
|
||||
|
||||
TIPS_REMOVE_LINK: 此操作將在相簿中移除該圖片,是否繼續?
|
||||
TIPS_WILL_REMOVE_CHOOSED_IMAGES: 將在相簿中移除剛才選中的 ${m} 張圖片,是否繼續?
|
||||
TIPS_MUST_CONTAINS_URL: 必須含有$url
|
||||
TIPS_MUST_CONTAINS_URL: 必須含有$url 或 $fileName 或 $extName
|
||||
TIPS_NETWORK_ERROR: 網路錯誤,暫時無法取得
|
||||
TIPS_NEED_RELOAD: 需要重新啟動生效
|
||||
TIPS_PLEASE_CHOOSE_LOG_LEVEL: 請選擇記錄等級
|
||||
@@ -181,6 +204,32 @@ 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: 警告
|
||||
@@ -191,7 +240,7 @@ 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_PICGO_BACKUP_FILE_VERSION: "備份檔案版本: ${v}"
|
||||
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自訂設定檔案解析出錯,請檢查路徑內容是否正確
|
||||
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷鍵已經修改成功
|
||||
TIPS_SHORTCUT_MODIFIED_CONFLICT: 快捷鍵衝突,請重新設定
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
+1
-1
@@ -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/${version}/${prefix}${version}${platform}${ext}`
|
||||
return `https://picgo-release.molunerfinn.com/${version}/${prefix}${version}${platform}${ext}`
|
||||
}
|
||||
|
||||
const platformExtList = [
|
||||
|
||||
@@ -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.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=${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()
|
||||
+35
-22
@@ -1,26 +1,30 @@
|
||||
import Vue from 'vue'
|
||||
import './renderer/assets/css/tailwind.css'
|
||||
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()
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$builtInPicBed = [
|
||||
const app = createApp(App)
|
||||
|
||||
app.config.globalProperties.$builtInPicBed = [
|
||||
'smms',
|
||||
'imgur',
|
||||
'qiniu',
|
||||
@@ -29,19 +33,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 = i18nManager
|
||||
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,6 @@
|
||||
import { SHORTKEY_COMMAND_UPLOAD } from '../../core/bus/constants'
|
||||
import { uploadClipboardFiles } from '../uploader/apis'
|
||||
|
||||
export const BuiltinShortKeyMap: Record<string, FN> = {
|
||||
[SHORTKEY_COMMAND_UPLOAD]: uploadClipboardFiles
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import db from '~/main/apis/core/datastore'
|
||||
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
|
||||
import shortKeyService from './shortKeyService'
|
||||
import picgo from '@core/picgo'
|
||||
import { BuiltinShortKeyMap } from './builtin'
|
||||
import { SHORTKEY_BUILTIN_PREFIX } from '../../core/bus/constants'
|
||||
|
||||
class ShortKeyHandler {
|
||||
private isInModifiedMode: boolean = false
|
||||
@@ -29,10 +31,14 @@ class ShortKeyHandler {
|
||||
.forEach(command => {
|
||||
const config = commands[command]
|
||||
// if disabled, don't register #534
|
||||
logger.info(`register builtin shortKey command: [${command}] - [${config.key}]`)
|
||||
if (config.enable) {
|
||||
logger.info(`register builtin shortKey command: [${command}] - [${config.key}] successfully`)
|
||||
globalShortcut.register(config.key, () => {
|
||||
this.handler(command)
|
||||
})
|
||||
} else {
|
||||
logger.warn(`builtin shortKey command: [${command}] - [${config.key}] register failed, it's disabled`)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -76,7 +82,7 @@ class ShortKeyHandler {
|
||||
} else {
|
||||
logger.warn(`${command} do not provide a key to bind`)
|
||||
}
|
||||
// if the configfile already had this command
|
||||
// if the config file already had this command
|
||||
// then writeFlag -> false
|
||||
if (writeFlag) {
|
||||
picgo.saveConfig({
|
||||
@@ -130,17 +136,25 @@ class ShortKeyHandler {
|
||||
|
||||
private async handler (command: string) {
|
||||
if (this.isInModifiedMode) {
|
||||
logger.warn(`in modified mode, ignore shortKey command: [${command}]`)
|
||||
return
|
||||
}
|
||||
if (command.includes('picgo:')) {
|
||||
bus.emit(command)
|
||||
if (command.includes(SHORTKEY_BUILTIN_PREFIX)) {
|
||||
const handler = BuiltinShortKeyMap[command]
|
||||
if (handler) {
|
||||
logger.info(`get builtin shortKey handler for command: [${command}]`)
|
||||
return handler()
|
||||
} else {
|
||||
logger.warn(`can't find builtin shortKey handler for command: [${command}]`)
|
||||
}
|
||||
} else if (command.includes('picgo-plugin-')) {
|
||||
const handler = shortKeyService.getShortKeyHandler(command)
|
||||
if (handler) {
|
||||
logger.info(`get plugin shortKey handler for command: [${command}]`)
|
||||
return handler(picgo, GuiApi.getInstance())
|
||||
}
|
||||
} else {
|
||||
logger.warn(`can not find command: ${command}`)
|
||||
logger.warn(`can not find command: [${command}]`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,44 +5,28 @@ import {
|
||||
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 { ensureFilePath, handleCopyUrl } from '~/main/utils/common'
|
||||
import { privacyManager } from '~/main/utils/privacyManager'
|
||||
// import { T } from '#/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
import { isMacOSVersionGreaterThanOrEqualTo } from '~/main/utils/getMacOSVersion'
|
||||
import { buildPicBedListMenu } from '~/main/events/remotes/menu'
|
||||
import { isLinux, isMacOS } from '~/universal/utils/common'
|
||||
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'),
|
||||
@@ -148,12 +132,15 @@ export function createContextMenu () {
|
||||
}
|
||||
])
|
||||
}
|
||||
return contextMenu!
|
||||
}
|
||||
|
||||
const getTrayIcon = () => {
|
||||
if (process.platform === 'darwin') {
|
||||
const isMacOSGreaterThan11 = isMacOSVersionGreaterThanOrEqualTo('11')
|
||||
return isMacOSGreaterThan11 ? `${__static}/menubar-newdarwinTemplate.png` : `${__static}/menubar.png`
|
||||
return isMacOSGreaterThan11
|
||||
? `${__static}/menubar-newdarwinTemplate.png`
|
||||
: `${__static}/menubar.png`
|
||||
} else {
|
||||
return `${__static}/menubar-nodarwin.png`
|
||||
}
|
||||
@@ -169,7 +156,9 @@ export function createTray () {
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.hide()
|
||||
}
|
||||
createContextMenu()
|
||||
tray!.popUpContextMenu(contextMenu!)
|
||||
setTimeout(() => {
|
||||
tray!.popUpContextMenu(contextMenu!)
|
||||
}, 0)
|
||||
})
|
||||
tray.on('click', (event, bounds) => {
|
||||
if (process.platform === 'darwin') {
|
||||
@@ -190,7 +179,10 @@ export function createTray () {
|
||||
} else {
|
||||
if (decodePath !== '') {
|
||||
// 带有中文的路径,无法直接被img.src所使用,会被转义
|
||||
const base64 = await fs.readFile(decodePath.replace('file://', ''), { encoding: 'base64' })
|
||||
const base64 = await fs.readFile(
|
||||
decodePath.replace('file://', ''),
|
||||
{ encoding: 'base64' }
|
||||
)
|
||||
obj.push({
|
||||
imgUrl: `data:image/png;base64,${base64}`
|
||||
})
|
||||
@@ -206,7 +198,9 @@ export function createTray () {
|
||||
})
|
||||
}
|
||||
}
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('clipboardFiles', obj)
|
||||
windowManager
|
||||
.get(IWindowList.TRAY_WINDOW)!
|
||||
.webContents.send('clipboardFiles', obj)
|
||||
}, 0)
|
||||
} else {
|
||||
if (windowManager.has(IWindowList.TRAY_WINDOW)) {
|
||||
@@ -222,7 +216,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`)
|
||||
@@ -230,7 +224,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
|
||||
@@ -244,11 +239,13 @@ export function createTray () {
|
||||
if (imgs !== false) {
|
||||
const pasteText: string[] = []
|
||||
for (let i = 0; i < imgs.length; i++) {
|
||||
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
|
||||
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()
|
||||
@@ -260,35 +257,59 @@ export function createTray () {
|
||||
}
|
||||
})
|
||||
// toggleWindow()
|
||||
} else if (process.platform === 'linux') {
|
||||
// click事件在Ubuntu上无法触发,Unity不支持(在Mac和Windows上可以触发)
|
||||
// 需要使用 setContextMenu 设置菜单
|
||||
} else if (isLinux) {
|
||||
// click事件在Ubuntu上无法触发,Unity不支持(在Mac和Windows上可以触发)
|
||||
// 需要使用 setContextMenu 设置菜单
|
||||
createContextMenu()
|
||||
tray!.setContextMenu(contextMenu)
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDockIcon () {
|
||||
if (isMacOS) {
|
||||
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',
|
||||
submenu: [
|
||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
|
||||
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
|
||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
|
||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
|
||||
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'CmdOrCtrl+Q',
|
||||
click () {
|
||||
app.quit()
|
||||
const template = [
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
|
||||
{
|
||||
label: 'Redo',
|
||||
accelerator: 'Shift+CmdOrCtrl+Z',
|
||||
selector: 'redo:'
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
|
||||
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
|
||||
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
|
||||
{
|
||||
label: 'Select All',
|
||||
accelerator: 'CmdOrCtrl+A',
|
||||
selector: 'selectAll:'
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'CmdOrCtrl+Q',
|
||||
click () {
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
]
|
||||
// @ts-ignore
|
||||
menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(menu)
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
WebContents
|
||||
} from 'electron'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IRPCActionType, IWindowList } from '#/types/enum'
|
||||
import uploader from '.'
|
||||
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 { handleCopyUrl, handleUrlEncodeWithSetting } from '~/main/utils/common'
|
||||
import { T } from '~/main/i18n/index'
|
||||
import logger from '@core/picgo/logger'
|
||||
// import dayjs from 'dayjs'
|
||||
|
||||
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
@@ -22,6 +22,7 @@ const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
|
||||
}
|
||||
|
||||
export const uploadClipboardFiles = async (): Promise<string> => {
|
||||
logger.info('upload clipboard file')
|
||||
const img = await handleClipboardUploading()
|
||||
if (img !== false) {
|
||||
if (img.length > 0) {
|
||||
@@ -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()
|
||||
@@ -41,9 +42,9 @@ export const uploadClipboardFiles = async (): Promise<string> => {
|
||||
trayWindow?.webContents?.send('clipboardFiles', [])
|
||||
trayWindow?.webContents?.send('uploadFiles', img)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
return handleUrlEncode(img[0].imgUrl as string)
|
||||
return handleUrlEncodeWithSetting(img[0].imgUrl as string)
|
||||
} else {
|
||||
const notification = new Notification({
|
||||
title: T('UPLOAD_FAILED'),
|
||||
@@ -68,20 +69,20 @@ 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()
|
||||
}, i * 100)
|
||||
await GalleryDB.getInstance().insert(imgs[i])
|
||||
result.push(handleUrlEncode(imgs[i].imgUrl!))
|
||||
result.push(handleUrlEncodeWithSetting(imgs[i].imgUrl!))
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
// trayWindow just be created in mac/windows, not in linux
|
||||
windowManager.get(IWindowList.TRAY_WINDOW)?.webContents?.send('uploadFiles', imgs)
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send('updateGallery')
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents?.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IWindowList } from '#/types/enum'
|
||||
import util from 'util'
|
||||
import { IPicGo } from 'picgo'
|
||||
import { showNotification, calcDurationRange, getClipboardFilePath } from '~/main/utils/common'
|
||||
import { RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
|
||||
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { T } from '~/main/i18n'
|
||||
import fse from 'fs-extra'
|
||||
@@ -22,14 +22,6 @@ import { privacyManager } from '~/main/utils/privacyManager'
|
||||
import writeFile from 'write-file-atomic'
|
||||
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
|
||||
const waitForShow = (webcontent: WebContents) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
webcontent.on('did-finish-load', () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
|
||||
return new Promise((resolve) => {
|
||||
const windowId = window.id
|
||||
@@ -99,8 +91,13 @@ class Uploader {
|
||||
}
|
||||
if (rename) {
|
||||
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
|
||||
await waitForShow(window.webContents)
|
||||
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
|
||||
logger.info('wait for rename window ready...')
|
||||
ipcMain.on(GET_RENAME_FILE_NAME, (evt) => {
|
||||
if (evt.sender.id === window.webContents.id) {
|
||||
logger.info('rename window ready, wait for rename...')
|
||||
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
|
||||
}
|
||||
})
|
||||
name = await waitForRename(window, window.webContents.id)
|
||||
}
|
||||
item.fileName = name || fileName
|
||||
@@ -177,6 +174,8 @@ class Uploader {
|
||||
})
|
||||
}, 500)
|
||||
return false
|
||||
} finally {
|
||||
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,16 +2,17 @@ import {
|
||||
SETTING_WINDOW_URL,
|
||||
TRAY_WINDOW_URL,
|
||||
MINI_WINDOW_URL,
|
||||
RENAME_WINDOW_URL
|
||||
RENAME_WINDOW_URL,
|
||||
TOOLBOX_WINDOW_URL
|
||||
} from './constants'
|
||||
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
|
||||
import { IStartupMode, 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 { remoteNoticeHandler } from '../remoteNotice'
|
||||
// import { i18n } from '~/main/i18n'
|
||||
import { T } from '~/main/i18n'
|
||||
import { isLinux } from '~/universal/utils/common'
|
||||
// import { URLSearchParams } from 'url'
|
||||
|
||||
const windowList = new Map<IWindowList, IWindowListItem>()
|
||||
@@ -25,6 +26,21 @@ const handleWindowParams = (windowURL: string) => {
|
||||
return windowURL
|
||||
}
|
||||
|
||||
export const isWindowShouldShowOnStartup = (currentWindow: IWindowList) => {
|
||||
const startupMode = db.get('settings.startupMode') || (isLinux ? IStartupMode.SHOW_MINI_WINDOW : IStartupMode.HIDE)
|
||||
switch (currentWindow) {
|
||||
case IWindowList.MINI_WINDOW: {
|
||||
return startupMode === IStartupMode.SHOW_MINI_WINDOW
|
||||
}
|
||||
case IWindowList.SETTING_WINDOW: {
|
||||
return startupMode === IStartupMode.SHOW_MAIN_WINDOW
|
||||
}
|
||||
default: {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
windowList.set(IWindowList.TRAY_WINDOW, {
|
||||
isValid: process.platform !== 'linux',
|
||||
multiple: false,
|
||||
@@ -59,6 +75,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,
|
||||
@@ -70,6 +87,7 @@ windowList.set(IWindowList.SETTING_WINDOW, {
|
||||
title: 'PicGo',
|
||||
vibrancy: 'ultra-dark',
|
||||
transparent: true,
|
||||
skipTaskbar: !showDockIcon,
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
@@ -85,13 +103,11 @@ 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)
|
||||
@@ -113,7 +129,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
|
||||
const obj: IBrowserWindowOptions = {
|
||||
height: 64,
|
||||
width: 64,
|
||||
show: process.platform === 'linux',
|
||||
show: isLinux,
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
@@ -128,7 +144,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
|
||||
}
|
||||
}
|
||||
|
||||
if (db.get('settings.miniWindowOntop')) {
|
||||
if (db.get('settings.miniWindowOnTop')) {
|
||||
obj.alwaysOnTop = true
|
||||
}
|
||||
return obj
|
||||
@@ -183,4 +199,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
|
||||
|
||||
@@ -7,3 +7,7 @@ export const UPLOAD_WITH_FILES_RESPONSE = 'UPLOAD_WITH_FILES_RESPONSE'
|
||||
export const UPLOAD_WITH_CLIPBOARD_FILES = 'UPLOAD_WITH_CLIPBOARD_FILES'
|
||||
export const UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE = 'UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE'
|
||||
export const CREATE_APP_MENU = 'CREATE_APP_MENU'
|
||||
|
||||
// shortkey command
|
||||
export const SHORTKEY_BUILTIN_PREFIX = 'picgo:'
|
||||
export const SHORTKEY_COMMAND_UPLOAD = `${SHORTKEY_BUILTIN_PREFIX}upload`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,14 +18,16 @@ import {
|
||||
} from '~/universal/events/constants'
|
||||
import { DBStore } from '@picgo/store'
|
||||
import { T } from '~/main/i18n'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
|
||||
// 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
|
||||
private constructor () {
|
||||
console.log('init guiapi')
|
||||
console.log('init gui api')
|
||||
}
|
||||
|
||||
public static getInstance (): GuiApi {
|
||||
@@ -49,7 +51,7 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
private getWebcontentsByWindowId (id: number) {
|
||||
private getWebContentsByWindowId (id: number) {
|
||||
return BrowserWindow.fromId(id)?.webContents
|
||||
}
|
||||
|
||||
@@ -58,7 +60,7 @@ class GuiApi implements IGuiApi {
|
||||
placeholder: ''
|
||||
}) {
|
||||
await this.showSettingWindow()
|
||||
this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
|
||||
this.getWebContentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
|
||||
return new Promise<string>((resolve) => {
|
||||
ipcMain.once(SHOW_INPUT_BOX, (event: Event, value: string) => {
|
||||
resolve(value)
|
||||
@@ -74,7 +76,7 @@ class GuiApi implements IGuiApi {
|
||||
|
||||
async upload (input: IUploadOption) {
|
||||
this.windowId = await getWindowId()
|
||||
const webContents = this.getWebcontentsByWindowId(this.windowId)
|
||||
const webContents = this.getWebContentsByWindowId(this.windowId)
|
||||
const imgs = await uploader.setWebContents(webContents!).upload(input)
|
||||
if (imgs !== false) {
|
||||
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
|
||||
@@ -83,8 +85,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()
|
||||
@@ -93,7 +95,7 @@ class GuiApi implements IGuiApi {
|
||||
}
|
||||
handleCopyUrl(pasteText.join('\n'))
|
||||
webContents?.send('uploadFiles', imgs)
|
||||
webContents?.send('updateGallery')
|
||||
webContents?.send(IRPCActionType.UPDATE_GALLERY)
|
||||
return imgs
|
||||
}
|
||||
return []
|
||||
@@ -130,6 +132,20 @@ class GuiApi implements IGuiApi {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* v2.4.0+
|
||||
* @param options
|
||||
*/
|
||||
async showConfigDialog<T extends IStringKeyMap> (options: IPicGoPluginShowConfigDialogOption) {
|
||||
await this.showSettingWindow()
|
||||
this.getWebContentsByWindowId(this.settingWindowId)?.send(IRPCActionType.OPEN_CONFIG_DIALOG, options)
|
||||
return new Promise<T | false>((resolve) => {
|
||||
ipcMain.once(IRPCActionType.OPEN_CONFIG_DIALOG, (event: Event, value: T | false) => {
|
||||
resolve(value)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* get picgo config/data path
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
} from '@core/bus/constants'
|
||||
function initEventCenter () {
|
||||
const eventList: any = {
|
||||
'picgo:upload': uploadClipboardFiles,
|
||||
[UPLOAD_WITH_CLIPBOARD_FILES]: busCallUploadClipboardFiles,
|
||||
[UPLOAD_WITH_FILES]: busCallUploadFiles,
|
||||
[GET_WINDOW_ID]: busCallGetWindowId,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
BrowserWindow
|
||||
} from 'electron'
|
||||
import windowManager from 'apis/app/window/windowManager'
|
||||
import { IWindowList } from '#/types/enum'
|
||||
import { IRPCActionType, IWindowList } from '#/types/enum'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import pasteTemplate from '~/main/utils/pasteTemplate'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
SHOW_UPLOAD_PAGE_MENU,
|
||||
OPEN_USER_STORE_FILE,
|
||||
OPEN_URL,
|
||||
RELOAD_APP,
|
||||
SHOW_PLUGIN_PAGE_MENU,
|
||||
SET_MINI_WINDOW_POS,
|
||||
GET_PICBEDS
|
||||
@@ -36,7 +35,7 @@ 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 { T } from '~/main/i18n'
|
||||
|
||||
@@ -55,21 +54,22 @@ export default {
|
||||
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])
|
||||
trayWindow.webContents.send('clipboardFiles', [])
|
||||
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
}
|
||||
trayWindow.webContents.send('uploadFiles')
|
||||
})
|
||||
|
||||
ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
|
||||
console.log('handle')
|
||||
uploadClipboardFiles()
|
||||
})
|
||||
|
||||
@@ -137,7 +137,7 @@ export default {
|
||||
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
|
||||
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
|
||||
if (db.get('settings.miniWindowOntop')) {
|
||||
if (db.get('settings.miniWindowOnTop')) {
|
||||
miniWindow.setAlwaysOnTop(true)
|
||||
}
|
||||
|
||||
@@ -186,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
|
||||
})
|
||||
@@ -217,10 +217,6 @@ 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)
|
||||
|
||||
@@ -27,16 +27,17 @@ import {
|
||||
PICGO_OPEN_FILE,
|
||||
PASTE_TEXT,
|
||||
OPEN_WINDOW,
|
||||
DEFAULT_LOGO,
|
||||
GET_LANGUAGE_LIST,
|
||||
SET_CURRENT_LANGUAGE,
|
||||
GET_CURRENT_LANGUAGE
|
||||
GET_CURRENT_LANGUAGE,
|
||||
GET_PICBED_CONFIG
|
||||
} from '#/events/constants'
|
||||
|
||||
import { GalleryDB } from 'apis/core/datastore'
|
||||
import { IObject, IFilter } from '@picgo/store/dist/types'
|
||||
import pasteTemplate from '../utils/pasteTemplate'
|
||||
import { i18nManager, T } from '~/main/i18n'
|
||||
import { rpcServer } from './rpc'
|
||||
|
||||
// eslint-disable-next-line
|
||||
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
|
||||
@@ -223,14 +224,14 @@ const handleNPMError = (): IDispose => {
|
||||
}
|
||||
|
||||
const handleGetPicBedConfig = () => {
|
||||
ipcMain.on('getPicBedConfig', (event: IpcMainEvent, type: string) => {
|
||||
ipcMain.on(GET_PICBED_CONFIG, (event: IpcMainEvent, 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)
|
||||
event.sender.send('getPicBedConfig', config, name)
|
||||
event.sender.send(GET_PICBED_CONFIG, config, name)
|
||||
} else {
|
||||
event.sender.send('getPicBedConfig', [], name)
|
||||
event.sender.send(GET_PICBED_CONFIG, [], name)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -370,12 +371,6 @@ 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)
|
||||
@@ -402,6 +397,10 @@ const handleI18n = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleRPCActions = () => {
|
||||
rpcServer.start()
|
||||
}
|
||||
|
||||
export default {
|
||||
listen () {
|
||||
handleGetPluginList()
|
||||
@@ -415,8 +414,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 '~/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,61 @@
|
||||
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'
|
||||
import { galleryToolboxRouter } from './routes/galleryToolbox'
|
||||
|
||||
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())
|
||||
rpcServer.use(galleryToolboxRouter.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,77 @@
|
||||
import { logger } from '@picgo/i18n'
|
||||
import { IPicGo } from 'picgo'
|
||||
import { T } from '~/main/i18n'
|
||||
import { getHost, removeProtocolAndSuffix, replaceHost } from '~/main/utils/common'
|
||||
|
||||
export const galleryMenu = () => {
|
||||
return [{
|
||||
label: T('CHANGE_IMAGE_URL_HOST'),
|
||||
async handle (ctx: IPicGo, guiApi: IGuiApi, selectedList: ImgInfo[] = []) {
|
||||
const hostList = [...new Set(selectedList.map((item) => {
|
||||
return getHost(item.imgUrl)
|
||||
}).filter(Boolean) as string[])]
|
||||
if (hostList.length === 0) {
|
||||
guiApi.showNotification({
|
||||
title: T('CHANGE_IMAGE_URL_HOST'),
|
||||
body: T('CHANGE_IMAGE_URL_HOST_WARN')
|
||||
})
|
||||
logger.warn(T('CHANGE_IMAGE_URL_HOST_WARN'))
|
||||
return
|
||||
}
|
||||
const config: IPicGoPluginConfig[] = [
|
||||
{
|
||||
alias: T('SELECTED_IMAGE_URL_HOST'),
|
||||
name: 'selectedHost',
|
||||
type: 'checkbox',
|
||||
choices: hostList.map(item => ({
|
||||
name: item,
|
||||
value: item,
|
||||
checked: true
|
||||
})),
|
||||
required: true
|
||||
},
|
||||
{
|
||||
alias: T('NEW_IMAGE_URL_HOST'),
|
||||
name: 'newHost',
|
||||
type: 'input',
|
||||
message: 'www.example.com',
|
||||
default: '',
|
||||
required: true
|
||||
}
|
||||
]
|
||||
const options: IPicGoPluginShowConfigDialogOption = {
|
||||
title: T('CHANGE_IMAGE_URL_HOST'),
|
||||
config
|
||||
}
|
||||
const res = await guiApi.showConfigDialog<{
|
||||
selectedHost: string[]
|
||||
newHost: string
|
||||
}>(options)
|
||||
|
||||
if (res) {
|
||||
const selectedHost = res.selectedHost
|
||||
const newHost = removeProtocolAndSuffix(res.newHost)
|
||||
const changedList = selectedList.map((item) => {
|
||||
try {
|
||||
const url = new URL(item.imgUrl || '')
|
||||
const host = url.host
|
||||
if (selectedHost.includes(host)) {
|
||||
item.imgUrl = replaceHost(item.imgUrl!, host, newHost)
|
||||
return item
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (e: any) {
|
||||
ctx.log.error(e)
|
||||
return false
|
||||
}
|
||||
}).filter(Boolean) as ImgInfo[]
|
||||
const updateRes = await guiApi.galleryDB.updateMany(changedList)
|
||||
guiApi.showNotification({
|
||||
title: T('CHANGE_IMAGE_URL_HOST_RESULT'),
|
||||
body: `${T('SUCCESS')}: ${updateRes.success} ${T('FAILED')}: ${updateRes.total - updateRes.success}}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { galleryMenu as changeHostGalleryMenu } from './changeHost'
|
||||
export const builtInGalleryToolboxMenu = () => {
|
||||
const menuList = [...changeHostGalleryMenu()]
|
||||
|
||||
return menuList
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IRPCActionType, IWindowList } from '~/universal/types/enum'
|
||||
import { RPCRouter } from '../../router'
|
||||
import windowManager from '~/main/apis/app/window/windowManager'
|
||||
import { galleryMenuListManager } from './menuListManager'
|
||||
|
||||
const galleryToolboxRouter = new RPCRouter()
|
||||
|
||||
galleryToolboxRouter.add(IRPCActionType.GET_GALLERY_MENU_LIST, async (args) => {
|
||||
const [selectedList] = args as IGetGalleryMenuListArgs
|
||||
const win = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||
const menu = galleryMenuListManager.getMenu(selectedList)
|
||||
|
||||
menu.popup({
|
||||
window: win
|
||||
})
|
||||
})
|
||||
|
||||
export {
|
||||
galleryToolboxRouter
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Menu, MenuItemConstructorOptions } from 'electron'
|
||||
import { builtInGalleryToolboxMenu } from './builtIn'
|
||||
import picgo from '@core/picgo'
|
||||
import GuiApi from 'apis/gui'
|
||||
import windowManager from '~/main/apis/app/window/windowManager'
|
||||
import { IRPCActionType, IWindowList } from '~/universal/types/enum'
|
||||
import logger from '~/main/apis/core/picgo/logger'
|
||||
|
||||
class GalleryMenuListManager {
|
||||
private menuList: MenuItemConstructorOptions[] = []
|
||||
private menu: Menu | null = null
|
||||
|
||||
private getBuiltInMenuList (selectedList: IGalleryItem[]): MenuItemConstructorOptions[] {
|
||||
const builtInMenu = builtInGalleryToolboxMenu().map(item => {
|
||||
return {
|
||||
label: item.label,
|
||||
async click () {
|
||||
try {
|
||||
await item.handle(picgo, GuiApi.getInstance(), selectedList)
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
} finally {
|
||||
windowManager.get(IWindowList.SETTING_WINDOW)?.webContents.send(IRPCActionType.UPDATE_GALLERY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) as MenuItemConstructorOptions[]
|
||||
this.menuList = [...builtInMenu]
|
||||
|
||||
return this.menuList
|
||||
}
|
||||
|
||||
private getMenuItemList (selectedList: IGalleryItem[]) {
|
||||
// current only support built-in menu
|
||||
return this.getBuiltInMenuList(selectedList)
|
||||
}
|
||||
|
||||
public getMenu (selectedList: IGalleryItem[]): Menu {
|
||||
this.menu = Menu.buildFromTemplate(this.getMenuItemList(selectedList))
|
||||
return this.menu
|
||||
}
|
||||
}
|
||||
|
||||
const galleryMenuListManager = new GalleryMenuListManager()
|
||||
|
||||
export {
|
||||
galleryMenuListManager
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -7,14 +7,14 @@ import { builtinI18nList } from '#/i18n'
|
||||
class I18nManager {
|
||||
private i18n: I18n | null = null
|
||||
private builtinI18nFolder = path.join(__static, 'i18n')
|
||||
private outterI18nFolder = ''
|
||||
private outerI18nFolder = ''
|
||||
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
|
||||
setOuterI18nFolder (folder: string) {
|
||||
this.outerI18nFolder = folder
|
||||
}
|
||||
|
||||
addI18nFile (file: string, label: string) {
|
||||
@@ -30,7 +30,7 @@ class I18nManager {
|
||||
}
|
||||
let localesPath = path.join(this.builtinI18nFolder, `${lang}.yml`)
|
||||
if (!fs.existsSync(localesPath)) {
|
||||
localesPath = path.join(this.outterI18nFolder, `${lang}.yml`)
|
||||
localesPath = path.join(this.outerI18nFolder, `${lang}.yml`)
|
||||
if (!fs.existsSync(localesPath)) {
|
||||
localesPath = path.join(this.builtinI18nFolder, `${this.defaultLanguage}.yml`)
|
||||
}
|
||||
|
||||
@@ -9,7 +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) => {
|
||||
const handleProcessError = (error: Error | string) => {
|
||||
logger('error', error)
|
||||
}
|
||||
|
||||
@@ -20,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()
|
||||
|
||||
@@ -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'
|
||||
@@ -36,6 +36,8 @@ 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'
|
||||
import { isWindowShouldShowOnStartup } from '../apis/app/window/windowList'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
@@ -78,12 +80,27 @@ class LifeCycle {
|
||||
try {
|
||||
await installExtension(VUEJS_DEVTOOLS)
|
||||
} catch (e: any) {
|
||||
console.error('Vue Devtools failed to install:', e.toString())
|
||||
console.error('Vue Devtools failed to install:', e?.toString())
|
||||
}
|
||||
}
|
||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
const settingWindow = windowManager.create(IWindowList.SETTING_WINDOW)
|
||||
settingWindow?.once('show', () => {
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
|
||||
})
|
||||
if (isWindowShouldShowOnStartup(IWindowList.SETTING_WINDOW)) {
|
||||
settingWindow?.show()
|
||||
settingWindow?.focus()
|
||||
}
|
||||
if (!isMacOS) {
|
||||
if (isWindowShouldShowOnStartup(IWindowList.MINI_WINDOW)) {
|
||||
const miniWindow = windowManager.create(IWindowList.MINI_WINDOW)
|
||||
miniWindow?.show()
|
||||
miniWindow?.focus()
|
||||
}
|
||||
}
|
||||
createTray()
|
||||
handleDockIcon()
|
||||
db.set('needReload', false)
|
||||
updateChecker()
|
||||
// 不需要阻塞
|
||||
@@ -105,11 +122,7 @@ class LifeCycle {
|
||||
await remoteNoticeHandler.init()
|
||||
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
|
||||
}
|
||||
if (!app.isReady()) {
|
||||
app.on('ready', readyFunction)
|
||||
} else {
|
||||
readyFunction()
|
||||
}
|
||||
app.whenReady().then(readyFunction)
|
||||
}
|
||||
|
||||
private onRunning () {
|
||||
@@ -134,6 +147,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
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from 'path'
|
||||
import fse from 'fs-extra'
|
||||
import { PicGo as PicGoCore } from 'picgo'
|
||||
import { T } from '~/main/i18n'
|
||||
import { SHORTKEY_COMMAND_UPLOAD } from 'apis/core/bus/constants'
|
||||
// from v2.1.2
|
||||
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
|
||||
// #557 极端情况可能会出现配置不存在,需要重新写入
|
||||
@@ -19,7 +20,7 @@ const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IS
|
||||
}
|
||||
if (shortKeyConfig.upload) {
|
||||
// @ts-ignore
|
||||
shortKeyConfig['picgo:upload'] = {
|
||||
shortKeyConfig[SHORTKEY_COMMAND_UPLOAD] = {
|
||||
enable: true,
|
||||
key: shortKeyConfig.upload,
|
||||
name: 'upload',
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -16,6 +16,29 @@ function beforeOpen () {
|
||||
resolveClipboardImageGenerator()
|
||||
resolveOtherI18nFiles()
|
||||
}
|
||||
function copyFileOutsideOfElectronAsar (
|
||||
sourceInAsarArchive: string,
|
||||
destOutsideAsarArchive: string
|
||||
) {
|
||||
if (fs.existsSync(sourceInAsarArchive)) {
|
||||
// file will be copied
|
||||
if (fs.statSync(sourceInAsarArchive).isFile()) {
|
||||
const file = destOutsideAsarArchive;
|
||||
const dir = path.dirname(file)
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
fs.writeFileSync(file, fs.readFileSync(sourceInAsarArchive))
|
||||
} else if (fs.statSync(sourceInAsarArchive).isDirectory()) {
|
||||
fs.readdirSync(sourceInAsarArchive).forEach(function (fileOrFolderName) {
|
||||
copyFileOutsideOfElectronAsar(
|
||||
`${sourceInAsarArchive}/${fileOrFolderName}`,
|
||||
`${destOutsideAsarArchive}/${fileOrFolderName}`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* macOS 右键菜单
|
||||
@@ -26,7 +49,7 @@ function resolveMacWorkFlow () {
|
||||
return true
|
||||
} else {
|
||||
try {
|
||||
fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
|
||||
copyFileOutsideOfElectronAsar(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
@@ -88,7 +111,7 @@ function resolveOtherI18nFiles () {
|
||||
if (!fs.pathExistsSync(i18nFolder)) {
|
||||
fs.mkdirSync(i18nFolder)
|
||||
}
|
||||
i18nManager.setOutterI18nFolder(i18nFolder)
|
||||
i18nManager.setOuterI18nFolder(i18nFolder)
|
||||
const i18nFiles = fs.readdirSync(path.join(CONFIG_DIR, 'i18n'), {
|
||||
withFileTypes: true
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs-extra'
|
||||
import db from '~/main/apis/core/datastore'
|
||||
import { clipboard, Notification, dialog } from 'electron'
|
||||
import { handleUrlEncode } from '~/universal/utils/common'
|
||||
|
||||
export const handleCopyUrl = (str: string): void => {
|
||||
if (db.get('settings.autoCopy') !== false) {
|
||||
@@ -21,8 +22,8 @@ export const showNotification = (options: IPrivateShowNotificationOption = {
|
||||
}) => {
|
||||
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) {
|
||||
@@ -117,3 +118,38 @@ export const getClipboardFilePath = (): string => {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export const handleUrlEncodeWithSetting = (url: string) => {
|
||||
if (db.get('settings.encodeOutputURL') === true) {
|
||||
url = handleUrlEncode(url)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
export const replaceHost = (url: string, oldHost: string, newHost: string) => {
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
if (parsedUrl.host === oldHost) {
|
||||
parsedUrl.host = newHost
|
||||
}
|
||||
return parsedUrl.toString()
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export const getHost = (url: string = '') => {
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
return parsedUrl.host
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove protocol and suffix
|
||||
*/
|
||||
export const removeProtocolAndSuffix = (url: string = '') => {
|
||||
return url.replace(/^(https?:\/\/)?/, '').replace(/\/$/, '')
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
@@ -0,0 +1,169 @@
|
||||
import { trimValues } from '#/utils/common'
|
||||
import picgo from '@core/picgo'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
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,14 +1,16 @@
|
||||
import { IPasteStyle } from '#/types/enum'
|
||||
import { handleUrlEncode } from '#/utils/common'
|
||||
import { handleUrlEncodeWithSetting } from './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')
|
||||
@@ -19,14 +21,17 @@ const formatCustomLink = (customLink: string, item: ImgInfo) => {
|
||||
}
|
||||
|
||||
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
|
||||
const url = handleUrlEncode(item.url || item.imgUrl)
|
||||
const url = handleUrlEncodeWithSetting(item.url || item.imgUrl)
|
||||
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]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import db from '~/main/apis/core/datastore'
|
||||
import pkg from 'root/package.json'
|
||||
import { lt } from 'semver'
|
||||
import { T } from '~/main/i18n'
|
||||
import { getLatestVersion } from '#/utils/getLatestVersion'
|
||||
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'
|
||||
|
||||
+34
-3
@@ -1,12 +1,43 @@
|
||||
<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'
|
||||
import { useATagClick } from './hooks/useATagClick'
|
||||
|
||||
useATagClick()
|
||||
|
||||
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>
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -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,160 +1,141 @@
|
||||
<template>
|
||||
<div id="config-form">
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
:model="ruleForm"
|
||||
ref="form"
|
||||
size="mini"
|
||||
<div
|
||||
id="config-form"
|
||||
:class="props.colorMode === 'white' ? 'white' : ''"
|
||||
>
|
||||
<BaseConfigForm
|
||||
ref="$form"
|
||||
:config="configList"
|
||||
:form-model="formModel"
|
||||
>
|
||||
<el-form-item
|
||||
v-for="(item, index) in configList"
|
||||
: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]"
|
||||
:placeholder="item.message || item.name"
|
||||
></el-input>
|
||||
<el-select
|
||||
v-else-if="item.type === 'list' && item.choices"
|
||||
v-model="ruleForm[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
<template #extra-form>
|
||||
<el-form-item
|
||||
v-if="isUploader"
|
||||
:label="$T('UPLOADER_CONFIG_NAME')"
|
||||
required
|
||||
prop="_configName"
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:key="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="item.type === 'checkbox' && item.choices"
|
||||
v-model="ruleForm[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
multiple
|
||||
collapse-tags
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:key="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>
|
||||
</el-form>
|
||||
<el-input
|
||||
v-model="formModel._configName"
|
||||
type="input"
|
||||
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<slot />
|
||||
</BaseConfigForm>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {
|
||||
Component,
|
||||
Vue,
|
||||
Prop,
|
||||
Watch
|
||||
} from 'vue-property-decorator'
|
||||
import { cloneDeep, union } from 'lodash'
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch, toRefs } from 'vue'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import { useRoute } from 'vue-router'
|
||||
import BaseConfigForm from './form/BaseConfigForm.vue'
|
||||
import { useConfigForm } from '@/hooks/useConfigForm'
|
||||
|
||||
@Component({
|
||||
name: 'config-form'
|
||||
})
|
||||
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)
|
||||
}
|
||||
interface IProps {
|
||||
config: any[]
|
||||
type: 'uploader' | 'transformer' | 'plugin'
|
||||
id: string
|
||||
colorMode?: 'white' | 'dark'
|
||||
}
|
||||
|
||||
async validate () {
|
||||
return new Promise((resolve) => {
|
||||
// @ts-ignore
|
||||
this.$refs.form.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(this.ruleForm)
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
const props = defineProps<IProps>()
|
||||
const $route = useRoute()
|
||||
const $form = ref<IFormInstance>()
|
||||
const configList = ref<IPicGoPluginConfig[]>([])
|
||||
const formModel = reactive<IStringKeyMap>({})
|
||||
const isUploader = props.type === 'uploader'
|
||||
|
||||
getConfigType () {
|
||||
switch (this.type) {
|
||||
case 'plugin': {
|
||||
return this.id
|
||||
}
|
||||
case 'uploader': {
|
||||
return `picBed.${this.id}`
|
||||
}
|
||||
case 'transformer': {
|
||||
return `transformer.${this.id}`
|
||||
}
|
||||
default:
|
||||
return 'unknown'
|
||||
async function validate (): Promise<IStringKeyMap | false> {
|
||||
const res = await $form.value?.validate() || false
|
||||
return res
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
const handleConfigForm = useConfigForm()
|
||||
|
||||
async function handleConfig (inputConfig: IPicGoPluginConfig[]) {
|
||||
const currentConfig = await getCurConfigFormData()
|
||||
const resetConfig = isUploader && !$route.params.configId
|
||||
Object.assign(formModel, currentConfig)
|
||||
configList.value = handleConfigForm(inputConfig, formModel, currentConfig, resetConfig)
|
||||
}
|
||||
|
||||
async function getCurConfigFormData () {
|
||||
const configId = $route.params.configId
|
||||
const configType = getConfigType()
|
||||
if (isUploader) {
|
||||
const curTypeConfigList = await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`) || []
|
||||
return curTypeConfigList.find(i => i._id === configId) || {}
|
||||
} else {
|
||||
const config = await getConfig<IStringKeyMap>(configType)
|
||||
return config || {}
|
||||
}
|
||||
}
|
||||
|
||||
async function resetConfig () {
|
||||
const config = await getCurConfigFormData()
|
||||
Object.assign(formModel, config)
|
||||
}
|
||||
|
||||
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => {
|
||||
handleConfig(val)
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
getConfigType,
|
||||
resetConfig
|
||||
})
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.config-form-common-tips
|
||||
a
|
||||
color #409EFF
|
||||
text-decoration none
|
||||
#config-form
|
||||
.el-form
|
||||
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,70 +0,0 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
: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>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron'
|
||||
import {
|
||||
SHOW_INPUT_BOX,
|
||||
SHOW_INPUT_BOX_RESPONSE
|
||||
} from '~/universal/events/constants'
|
||||
@Component({
|
||||
name: 'input-box-dialog'
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
||||
ipcEventHandler (evt: IpcRendererEvent, options: IShowInputBoxOption) {
|
||||
this.initInputBoxValue(options)
|
||||
}
|
||||
|
||||
initInputBoxValue (options: IShowInputBoxOption) {
|
||||
this.inputBoxValue = options.value || ''
|
||||
this.inputBoxOptions.title = options.title || ''
|
||||
this.inputBoxOptions.placeholder = options.placeholder || ''
|
||||
this.showInputBoxVisible = true
|
||||
}
|
||||
|
||||
handleInputBoxCancel () {
|
||||
// TODO: RPCServer
|
||||
this.showInputBoxVisible = false
|
||||
ipcRenderer.send(SHOW_INPUT_BOX, '')
|
||||
this.$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)
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener(SHOW_INPUT_BOX, this.ipcEventHandler)
|
||||
this.$bus.$off(SHOW_INPUT_BOX)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -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,63 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<ConfirmDialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
@confirm="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<template #body>
|
||||
<BaseConfigForm
|
||||
ref="$form"
|
||||
:config="configList"
|
||||
:form-model="formModel"
|
||||
theme="light"
|
||||
/>
|
||||
</template>
|
||||
</ConfirmDialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import ConfirmDialog from '@/components/dialog/ConfirmDialog.vue'
|
||||
import { useIPCOn } from '@/hooks/useIPC'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import BaseConfigForm from '@/components/form/BaseConfigForm.vue'
|
||||
import { useConfigForm } from '@/hooks/useConfigForm'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
|
||||
const configList = ref<IPicGoPluginConfig[]>([])
|
||||
const formModel = reactive<IStringKeyMap>({})
|
||||
const $form = ref<IFormInstance>()
|
||||
const title = ref('')
|
||||
|
||||
const handleConfigForm = useConfigForm()
|
||||
|
||||
const visible = ref(false)
|
||||
useIPCOn(IRPCActionType.OPEN_CONFIG_DIALOG, (event, options: IPicGoPluginShowConfigDialogOption) => {
|
||||
visible.value = true
|
||||
configList.value = handleConfigForm(options.config, formModel)
|
||||
title.value = options.title
|
||||
})
|
||||
const handleConfirm = async () => {
|
||||
const res = await $form.value?.validate() || false
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
sendToMain(IRPCActionType.OPEN_CONFIG_DIALOG, res)
|
||||
visible.value = false
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
sendToMain(IRPCActionType.OPEN_CONFIG_DIALOG, false)
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ConfigFormDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
@close="handleClose"
|
||||
>
|
||||
<slot name="body" />
|
||||
<template #footer>
|
||||
<slot
|
||||
v-if="footerSlots"
|
||||
name="footer"
|
||||
/>
|
||||
<template v-else>
|
||||
<ElButton
|
||||
type="default"
|
||||
round
|
||||
@click="handleCancel"
|
||||
>
|
||||
{{ cancelButtonText }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
round
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ confirmButtonText }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { useSlots, ref } from 'vue'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
title: string
|
||||
confirmButtonText?: string
|
||||
cancelButtonText?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
confirmButtonText: $T('CONFIRM'),
|
||||
cancelButtonText: $T('CANCEL')
|
||||
})
|
||||
const $emit = defineEmits(['confirm', 'cancel', 'close'])
|
||||
|
||||
const isCancel = ref(false)
|
||||
const isConfirm = ref(false)
|
||||
|
||||
const visible = useVModel(props, 'modelValue')
|
||||
|
||||
const handleClose = () => {
|
||||
if (isConfirm.value) {
|
||||
return
|
||||
}
|
||||
if (isCancel.value) {
|
||||
$emit('cancel')
|
||||
isCancel.value = false
|
||||
} else {
|
||||
$emit('close')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
isCancel.value = true
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
isConfirm.value = true
|
||||
$emit('confirm')
|
||||
}
|
||||
|
||||
const footerSlots = !!useSlots().footer
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ConfirmDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="showInputBoxVisible"
|
||||
:title="inputBoxOptions.title || $T('INPUT')"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<el-input
|
||||
v-model="inputBoxValue"
|
||||
: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" 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'
|
||||
import $bus from '@/utils/bus'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
const inputBoxValue = ref('')
|
||||
const showInputBoxVisible = ref(false)
|
||||
const inputBoxOptions = reactive({
|
||||
title: '',
|
||||
placeholder: ''
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
ipcRenderer.on(SHOW_INPUT_BOX, ipcEventHandler)
|
||||
$bus.on(SHOW_INPUT_BOX, initInputBoxValue)
|
||||
})
|
||||
|
||||
function ipcEventHandler (evt: IpcRendererEvent, options: IShowInputBoxOption) {
|
||||
initInputBoxValue(options)
|
||||
}
|
||||
|
||||
function initInputBoxValue (options: IShowInputBoxOption) {
|
||||
inputBoxValue.value = options.value || ''
|
||||
inputBoxOptions.title = options.title || ''
|
||||
inputBoxOptions.placeholder = options.placeholder || ''
|
||||
showInputBoxVisible.value = true
|
||||
}
|
||||
|
||||
function handleInputBoxCancel () {
|
||||
// TODO: RPCServer
|
||||
showInputBoxVisible.value = false
|
||||
sendToMain(SHOW_INPUT_BOX, '')
|
||||
$bus.emit(SHOW_INPUT_BOX_RESPONSE, '')
|
||||
}
|
||||
|
||||
function handleInputBoxConfirm () {
|
||||
showInputBoxVisible.value = false
|
||||
sendToMain(SHOW_INPUT_BOX, inputBoxValue.value)
|
||||
$bus.emit(SHOW_INPUT_BOX_RESPONSE, inputBoxValue.value)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeListener(SHOW_INPUT_BOX, ipcEventHandler)
|
||||
$bus.off(SHOW_INPUT_BOX)
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'InputBoxDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div
|
||||
id="base-config-form"
|
||||
:class="theme ?? 'dark'"
|
||||
>
|
||||
<el-form
|
||||
ref="$form"
|
||||
label-position="left"
|
||||
label-width="50%"
|
||||
:model="form"
|
||||
size="small"
|
||||
>
|
||||
<slot name="extra-form" />
|
||||
<!-- dynamic config -->
|
||||
<el-form-item
|
||||
v-for="(item, index) in config"
|
||||
:key="item.name + index"
|
||||
:required="item.required"
|
||||
:prop="item.name"
|
||||
:class="index !== config.length - 1 ? 'has-border' : ''"
|
||||
>
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
{{ item.alias || item.name }}
|
||||
<template v-if="item.tips">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
placement="right"
|
||||
>
|
||||
<template #content>
|
||||
<span
|
||||
class="config-form-common-tips"
|
||||
v-html="transformMarkdownToHTML(item.tips)"
|
||||
/>
|
||||
</template>
|
||||
<el-icon class="ml-[4px] cursor-pointer hover:text-blue">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-input
|
||||
v-if="item.type === 'input' || item.type === 'password'"
|
||||
v-model="form[item.name]"
|
||||
:type="item.type === 'password' ? 'password' : 'input'"
|
||||
:placeholder="item.message || item.name"
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="item.type === 'list' && item.choices"
|
||||
v-model="form[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.name || choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="item.type === 'checkbox' && item.choices"
|
||||
v-model="form[item.name]"
|
||||
:placeholder="item.message || item.name"
|
||||
multiple
|
||||
collapse-tags
|
||||
>
|
||||
<el-option
|
||||
v-for="choice in item.choices"
|
||||
:key="choice.value || choice"
|
||||
:label="choice.name || choice.value || choice"
|
||||
:value="choice.value || choice"
|
||||
/>
|
||||
</el-select>
|
||||
<el-switch
|
||||
v-else-if="item.type === 'confirm'"
|
||||
v-model="form[item.name]"
|
||||
:active-text="item.confirmText || 'yes'"
|
||||
:inactive-text="item.cancelText || 'no'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<slot />
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
const $form = ref<FormInstance>()
|
||||
|
||||
interface IProps {
|
||||
config: IPicGoPluginConfig[]
|
||||
formModel: IStringKeyMap
|
||||
theme?: 'light' | 'dark'
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const form = useVModel(props, 'formModel')
|
||||
|
||||
function transformMarkdownToHTML (markdown: string) {
|
||||
try {
|
||||
return marked.parse(markdown)
|
||||
} catch (e) {
|
||||
return markdown
|
||||
}
|
||||
}
|
||||
|
||||
async function validate (): Promise<IStringKeyMap | false> {
|
||||
return new Promise((resolve) => {
|
||||
$form.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
resolve(form.value)
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'BaseConfigForm'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#base-config-form
|
||||
.el-form
|
||||
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-radio-group
|
||||
margin-left 25px
|
||||
.el-switch__label
|
||||
&.is-active
|
||||
color #409EFF
|
||||
&.light
|
||||
.el-form-item.has-border
|
||||
border-bottom 1px solid #ddd
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<el-form-item
|
||||
:label="label"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="handleClick"
|
||||
>
|
||||
{{ buttonLabel }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
interface IProps {
|
||||
label: string
|
||||
buttonLabel: string
|
||||
}
|
||||
|
||||
defineProps<IProps>()
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click')
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ButtonFormItem'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<el-form
|
||||
label-position="left"
|
||||
label-width="50%"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
:label="label"
|
||||
>
|
||||
<el-select
|
||||
v-model="currentValue"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
:placeholder="placeholder"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in list"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup generic="T extends string | number">
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
interface IProps {
|
||||
modelValue: T
|
||||
label: string
|
||||
placeholder?: string
|
||||
list: {
|
||||
label: string
|
||||
value: T
|
||||
}[]
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const currentValue = useVModel(props, 'modelValue')
|
||||
|
||||
const handleChange = (val: T) => {
|
||||
emit('change', val)
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SelectFormItem'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
{{ props.label }}
|
||||
<template v-if="props.tooltips">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="props.tooltips"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="ml-[4px] cursor-pointer hover:text-blue">
|
||||
<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 { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import { showNotification } from '@/utils/common'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
interface IProps {
|
||||
tooltips?: string
|
||||
settingProps: keyof ISettingForm
|
||||
label: string
|
||||
modelValue: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const value = useVModel(props, 'modelValue')
|
||||
|
||||
const handleChange = (value: ISwitchValueType) => {
|
||||
saveConfig(`settings.${props.settingProps}`, value)
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
showNotification(props.label, $T('TIPS_SET_SUCCEED'))
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SwitchFormItem'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,19 @@
|
||||
import { openURL } from '@/utils/common'
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export function useATagClick () {
|
||||
const handleATagClick = (e: MouseEvent) => {
|
||||
if (e.target instanceof HTMLAnchorElement) {
|
||||
if (e.target.href) {
|
||||
e.preventDefault()
|
||||
openURL(e.target.href)
|
||||
}
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleATagClick)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleATagClick)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { cloneDeep, union } from 'lodash'
|
||||
/**
|
||||
*
|
||||
* @param configList origin config list
|
||||
* @param formModel el-form form model for default value
|
||||
* @param currentConfig current config
|
||||
* @param resetConfigForm reset form model -> clear default value
|
||||
* @returns transformed config list
|
||||
*/
|
||||
export const useConfigForm = () => {
|
||||
return (configList: IPicGoPluginConfig[], formModel: IStringKeyMap, currentConfig?: IStringKeyMap, resetConfigForm?: boolean) => {
|
||||
if (configList.length > 0) {
|
||||
return cloneDeep(configList).map((item) => {
|
||||
// if (!configId) return item
|
||||
if (resetConfigForm) 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 (currentConfig && currentConfig[item.name] !== undefined) {
|
||||
defaultValue = currentConfig[item.name]
|
||||
}
|
||||
formModel[item.name] = defaultValue
|
||||
return item
|
||||
})
|
||||
}
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -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,16 @@
|
||||
import { computed, getCurrentInstance } from 'vue'
|
||||
|
||||
export type VModelObject = object
|
||||
|
||||
/**
|
||||
* v-model for single prop
|
||||
*/
|
||||
export function useVModel<T extends VModelObject, K extends keyof T> (props: T, key: K) {
|
||||
const vm = getCurrentInstance()
|
||||
return computed({
|
||||
get: () => props[key],
|
||||
set: (value) => {
|
||||
vm?.emit(`update:${key as string}`, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { getCurrentInstance, reactive, UnwrapNestedRefs, watch } from 'vue'
|
||||
|
||||
/**
|
||||
* v-model for multiple props
|
||||
*/
|
||||
export function useVModelValues<T extends object> (props: T, keys: Array<keyof T>) {
|
||||
const vm = getCurrentInstance()
|
||||
const obj = {} as T
|
||||
keys.forEach(key => {
|
||||
obj[key] = props[key]
|
||||
})
|
||||
const mutableValue = reactive(obj) as T
|
||||
|
||||
watch(() => props, (val) => {
|
||||
keys.forEach(key => {
|
||||
mutableValue[key] = val[key]
|
||||
})
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
function updateProps () {
|
||||
for (const key of keys) {
|
||||
vm?.emit(`update:${key as string}`, mutableValue[key])
|
||||
}
|
||||
}
|
||||
|
||||
return [mutableValue as UnwrapNestedRefs<T>, updateProps] as const
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export class I18nManager {
|
||||
ipcRenderer.send(GET_CURRENT_LANGUAGE)
|
||||
ipcRenderer.once(GET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => {
|
||||
this.setLocales(lang, locales)
|
||||
bus.$emit(FORCE_UPDATE)
|
||||
bus.emit(FORCE_UPDATE)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export class I18nManager {
|
||||
this.getLanguageList()
|
||||
ipcRenderer.on(SET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => {
|
||||
this.setLocales(lang, locales)
|
||||
bus.$emit(FORCE_UPDATE)
|
||||
bus.emit(FORCE_UPDATE)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+240
-168
@@ -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,18 +200,33 @@
|
||||
<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 as $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 InputBoxDialog from '@/components/dialog/InputBoxDialog.vue'
|
||||
import {
|
||||
MINIMIZE_WINDOW,
|
||||
CLOSE_WINDOW,
|
||||
@@ -163,116 +235,118 @@ import {
|
||||
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(GET_PICBEDS)
|
||||
ipcRenderer.on(GET_PICBEDS, 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(GET_PICBEDS)
|
||||
}
|
||||
|
||||
handleSelect (index: string) {
|
||||
const type = index.match(/picbeds-/)
|
||||
if (type === null) {
|
||||
this.$router.push({
|
||||
name: index
|
||||
})
|
||||
} else {
|
||||
const picBed = index.replace(/picbeds-/, '')
|
||||
this.$router.push({
|
||||
name: 'picbeds',
|
||||
params: {
|
||||
type: picBed
|
||||
}
|
||||
})
|
||||
// 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(GET_PICBEDS, this.getPicBeds)
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'MainPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
@@ -289,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
|
||||
@@ -304,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
|
||||
@@ -333,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
|
||||
@@ -355,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
|
||||
@@ -385,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
|
||||
|
||||
+479
-404
File diff suppressed because it is too large
Load Diff
+163
-147
@@ -1,187 +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
|
||||
}
|
||||
})
|
||||
}
|
||||
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)
|
||||
})
|
||||
|
||||
onDrop (e: DragEvent) {
|
||||
this.dragover = false
|
||||
const items = e.dataTransfer!.items
|
||||
watch(progress, (val) => {
|
||||
if (val === 100) {
|
||||
setTimeout(() => {
|
||||
showProgress.value = false
|
||||
showError.value = false
|
||||
}, 1000)
|
||||
setTimeout(() => {
|
||||
progress.value = 0
|
||||
}, 1200)
|
||||
}
|
||||
})
|
||||
|
||||
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.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')
|
||||
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'>
|
||||
|
||||
+127
-761
@@ -1,782 +1,143 @@
|
||||
<template>
|
||||
<div id="picgo-setting">
|
||||
<div class="view-title">
|
||||
{{ $T('PICGO_SETTINGS') }} - <i class="el-icon-document" @click="goConfigPage"></i>
|
||||
</div>
|
||||
<el-row
|
||||
class="view-title"
|
||||
align="middle"
|
||||
justify="center"
|
||||
>
|
||||
{{ $T('PICGO_SETTINGS') }} -
|
||||
<el-icon
|
||||
class="el-icon-document"
|
||||
@click="goConfigPage"
|
||||
>
|
||||
<Reading />
|
||||
</el-icon>
|
||||
</el-row>
|
||||
<el-row class="setting-list">
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-row>
|
||||
<el-form
|
||||
label-position="left"
|
||||
label-width="10"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_CHOOSE_LANGUAGE')"
|
||||
<el-col
|
||||
:span="20"
|
||||
:offset="2"
|
||||
>
|
||||
<el-row style="width: 100%">
|
||||
<el-form
|
||||
label-position="left"
|
||||
label-width="50%"
|
||||
size="small"
|
||||
>
|
||||
<!-- <el-button type="primary" round size="mini" @click="openFile('data.json')">{{ $T('SETTINGS_CLICK_TO_OPEN') }}</el-button> -->
|
||||
<el-select
|
||||
v-model="currentLanguage"
|
||||
size="mini"
|
||||
style="width: 100%"
|
||||
@change="handleLanguageChange"
|
||||
:placeholder="$T('SETTINGS_CHOOSE_LANGUAGE')">
|
||||
<el-option
|
||||
v-for="item in languageList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_OPEN_CONFIG_FILE')"
|
||||
>
|
||||
<el-button type="primary" round size="mini" @click="openFile('data.json')">{{ $T('SETTINGS_CLICK_TO_OPEN') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_LOG_FILE')"
|
||||
>
|
||||
<el-button type="primary" round size="mini" @click="openLogSetting">{{ $T('SETTINGS_CLICK_TO_SET') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SHORTCUT')"
|
||||
>
|
||||
<el-button type="primary" round size="mini" @click="goShortCutPage">{{ $T('SETTINGS_CLICK_TO_SET') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
|
||||
>
|
||||
<el-button type="primary" round size="mini" @click="customLinkVisible = true">{{ $T('SETTINGS_CLICK_TO_SET') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
|
||||
>
|
||||
<el-button type="primary" round size="mini" @click="proxyVisible = true">{{ $T('SETTINGS_CLICK_TO_SET') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER')"
|
||||
>
|
||||
<el-button type="primary" round size="mini" @click="serverVisible = true">{{ $T('SETTINGS_CLICK_TO_SET') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_CHECK_UPDATE')"
|
||||
>
|
||||
<el-button type="primary" round size="mini" @click="checkUpdate">{{ $T('SETTINGS_CLICK_TO_CHECK') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_OPEN_UPDATE_HELPER')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.updateHelper"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="updateHelperChange"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-show="form.updateHelper"
|
||||
:label="$T('SETTINGS_ACCEPT_BETA_UPDATE')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.checkBetaUpdate"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="checkBetaUpdateChange"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LAUNCH_ON_BOOT')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.autoStart"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleAutoStartChange"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_RENAME_BEFORE_UPLOAD')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.rename"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleRename"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_TIMESTAMP_RENAME')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.autoRename"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleAutoRename"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_OPEN_UPLOAD_TIPS')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.uploadNotification"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleUploadNotification"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="os !== 'darwin'"
|
||||
:label="$T('SETTINGS_MINI_WINDOW_ON_TOP')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.miniWindowOntop"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleMiniWindowOntop"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.autoCopyUrl"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="handleAutoCopyUrl"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
>
|
||||
<div class="el-form-item__custom-label" slot="label">
|
||||
{{ $T('SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD') }}
|
||||
<el-tooltip class="item" effect="dark" :content="$T('BUILTIN_CLIPBOARD_TIPS')" placement="right">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch
|
||||
v-model="form.useBuiltinClipboard"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
@change="useBuiltinClipboardChange"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:style="{ marginRight: '-64px' }"
|
||||
:label="$T('CHOOSE_SHOWED_PICBED')"
|
||||
>
|
||||
<el-checkbox-group
|
||||
v-model="form.showPicBedList"
|
||||
@change="handleShowPicBedListChange"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="item in picBed"
|
||||
:label="item.name"
|
||||
:key="item.name"
|
||||
></el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<SelectAreaSettings
|
||||
:settings="form"
|
||||
/>
|
||||
<ButtonAreaSettings
|
||||
v-model:proxy="proxy"
|
||||
:settings="form"
|
||||
/>
|
||||
<SwitchAreaSettings
|
||||
:settings="form"
|
||||
/>
|
||||
<CustomAreaSettings
|
||||
:settings="form"
|
||||
/>
|
||||
</el-form>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog
|
||||
:title="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
|
||||
:visible.sync="customLinkVisible"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<el-form
|
||||
label-position="top"
|
||||
:model="customLink"
|
||||
ref="customLink"
|
||||
:rules="rules"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
prop="value"
|
||||
>
|
||||
<div class="custom-title">
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_URL') }}
|
||||
</div>
|
||||
<div class="custom-title">
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_FILENAME') }}
|
||||
</div>
|
||||
<el-input
|
||||
class="align-center"
|
||||
v-model="customLink.value"
|
||||
:autofocus="true"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_SUCH_AS') }}[$fileName]($url)
|
||||
</div>
|
||||
<span slot="footer">
|
||||
<el-button @click="cancelCustomLink" round>{{ $T('CANCEL') }}</el-button>
|
||||
<el-button type="primary" @click="confirmCustomLink" round>{{ $T('CONFIRM') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
:title="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
|
||||
:visible.sync="proxyVisible"
|
||||
:modal-append-to-body="false"
|
||||
width="70%"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
:model="customLink"
|
||||
ref="customLink"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_UPLOAD_PROXY')"
|
||||
>
|
||||
<el-input
|
||||
v-model="proxy"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:http://127.0.0.1:1080`"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_PLUGIN_INSTALL_PROXY')"
|
||||
>
|
||||
<el-input
|
||||
v-model="npmProxy"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:http://127.0.0.1:1080`"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_PLUGIN_INSTALL_MIRROR')"
|
||||
>
|
||||
<el-input
|
||||
v-model="npmRegistry"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:https://registry.npmmirror.com`"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="cancelProxy" round>{{ $T('CANCEL') }}</el-button>
|
||||
<el-button type="primary" @click="confirmProxy" round>{{ $T('CONFIRM') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
:title="$T('SETTINGS_CHECK_UPDATE')"
|
||||
:visible.sync="checkUpdateVisible"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<div>
|
||||
{{ $T('SETTINGS_CURRENT_VERSION') }}: {{ version }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $T('SETTINGS_NEWEST_VERSION') }}: {{ latestVersion ? latestVersion : `${$T('SETTINGS_GETING')}...` }}
|
||||
</div>
|
||||
<div v-if="needUpdate">
|
||||
{{ $T('SETTINGS_TIPS_HAS_NEW_VERSION') }}
|
||||
</div>
|
||||
<span slot="footer">
|
||||
<el-button @click="cancelCheckVersion" round>{{ $T('CANCEL') }}</el-button>
|
||||
<el-button type="primary" @click="confirmCheckVersion" round>{{ $T('CONFIRM') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
:title="$T('SETTINGS_SET_LOG_FILE')"
|
||||
:visible.sync="logFileVisible"
|
||||
:modal-append-to-body="false"
|
||||
width="500px"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="150px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LOG_FILE')"
|
||||
>
|
||||
<el-button type="primary" round size="mini" @click="openFile('picgo.log')">{{ $T('SETTINGS_CLICK_TO_OPEN') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LOG_LEVEL')"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.logLevel"
|
||||
multiple
|
||||
collapse-tags
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-option
|
||||
v-for="(value, key) of logLevel"
|
||||
:key="key"
|
||||
:label="value"
|
||||
:value="key"
|
||||
:disabled="handleLevelDisabled(key)"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$T('SETTINGS_LOG_FILE_SIZE')} (MB)`"
|
||||
>
|
||||
<el-input-number
|
||||
style="width: 100%;"
|
||||
v-model="form.logFileSizeLimit"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:10`"
|
||||
:controls="false"
|
||||
:min="1"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="cancelLogLevelSetting" round>{{ $T('CANCEL') }}</el-button>
|
||||
<el-button type="primary" @click="confirmLogLevelSetting" round>{{ $T('CONFIRM') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
class="server-dialog"
|
||||
width="60%"
|
||||
:title="$T('SETTINGS_SET_PICGO_SERVER')"
|
||||
:visible.sync="serverVisible"
|
||||
:modal-append-to-body="false"
|
||||
>
|
||||
<div class="notice-text">
|
||||
{{ $T('SETTINGS_TIPS_SERVER_NOTICE') }}
|
||||
</div>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_ENABLE_SERVER')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="server.enable"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
<template v-if="server.enable">
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER_HOST')"
|
||||
>
|
||||
<el-input
|
||||
type="input"
|
||||
v-model="server.host"
|
||||
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_HOST')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER_PORT')"
|
||||
>
|
||||
<el-input
|
||||
type="number"
|
||||
v-model="server.port"
|
||||
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_PORT')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="cancelServerSetting" round>{{ $T('CANCEL') }}</el-button>
|
||||
<el-button type="primary" @click="confirmServerSetting" round>{{ $T('CONFIRM') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import keyDetect from '@/utils/key-binding'
|
||||
import pkg from 'root/package.json'
|
||||
<script lang="ts" setup>
|
||||
import { ElForm } from 'element-plus'
|
||||
import { Reading } from '@element-plus/icons-vue'
|
||||
import { IConfig } from 'picgo'
|
||||
import { PICGO_OPEN_FILE, OPEN_URL, GET_PICBEDS } from '#/events/constants'
|
||||
import {
|
||||
ipcRenderer
|
||||
} from 'electron'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { T, i18nManager } from '@/i18n/index'
|
||||
import { enforceNumber } from '~/universal/utils/common'
|
||||
import { getLatestVersion } from '#/utils/getLatestVersion'
|
||||
import { compare } from 'compare-versions'
|
||||
import { STABLE_RELEASE_URL, BETA_RELEASE_URL } from '#/utils/static'
|
||||
const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) => void) => {
|
||||
if (!/\$url/.test(value)) {
|
||||
return callback(new Error(T('TIPS_MUST_CONTAINS_URL')))
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import { enforceNumber, isLinux } from '~/universal/utils/common'
|
||||
import { onBeforeMount, reactive, ref } from 'vue'
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
import ButtonAreaSettings from './components/settings/buttonArea/ButtonAreaSettings.vue'
|
||||
import SwitchAreaSettings from './components/settings/switchArea/SwitchAreaSettings.vue'
|
||||
import CustomAreaSettings from './components/settings/customArea/CustomAreaSettings.vue'
|
||||
import SelectAreaSettings from './components/settings/selectArea/SelectAreaSettings.vue'
|
||||
import { openURL } from '@/utils/common'
|
||||
import { IStartupMode } from '#/types/enum'
|
||||
|
||||
@Component({
|
||||
name: 'picgo-setting'
|
||||
})
|
||||
export default class extends Vue {
|
||||
form: ISettingForm = {
|
||||
updateHelper: false,
|
||||
showPicBedList: [],
|
||||
autoStart: false,
|
||||
rename: false,
|
||||
autoRename: false,
|
||||
uploadNotification: false,
|
||||
miniWindowOntop: false,
|
||||
logLevel: ['all'],
|
||||
autoCopyUrl: true,
|
||||
checkBetaUpdate: true,
|
||||
useBuiltinClipboard: false,
|
||||
language: 'zh-CN',
|
||||
logFileSizeLimit: 10
|
||||
}
|
||||
|
||||
languageList = i18nManager.languageList.map(item => ({
|
||||
label: item.label,
|
||||
value: item.value
|
||||
}))
|
||||
|
||||
currentLanguage = 'zh-CN'
|
||||
picBed: IPicBedType[] = []
|
||||
logFileVisible = false
|
||||
keyBindingVisible = false
|
||||
customLinkVisible = false
|
||||
checkUpdateVisible = false
|
||||
serverVisible = false
|
||||
proxyVisible = false
|
||||
customLink = {
|
||||
value: '$url'
|
||||
}
|
||||
|
||||
shortKey: IShortKeyMap = {
|
||||
upload: ''
|
||||
}
|
||||
|
||||
proxy = ''
|
||||
npmRegistry = ''
|
||||
npmProxy = ''
|
||||
rules = {
|
||||
value: [
|
||||
{ validator: customLinkRule, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
logLevel = {
|
||||
all: this.$T('SETTINGS_LOG_LEVEL_ALL'),
|
||||
success: this.$T('SETTINGS_LOG_LEVEL_SUCCESS'),
|
||||
error: this.$T('SETTINGS_LOG_LEVEL_ERROR'),
|
||||
info: this.$T('SETTINGS_LOG_LEVEL_INFO'),
|
||||
warn: this.$T('SETTINGS_LOG_LEVEL_WARN'),
|
||||
none: this.$T('SETTINGS_LOG_LEVEL_NONE')
|
||||
}
|
||||
|
||||
server = {
|
||||
const form = reactive<ISettingForm>({
|
||||
showUpdateTip: false,
|
||||
showPicBedList: [],
|
||||
autoStart: false,
|
||||
rename: false,
|
||||
autoRename: false,
|
||||
uploadNotification: false,
|
||||
miniWindowOnTop: false,
|
||||
logLevel: ['all'],
|
||||
autoCopyUrl: true,
|
||||
checkBetaUpdate: true,
|
||||
useBuiltinClipboard: false,
|
||||
language: 'zh-CN',
|
||||
logFileSizeLimit: 10,
|
||||
encodeOutputURL: true,
|
||||
showDockIcon: true,
|
||||
customLink: '$url',
|
||||
npmProxy: '',
|
||||
npmRegistry: '',
|
||||
server: {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
},
|
||||
startupMode: IStartupMode.HIDE
|
||||
})
|
||||
|
||||
const proxy = ref('')
|
||||
|
||||
onBeforeMount(() => {
|
||||
initData()
|
||||
})
|
||||
|
||||
async function initData () {
|
||||
const config = (await getConfig<IConfig>())!
|
||||
if (config !== undefined) {
|
||||
const settings = config.settings || {}
|
||||
const picBed = config.picBed
|
||||
form.showUpdateTip = settings.showUpdateTip || false
|
||||
form.autoStart = settings.autoStart || false
|
||||
form.rename = settings.rename || false
|
||||
form.autoRename = settings.autoRename || false
|
||||
form.uploadNotification = settings.uploadNotification || false
|
||||
form.miniWindowOnTop = settings.miniWindowOnTop || false
|
||||
form.logLevel = initLogLevel(settings.logLevel || [])
|
||||
form.autoCopyUrl = settings.autoCopy === undefined ? true : settings.autoCopy
|
||||
form.checkBetaUpdate = settings.checkBetaUpdate === undefined ? true : settings.checkBetaUpdate
|
||||
form.useBuiltinClipboard = settings.useBuiltinClipboard === undefined ? false : settings.useBuiltinClipboard
|
||||
form.language = settings.language ?? 'zh-CN'
|
||||
form.encodeOutputURL = settings.encodeOutputURL === undefined ? false : settings.encodeOutputURL
|
||||
form.customLink = settings.customLink || '$url'
|
||||
form.npmProxy = settings.npmProxy || ''
|
||||
form.npmRegistry = settings.npmRegistry || ''
|
||||
proxy.value = picBed.proxy || ''
|
||||
form.server = settings.server
|
||||
form.logFileSizeLimit = enforceNumber(settings.logFileSizeLimit) || 10
|
||||
form.showDockIcon = settings.showDockIcon === undefined ? true : settings.showDockIcon
|
||||
form.startupMode = settings.startupMode || (isLinux ? IStartupMode.SHOW_MINI_WINDOW : IStartupMode.HIDE)
|
||||
}
|
||||
}
|
||||
|
||||
version = pkg.version
|
||||
latestVersion = ''
|
||||
os = ''
|
||||
|
||||
get needUpdate () {
|
||||
if (this.latestVersion) {
|
||||
return this.compareVersion2Update(this.version, this.latestVersion)
|
||||
function initLogLevel (logLevel: string | string[]) {
|
||||
if (!Array.isArray(logLevel)) {
|
||||
if (logLevel && logLevel.length > 0) {
|
||||
logLevel = [logLevel]
|
||||
} else {
|
||||
return false
|
||||
logLevel = ['all']
|
||||
}
|
||||
}
|
||||
return logLevel
|
||||
}
|
||||
|
||||
created () {
|
||||
this.os = process.platform
|
||||
ipcRenderer.send(GET_PICBEDS)
|
||||
ipcRenderer.on(GET_PICBEDS, this.getPicBeds)
|
||||
this.initData()
|
||||
}
|
||||
function goConfigPage () {
|
||||
openURL('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
|
||||
}
|
||||
|
||||
async initData () {
|
||||
const config = (await this.getConfig<IConfig>())!
|
||||
if (config !== undefined) {
|
||||
const settings = config.settings || {}
|
||||
const picBed = config.picBed
|
||||
this.form.updateHelper = settings.showUpdateTip || false
|
||||
this.form.autoStart = settings.autoStart || false
|
||||
this.form.rename = settings.rename || false
|
||||
this.form.autoRename = settings.autoRename || false
|
||||
this.form.uploadNotification = settings.uploadNotification || false
|
||||
this.form.miniWindowOntop = settings.miniWindowOntop || false
|
||||
this.form.logLevel = this.initLogLevel(settings.logLevel || [])
|
||||
this.form.autoCopyUrl = settings.autoCopy === undefined ? true : settings.autoCopy
|
||||
this.form.checkBetaUpdate = settings.checkBetaUpdate === undefined ? true : settings.checkBetaUpdate
|
||||
this.form.useBuiltinClipboard = settings.useBuiltinClipboard === undefined ? false : settings.useBuiltinClipboard
|
||||
this.form.language = settings.language ?? 'zh-CN'
|
||||
this.currentLanguage = settings.language ?? 'zh-CN'
|
||||
this.customLink.value = settings.customLink || '$url'
|
||||
this.shortKey.upload = settings.shortKey.upload
|
||||
this.proxy = picBed.proxy || ''
|
||||
this.npmRegistry = settings.registry || ''
|
||||
this.npmProxy = settings.proxy || ''
|
||||
this.server = settings.server || {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
}
|
||||
this.form.logFileSizeLimit = enforceNumber(settings.logFileSizeLimit) || 10
|
||||
}
|
||||
}
|
||||
|
||||
initLogLevel (logLevel: string | string[]) {
|
||||
if (!Array.isArray(logLevel)) {
|
||||
if (logLevel && logLevel.length > 0) {
|
||||
logLevel = [logLevel]
|
||||
} else {
|
||||
logLevel = ['all']
|
||||
}
|
||||
}
|
||||
return logLevel
|
||||
}
|
||||
|
||||
getPicBeds (event: Event, picBeds: IPicBedType[]) {
|
||||
this.picBed = picBeds
|
||||
this.form.showPicBedList = this.picBed.map(item => {
|
||||
if (item.visible) {
|
||||
return item.name
|
||||
}
|
||||
return null
|
||||
}).filter(item => item) as string[]
|
||||
}
|
||||
|
||||
openFile (file: string) {
|
||||
ipcRenderer.send(PICGO_OPEN_FILE, file)
|
||||
}
|
||||
|
||||
openLogSetting () {
|
||||
this.logFileVisible = true
|
||||
}
|
||||
|
||||
keyDetect (type: string, event: KeyboardEvent) {
|
||||
this.shortKey[type] = keyDetect(event).join('+')
|
||||
}
|
||||
|
||||
async cancelCustomLink () {
|
||||
this.customLinkVisible = false
|
||||
this.customLink.value = await this.getConfig<string>('settings.customLink') || '$url'
|
||||
}
|
||||
|
||||
confirmCustomLink () {
|
||||
// @ts-ignore
|
||||
this.$refs.customLink.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
this.saveConfig('settings.customLink', this.customLink.value)
|
||||
this.customLinkVisible = false
|
||||
ipcRenderer.send('updateCustomLink')
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async cancelProxy () {
|
||||
this.proxyVisible = false
|
||||
this.proxy = await this.getConfig<string>('picBed.proxy') || ''
|
||||
}
|
||||
|
||||
confirmProxy () {
|
||||
this.proxyVisible = false
|
||||
this.saveConfig({
|
||||
'picBed.proxy': this.proxy,
|
||||
'settings.proxy': this.npmProxy,
|
||||
'settings.registry': this.npmRegistry
|
||||
})
|
||||
const successNotification = new Notification(this.$T('SETTINGS_SET_PROXY_AND_MIRROR'), {
|
||||
body: this.$T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
updateHelperChange (val: boolean) {
|
||||
this.saveConfig('settings.showUpdateTip', val)
|
||||
}
|
||||
|
||||
checkBetaUpdateChange (val: boolean) {
|
||||
this.saveConfig('settings.checkBetaUpdate', val)
|
||||
}
|
||||
|
||||
useBuiltinClipboardChange (val: boolean) {
|
||||
this.saveConfig('settings.useBuiltinClipboard', val)
|
||||
}
|
||||
|
||||
handleShowPicBedListChange (val: string[]) {
|
||||
const list = this.picBed.map(item => {
|
||||
if (!val.includes(item.name)) {
|
||||
item.visible = false
|
||||
} else {
|
||||
item.visible = true
|
||||
}
|
||||
return item
|
||||
})
|
||||
this.saveConfig({
|
||||
'picBed.list': list
|
||||
})
|
||||
ipcRenderer.send(GET_PICBEDS)
|
||||
}
|
||||
|
||||
handleAutoStartChange (val: boolean) {
|
||||
this.saveConfig('settings.autoStart', val)
|
||||
ipcRenderer.send('autoStart', val)
|
||||
}
|
||||
|
||||
handleRename (val: boolean) {
|
||||
this.saveConfig({
|
||||
'settings.rename': val
|
||||
})
|
||||
}
|
||||
|
||||
handleAutoRename (val: boolean) {
|
||||
this.saveConfig({
|
||||
'settings.autoRename': val
|
||||
})
|
||||
}
|
||||
|
||||
compareVersion2Update (current: string, latest: string): boolean {
|
||||
return compare(current, latest, '<')
|
||||
}
|
||||
|
||||
async checkUpdate () {
|
||||
this.checkUpdateVisible = true
|
||||
const version = await getLatestVersion(this.form.checkBetaUpdate)
|
||||
if (version) {
|
||||
this.latestVersion = version
|
||||
} else {
|
||||
this.latestVersion = this.$T('TIPS_NETWORK_ERROR')
|
||||
}
|
||||
}
|
||||
|
||||
confirmCheckVersion () {
|
||||
if (this.needUpdate) {
|
||||
ipcRenderer.send(OPEN_URL, this.form.checkBetaUpdate ? BETA_RELEASE_URL : STABLE_RELEASE_URL)
|
||||
}
|
||||
this.checkUpdateVisible = false
|
||||
}
|
||||
|
||||
cancelCheckVersion () {
|
||||
this.checkUpdateVisible = false
|
||||
}
|
||||
|
||||
handleUploadNotification (val: boolean) {
|
||||
this.saveConfig({
|
||||
'settings.uploadNotification': val
|
||||
})
|
||||
}
|
||||
|
||||
handleMiniWindowOntop (val: boolean) {
|
||||
this.saveConfig('settings.miniWindowOntop', val)
|
||||
this.$message.info(this.$T('TIPS_NEED_RELOAD'))
|
||||
}
|
||||
|
||||
handleAutoCopyUrl (val: boolean) {
|
||||
this.saveConfig('settings.autoCopy', val)
|
||||
const successNotification = new Notification(this.$T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD'), {
|
||||
body: this.$T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
confirmLogLevelSetting () {
|
||||
if (this.form.logLevel.length === 0) {
|
||||
return this.$message.error(this.$T('TIPS_PLEASE_CHOOSE_LOG_LEVEL'))
|
||||
}
|
||||
this.saveConfig({
|
||||
'settings.logLevel': this.form.logLevel,
|
||||
'settings.logFileSizeLimit': this.form.logFileSizeLimit
|
||||
})
|
||||
const successNotification = new Notification(this.$T('SETTINGS_SET_LOG_FILE'), {
|
||||
body: this.$T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.logFileVisible = false
|
||||
}
|
||||
|
||||
async cancelLogLevelSetting () {
|
||||
this.logFileVisible = false
|
||||
let logLevel = await this.getConfig<string | string[]>('settings.logLevel')
|
||||
const logFileSizeLimit = await this.getConfig<number>('settings.logFileSizeLimit') || 10
|
||||
if (!Array.isArray(logLevel)) {
|
||||
if (logLevel && logLevel.length > 0) {
|
||||
logLevel = [logLevel]
|
||||
} else {
|
||||
logLevel = ['all']
|
||||
}
|
||||
}
|
||||
this.form.logLevel = logLevel
|
||||
this.form.logFileSizeLimit = logFileSizeLimit
|
||||
}
|
||||
|
||||
confirmServerSetting () {
|
||||
// @ts-ignore
|
||||
this.server.port = parseInt(this.server.port, 10)
|
||||
this.saveConfig({
|
||||
'settings.server': this.server
|
||||
})
|
||||
const successNotification = new Notification(this.$T('SETTINGS_SET_PICGO_SERVER'), {
|
||||
body: this.$T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
}
|
||||
this.serverVisible = false
|
||||
ipcRenderer.send('updateServer')
|
||||
}
|
||||
|
||||
async cancelServerSetting () {
|
||||
this.serverVisible = false
|
||||
this.server = await this.getConfig('settings.server') || {
|
||||
port: 36677,
|
||||
host: '127.0.0.1',
|
||||
enable: true
|
||||
}
|
||||
}
|
||||
|
||||
handleLevelDisabled (val: string) {
|
||||
const currentLevel = val
|
||||
let flagLevel
|
||||
const result = this.form.logLevel.some(item => {
|
||||
if (item === 'all' || item === 'none') {
|
||||
flagLevel = item
|
||||
}
|
||||
return (item === 'all' || item === 'none')
|
||||
})
|
||||
if (result) {
|
||||
if (currentLevel !== flagLevel) {
|
||||
return true
|
||||
}
|
||||
} else if (this.form.logLevel.length > 0) {
|
||||
if (val === 'all' || val === 'none') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
handleLanguageChange (val: string) {
|
||||
this.$i18n.setCurrentLanguage(val)
|
||||
this.saveConfig({
|
||||
'settings.language': val
|
||||
})
|
||||
this.sendToMain(GET_PICBEDS)
|
||||
}
|
||||
|
||||
goConfigPage () {
|
||||
ipcRenderer.send(OPEN_URL, 'https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
|
||||
}
|
||||
|
||||
goShortCutPage () {
|
||||
this.$router.push('shortKey')
|
||||
}
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeListener(GET_PICBEDS, this.getPicBeds)
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SettingPage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
@@ -784,6 +145,7 @@ export default class extends Vue {
|
||||
left 60%
|
||||
.view-title
|
||||
.el-icon-document
|
||||
margin-left 8px
|
||||
cursor pointer
|
||||
transition color .2s ease-in-out
|
||||
&:hover
|
||||
@@ -796,8 +158,10 @@ export default class extends Vue {
|
||||
box-sizing border-box
|
||||
overflow-y auto
|
||||
overflow-x hidden
|
||||
width 100%
|
||||
.setting-list
|
||||
.el-form
|
||||
width: 100%
|
||||
&-item
|
||||
display: flex
|
||||
justify-content space-between
|
||||
@@ -811,7 +175,11 @@ export default class extends Vue {
|
||||
display none
|
||||
&::before
|
||||
display none
|
||||
label
|
||||
&__content
|
||||
display flex
|
||||
justify-content flex-end
|
||||
flex-basis: 50%
|
||||
.el-form-item__label
|
||||
line-height 32px
|
||||
padding-bottom 0
|
||||
color #eee
|
||||
@@ -824,8 +192,6 @@ export default class extends Vue {
|
||||
width 100%
|
||||
.el-button
|
||||
width 50%
|
||||
.el-input__inner
|
||||
border-radius 19px
|
||||
.el-radio-group
|
||||
margin-left 25px
|
||||
.el-switch__label
|
||||
|
||||
+441
-342
@@ -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="handleCancelConfig"
|
||||
>
|
||||
{{ $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,341 +206,346 @@ 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
|
||||
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="file://${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(GET_PICBEDS)
|
||||
}
|
||||
|
||||
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 = ''
|
||||
}
|
||||
function handleCancelConfig () {
|
||||
dialogVisible.value = false
|
||||
$configForm.value?.resetConfig()
|
||||
}
|
||||
|
||||
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'>
|
||||
@@ -461,6 +556,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;
|
||||
@@ -481,6 +577,7 @@ $darwinBg = #172426
|
||||
margin 10px auto
|
||||
position relative
|
||||
i.el-icon-goods
|
||||
margin-left 4px
|
||||
font-size 20px
|
||||
vertical-align middle
|
||||
cursor pointer
|
||||
@@ -510,8 +607,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,69 +1,112 @@
|
||||
<template>
|
||||
<div id="rename-page">
|
||||
<div class="p-[20px] flex flex-col justify-between">
|
||||
<el-form
|
||||
@submit.native.prevent
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('FILE_RENAME')"
|
||||
prop="fileName"
|
||||
:rules="[
|
||||
{ required: true, message: 'file name is required', trigger: 'blur' }
|
||||
]"
|
||||
>
|
||||
<el-input
|
||||
v-model="fileName"
|
||||
v-model="form.fileName"
|
||||
size="small"
|
||||
@keyup.enter.native="confirmName"
|
||||
></el-input>
|
||||
autofocus
|
||||
@keyup.enter="confirmName"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon
|
||||
class="el-input__icon"
|
||||
style="cursor: pointer;"
|
||||
@click="form.fileName = ''"
|
||||
>
|
||||
<close />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</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>
|
||||
<div class="w-full flex justify-end mt-[20px]">
|
||||
<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">
|
||||
import { RENAME_FILE_NAME } from '#/events/constants'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import mixin from '@/utils/mixin'
|
||||
<script lang="ts" setup>
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME } from '#/events/constants'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
} from 'electron'
|
||||
@Component({
|
||||
name: 'rename-page',
|
||||
mixins: [mixin]
|
||||
import { onBeforeUnmount, onBeforeMount, ref, reactive } from 'vue'
|
||||
import { useIPCOn } from '@/hooks/useIPC'
|
||||
import { FormInstance } from 'element-plus'
|
||||
const id = ref<string | null>(null)
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const form = reactive({
|
||||
fileName: '',
|
||||
originName: ''
|
||||
})
|
||||
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)
|
||||
}
|
||||
const handleFileName = (event: IpcRendererEvent, newName: string, _originName: string, _id: string) => {
|
||||
form.fileName = newName
|
||||
form.originName = _originName
|
||||
id.value = _id
|
||||
}
|
||||
|
||||
cancel () {
|
||||
// if cancel, use origin file name
|
||||
ipcRenderer.send(`${RENAME_FILE_NAME}${this.id}`, this.originName)
|
||||
}
|
||||
useIPCOn(RENAME_FILE_NAME, handleFileName)
|
||||
|
||||
beforeDestroy () {
|
||||
ipcRenderer.removeAllListeners('rename')
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
ipcRenderer.send(GET_RENAME_FILE_NAME)
|
||||
})
|
||||
|
||||
function confirmName () {
|
||||
formRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
sendToMain(`${RENAME_FILE_NAME}${id.value}`, form.fileName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function cancel () {
|
||||
// if cancel, use origin file name
|
||||
sendToMain(`${RENAME_FILE_NAME}${id.value}`, form.originName)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ipcRenderer.removeAllListeners(RENAME_FILE_NAME)
|
||||
})
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'RenamePage'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
#rename-page
|
||||
padding 0 20px
|
||||
.pull-right
|
||||
float right
|
||||
.el-form-item__label
|
||||
color #ddd
|
||||
.el-form-item__label
|
||||
color #ddd
|
||||
</style>
|
||||
|
||||
+118
-97
@@ -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,110 @@
|
||||
>
|
||||
<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'
|
||||
import { T as $T } from '@/i18n/index'
|
||||
|
||||
@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 +207,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 +228,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>
|
||||
+147
-95
@@ -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,101 +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
|
||||
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>
|
||||
|
||||
@@ -169,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
|
||||
@@ -180,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>
|
||||
|
||||
+216
-154
@@ -1,9 +1,18 @@
|
||||
<template>
|
||||
<div id="upload-view">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="20" :offset="2">
|
||||
<div class="view-title">
|
||||
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }} <i class="el-icon-caret-bottom" @click="handleChangePicBed"></i>
|
||||
<el-col
|
||||
:span="20"
|
||||
:offset="2"
|
||||
>
|
||||
<div class="view-title text-[22px]">
|
||||
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }} - {{ configName }}
|
||||
<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="openUploadWindow"
|
||||
>
|
||||
<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,215 +45,256 @@
|
||||
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 { T as $T } from '@/i18n'
|
||||
import $bus from '@/utils/bus'
|
||||
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { CaretBottom, UploadFilled } from '@element-plus/icons-vue'
|
||||
import {
|
||||
ipcRenderer,
|
||||
IpcRendererEvent
|
||||
IpcRendererEvent,
|
||||
ipcRenderer
|
||||
} from 'electron'
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import {
|
||||
GET_PICBEDS,
|
||||
SHOW_INPUT_BOX,
|
||||
SHOW_INPUT_BOX_RESPONSE,
|
||||
SHOW_UPLOAD_PAGE_MENU,
|
||||
GET_PICBEDS
|
||||
SHOW_UPLOAD_PAGE_MENU
|
||||
} 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(GET_PICBEDS)
|
||||
ipcRenderer.on(GET_PICBEDS, 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)
|
||||
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('')
|
||||
const configName = 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(GET_PICBEDS, 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 openUploadWindow () {
|
||||
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')
|
||||
const currentConfigName = await getConfig<string>(`picBed.${currentPicBed}._configName`) || 'Default'
|
||||
picBed.value.forEach(item => {
|
||||
if (item.type === currentPicBed) {
|
||||
picBedName.value = item.name
|
||||
}
|
||||
})
|
||||
configName.value = currentConfigName
|
||||
}
|
||||
|
||||
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
|
||||
@@ -275,6 +335,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,251 @@
|
||||
<template>
|
||||
<div
|
||||
id="config-list-view"
|
||||
class="h-[425px]"
|
||||
>
|
||||
<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 100px
|
||||
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 fixed
|
||||
bottom 20px
|
||||
width 100%
|
||||
.set-default-btn
|
||||
width 250px
|
||||
transform translateX(-50%)
|
||||
</style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<el-col :span="12">
|
||||
<div class="w-full flex justify-between gap-x-[6px]">
|
||||
<div
|
||||
class="item-base copy round gap-x-[10px]"
|
||||
:class="{ active: isMultiple(selectedList)}"
|
||||
@click="$emit('multiCopy')"
|
||||
>
|
||||
{{ $T('COPY') }}
|
||||
</div>
|
||||
<div
|
||||
class="item-base delete round"
|
||||
:class="{ active: isMultiple(selectedList)}"
|
||||
@click="$emit('multiRemove')"
|
||||
>
|
||||
{{ $T('DELETE') }}
|
||||
</div>
|
||||
<div
|
||||
class="item-base all-pick round"
|
||||
:class="{ active: filterList.length > 0}"
|
||||
@click="$emit('toggleSelectAll')"
|
||||
>
|
||||
{{ isAllSelected ? $T('CANCEL') : $T('SELECT_ALL') }}
|
||||
</div>
|
||||
<div
|
||||
class="item-base select-more round !w-[18%]"
|
||||
:class="{ active: filterList.length > 0}"
|
||||
@click="$emit('selectMore')"
|
||||
>
|
||||
<el-icon name="el-icon-arrow-left">
|
||||
<MoreFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<ConfigFormDialog />
|
||||
</el-col>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { MoreFilled } from '@element-plus/icons-vue'
|
||||
import ConfigFormDialog from '@/components/dialog/ConfigFormDialog.vue'
|
||||
|
||||
interface IProps {
|
||||
selectedList: IObjT<boolean>
|
||||
filterList: IGalleryItem[]
|
||||
isAllSelected: boolean
|
||||
}
|
||||
defineProps<IProps>()
|
||||
|
||||
defineEmits(['multiCopy', 'multiRemove', 'toggleSelectAll', 'selectMore'])
|
||||
|
||||
function isMultiple (obj: IObj) {
|
||||
return Object.values(obj).some(item => item)
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'GalleryToolbar'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
.item-base
|
||||
background #2E2E2E
|
||||
text-align center
|
||||
width: 22%
|
||||
flex-grow: 1
|
||||
cursor pointer
|
||||
font-size 12px
|
||||
transition all .2s ease-in-out
|
||||
height: 24px
|
||||
line-height: 28px
|
||||
box-sizing: border-box
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
&.copy
|
||||
cursor not-allowed
|
||||
background #49B1F5
|
||||
&.active
|
||||
cursor pointer
|
||||
background #1B9EF3
|
||||
color #fff
|
||||
&.delete
|
||||
cursor not-allowed
|
||||
background #F47466
|
||||
&.active
|
||||
cursor pointer
|
||||
background #F15140
|
||||
color #fff
|
||||
&.all-pick
|
||||
cursor not-allowed
|
||||
background #69C282
|
||||
&.active
|
||||
cursor pointer
|
||||
background #44B363
|
||||
color #fff
|
||||
&.select-more
|
||||
cursor pointer
|
||||
background #858585
|
||||
color #fff
|
||||
</style>
|
||||
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_OPEN_CONFIG_FILE')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_OPEN')"
|
||||
@click="openFile('data.json')"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_SET_LOG_FILE')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="logFileDialogVisible = true"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_SET_SHORTCUT')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="goShortCutPage"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="customLinkDialogVisible = true"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="proxySettingDialogVisible = true"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_SET_SERVER')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_SET')"
|
||||
@click="serverSettingDialogVisible = true"
|
||||
/>
|
||||
<ButtonFormItem
|
||||
:label="$T('SETTINGS_CHECK_UPDATE')"
|
||||
:button-label="$T('SETTINGS_CLICK_TO_CHECK')"
|
||||
@click="checkUpdateDialogVisible = true"
|
||||
/>
|
||||
<LogSettingDialog
|
||||
v-model="logFileDialogVisible"
|
||||
v-model:log-level="form.logLevel"
|
||||
v-model:log-file-size-limit="form.logFileSizeLimit"
|
||||
/>
|
||||
<CustomLinkDialog
|
||||
v-model="customLinkDialogVisible"
|
||||
v-model:custom-link="form.customLink"
|
||||
/>
|
||||
<ProxySettingDialog
|
||||
v-model="proxySettingDialogVisible"
|
||||
v-model:proxy="proxyString"
|
||||
v-model:npm-proxy="form.npmProxy"
|
||||
v-model:npm-registry="form.npmRegistry"
|
||||
/>
|
||||
<ServerSettingsDialog
|
||||
v-model="serverSettingDialogVisible"
|
||||
v-model:host="form.server.host"
|
||||
v-model:port="form.server.port"
|
||||
v-model:enable="form.server.enable"
|
||||
/>
|
||||
<CheckUpdateDialog
|
||||
v-model="checkUpdateDialogVisible"
|
||||
v-model:checkBetaUpdate="form.checkBetaUpdate"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ButtonFormItem from '@/components/settings/ButtonFormItem.vue'
|
||||
import CustomLinkDialog from './CustomLinkDialog.vue'
|
||||
import ProxySettingDialog from './ProxySettingDialog.vue'
|
||||
import ServerSettingsDialog from './ServerSettingsDialog.vue'
|
||||
import CheckUpdateDialog from './CheckUpdateDialog.vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import { sendToMain } from '@/utils/dataSender'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { PICGO_OPEN_FILE } from '~/universal/events/constants'
|
||||
import LogSettingDialog from './LogSettingDialog.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { SHORTKEY_PAGE } from '@/router/config'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
const $router = useRouter()
|
||||
|
||||
interface IProps {
|
||||
settings: ISettingForm
|
||||
proxy: string
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const form = reactive(props.settings)
|
||||
const proxyString = useVModel(props, 'proxy')
|
||||
|
||||
const logFileDialogVisible = ref(false)
|
||||
const customLinkDialogVisible = ref(false)
|
||||
const proxySettingDialogVisible = ref(false)
|
||||
const serverSettingDialogVisible = ref(false)
|
||||
const checkUpdateDialogVisible = ref(false)
|
||||
|
||||
function openFile (file: string) {
|
||||
sendToMain(PICGO_OPEN_FILE, file)
|
||||
}
|
||||
|
||||
function goShortCutPage () {
|
||||
$router.push({
|
||||
name: SHORTKEY_PAGE
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ButtonAreaSettings'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$T('SETTINGS_CHECK_UPDATE')"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<div>
|
||||
{{ $T('SETTINGS_CURRENT_VERSION') }}: {{ version }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $T('SETTINGS_NEWEST_VERSION') }}: {{ latestVersion ? latestVersion : `${$T('SETTINGS_GETING')}...` }}
|
||||
</div>
|
||||
<div v-if="needUpdate">
|
||||
{{ $T('SETTINGS_TIPS_HAS_NEW_VERSION') }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelCheckVersion"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmCheckVersion"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import { STABLE_RELEASE_URL, BETA_RELEASE_URL } from '#/utils/static'
|
||||
import pkg from 'root/package.json'
|
||||
import { compare } from 'compare-versions'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { triggerRPC } from '@/utils/dataSender'
|
||||
import { openURL } from '@/utils/common'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
checkBetaUpdate: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
const checkBetaUpdate = useVModel(props, 'checkBetaUpdate')
|
||||
|
||||
const version = pkg.version
|
||||
const latestVersion = ref('')
|
||||
|
||||
function compareVersion2Update (current: string, latest: string): boolean {
|
||||
return compare(current, latest, '<')
|
||||
}
|
||||
|
||||
const needUpdate = computed(() => {
|
||||
if (latestVersion.value) {
|
||||
return compareVersion2Update(version, latestVersion.value)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => dialogVisible.value, (value) => {
|
||||
if (value) {
|
||||
checkUpdate()
|
||||
}
|
||||
})
|
||||
|
||||
async function checkUpdate () {
|
||||
const version = await triggerRPC<string>(IRPCActionType.GET_LATEST_VERSION, checkBetaUpdate.value)
|
||||
if (version) {
|
||||
latestVersion.value = version
|
||||
} else {
|
||||
latestVersion.value = $T('TIPS_NETWORK_ERROR')
|
||||
}
|
||||
}
|
||||
|
||||
function confirmCheckVersion () {
|
||||
if (needUpdate.value) {
|
||||
openURL(checkBetaUpdate.value ? BETA_RELEASE_URL : STABLE_RELEASE_URL)
|
||||
}
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
function cancelCheckVersion () {
|
||||
latestVersion.value = ''
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CheckUpdateDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<el-form
|
||||
ref="$customLink"
|
||||
label-position="top"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item
|
||||
prop="customLink"
|
||||
>
|
||||
<div class="flex flex-col mb-[8px]">
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_URL') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_FILENAME') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_EXTNAME') }}
|
||||
</div>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="form.customLink"
|
||||
class="align-center"
|
||||
:autofocus="true"
|
||||
size="default"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div>
|
||||
{{ $T('SETTINGS_TIPS_SUCH_AS') }}[$fileName]($url)
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelCustomLink"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmCustomLink"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { ElForm, FormRules } from 'element-plus'
|
||||
import { useVModelValues } from '@/hooks/useVModelValues'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
customLink: string
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const [form, updateProps] = useVModelValues<IProps>(props, ['customLink'])
|
||||
|
||||
const $customLink = ref<InstanceType<typeof ElForm> | null>(null)
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
|
||||
const customLinkRule = (rule: any, value: string, callback: (arg0?: Error) => void) => {
|
||||
if (!/\$url/.test(value) && !/\$fileName/.test(value) && !/\$extName/.test(value)) {
|
||||
return callback(new Error($T('TIPS_MUST_CONTAINS_URL')))
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
value: [
|
||||
{ validator: customLinkRule, trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
function confirmCustomLink () {
|
||||
$customLink.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
saveConfig('settings.customLink', form.customLink)
|
||||
dialogVisible.value = false
|
||||
sendToMain('updateCustomLink')
|
||||
updateProps()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function cancelCustomLink () {
|
||||
dialogVisible.value = false
|
||||
form.customLink = props.customLink
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CustomLinkDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$T('SETTINGS_SET_LOG_FILE')"
|
||||
:append-to-body="true"
|
||||
width="500px"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="150px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LOG_FILE')"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
round
|
||||
size="small"
|
||||
@click="openFile('picgo.log')"
|
||||
>
|
||||
{{ $T('SETTINGS_CLICK_TO_OPEN') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_LOG_LEVEL')"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.logLevel"
|
||||
multiple
|
||||
collapse-tags
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-option
|
||||
v-for="(value, key) of logLevelMap"
|
||||
:key="key"
|
||||
:label="value"
|
||||
:value="key"
|
||||
:disabled="handleLevelDisabled(key)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="`${$T('SETTINGS_LOG_FILE_SIZE')} (MB)`"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.logFileSizeLimit"
|
||||
style="width: 100%;"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:10`"
|
||||
:controls="false"
|
||||
:min="1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelLogLevelSetting"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmLogLevelSetting"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { saveConfig } from '@/utils/dataSender'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { openFile, showNotification } from '@/utils/common'
|
||||
import { ElMessage as $message } from 'element-plus'
|
||||
import { useVModelValues } from '@/hooks/useVModelValues'
|
||||
|
||||
const logLevelMap = {
|
||||
all: $T('SETTINGS_LOG_LEVEL_ALL'),
|
||||
success: $T('SETTINGS_LOG_LEVEL_SUCCESS'),
|
||||
error: $T('SETTINGS_LOG_LEVEL_ERROR'),
|
||||
info: $T('SETTINGS_LOG_LEVEL_INFO'),
|
||||
warn: $T('SETTINGS_LOG_LEVEL_WARN'),
|
||||
none: $T('SETTINGS_LOG_LEVEL_NONE')
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
logLevel: string[]
|
||||
logFileSizeLimit: number
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
|
||||
const [form, updateProps] = useVModelValues<IProps>(props, ['logFileSizeLimit', 'logLevel'])
|
||||
|
||||
async function cancelLogLevelSetting () {
|
||||
dialogVisible.value = false
|
||||
let logLevel = props.logLevel as string | string[]
|
||||
if (!Array.isArray(logLevel)) {
|
||||
if (logLevel && logLevel.length > 0) {
|
||||
logLevel = [logLevel]
|
||||
} else {
|
||||
logLevel = ['all']
|
||||
}
|
||||
}
|
||||
form.logLevel = logLevel
|
||||
form.logFileSizeLimit = props.logFileSizeLimit
|
||||
}
|
||||
|
||||
function confirmLogLevelSetting () {
|
||||
if (form.logLevel.length === 0) {
|
||||
return $message.error($T('TIPS_PLEASE_CHOOSE_LOG_LEVEL'))
|
||||
}
|
||||
saveConfig({
|
||||
'settings.logLevel': form.logLevel,
|
||||
'settings.logFileSizeLimit': form.logFileSizeLimit
|
||||
})
|
||||
showNotification($T('SETTINGS_SET_LOG_FILE'), $T('TIPS_SET_SUCCEED'))
|
||||
updateProps()
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
function handleLevelDisabled (val: string) {
|
||||
const currentLevel = val
|
||||
let flagLevel
|
||||
const result = form.logLevel.some((item: any) => {
|
||||
if (item === 'all' || item === 'none') {
|
||||
flagLevel = item
|
||||
}
|
||||
return (item === 'all' || item === 'none')
|
||||
})
|
||||
if (result) {
|
||||
if (currentLevel !== flagLevel) {
|
||||
return true
|
||||
}
|
||||
} else if (form.logLevel.length > 0) {
|
||||
if (val === 'all' || val === 'none') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'LogSettingDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
|
||||
:append-to-body="true"
|
||||
width="70%"
|
||||
>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_UPLOAD_PROXY')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.proxy"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:http://127.0.0.1:1080`"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_PLUGIN_INSTALL_PROXY')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.npmProxy"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:http://127.0.0.1:1080`"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_PLUGIN_INSTALL_MIRROR')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.npmRegistry"
|
||||
:autofocus="true"
|
||||
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}:https://registry.npmmirror.com`"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelProxy"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmProxy"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { saveConfig } from '@/utils/dataSender'
|
||||
import { useVModelValues } from '@/hooks/useVModelValues'
|
||||
import { showNotification } from '@/utils/common'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
proxy: string
|
||||
npmProxy: string
|
||||
npmRegistry: string
|
||||
}
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const [form, updateProps] = useVModelValues<IProps>(props, ['proxy', 'npmProxy', 'npmRegistry'])
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
|
||||
function confirmProxy () {
|
||||
dialogVisible.value = false
|
||||
saveConfig({
|
||||
'picBed.proxy': form.proxy,
|
||||
'settings.npmProxy': form.npmProxy,
|
||||
'settings.npmRegistry': form.npmRegistry
|
||||
})
|
||||
showNotification($T('SETTINGS_SET_PROXY_AND_MIRROR'), $T('TIPS_SET_SUCCEED'))
|
||||
updateProps()
|
||||
}
|
||||
|
||||
async function cancelProxy () {
|
||||
dialogVisible.value = false
|
||||
form.proxy = props.proxy || ''
|
||||
form.npmProxy = props.npmProxy || ''
|
||||
form.npmRegistry = props.npmRegistry || ''
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ProxySettingDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
class="server-dialog"
|
||||
width="60%"
|
||||
:title="$T('SETTINGS_SET_PICGO_SERVER')"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<div class="notice-text">
|
||||
{{ $T('SETTINGS_TIPS_SERVER_NOTICE') }}
|
||||
</div>
|
||||
<el-form
|
||||
label-position="right"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_ENABLE_SERVER')"
|
||||
>
|
||||
<el-switch
|
||||
v-model="form.enable"
|
||||
:active-text="$T('SETTINGS_OPEN')"
|
||||
:inactive-text="$T('SETTINGS_CLOSE')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<template v-if="form.enable">
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER_HOST')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.host"
|
||||
type="input"
|
||||
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_HOST')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$T('SETTINGS_SET_SERVER_PORT')"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.port"
|
||||
type="number"
|
||||
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_PORT')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button
|
||||
size="default"
|
||||
round
|
||||
@click="cancelServerSetting"
|
||||
>
|
||||
{{ $T('CANCEL') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
@click="confirmServerSetting"
|
||||
>
|
||||
{{ $T('CONFIRM') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { T as $T } from '@/i18n'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
import { useVModelValues } from '@/hooks/useVModelValues'
|
||||
import { saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { showNotification } from '@/utils/common'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
port: number
|
||||
host: string
|
||||
enable: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const dialogVisible = useVModel(props, 'modelValue')
|
||||
const [form, updateProps] = useVModelValues<IProps>(props, ['port', 'host', 'enable'])
|
||||
|
||||
function confirmServerSetting () {
|
||||
form.port = parseInt(form.port as unknown as string, 10)
|
||||
saveConfig({
|
||||
'settings.server': form
|
||||
})
|
||||
showNotification($T('SETTINGS_SET_PICGO_SERVER'), $T('TIPS_SET_SUCCEED'))
|
||||
dialogVisible.value = false
|
||||
sendToMain('updateServer')
|
||||
updateProps()
|
||||
}
|
||||
|
||||
async function cancelServerSetting () {
|
||||
dialogVisible.value = false
|
||||
form.port = props.port
|
||||
form.host = props.host
|
||||
form.enable = props.enable
|
||||
}
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ServerSettingsDialog'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<el-form-item
|
||||
:style="{ marginRight: '-64px' }"
|
||||
:label="$T('CHOOSE_SHOWED_PICBED')"
|
||||
>
|
||||
<el-checkbox-group
|
||||
v-model="showPicBedList"
|
||||
@change="handleShowPicBedListChange"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="item in picBed"
|
||||
:key="item.name"
|
||||
:label="item.name"
|
||||
/>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
import { T as $T } from '@/i18n'
|
||||
import { GET_PICBEDS } from '#/events/constants'
|
||||
import { saveConfig, sendToMain } from '@/utils/dataSender'
|
||||
import { useIPCOn } from '@/hooks/useIPC'
|
||||
import { useVModel } from '@/hooks/useVModel'
|
||||
|
||||
interface IProps {
|
||||
showPicBedList: string[]
|
||||
}
|
||||
const props = defineProps<IProps>()
|
||||
|
||||
const picBed = ref<IPicBedType[]>([])
|
||||
const showPicBedList = useVModel(props, 'showPicBedList')
|
||||
|
||||
function handleShowPicBedListChange (val: ISwitchValueType[]) {
|
||||
const list = picBed.value.map(item => {
|
||||
if (!val.includes(item.name)) {
|
||||
item.visible = false
|
||||
} else {
|
||||
item.visible = true
|
||||
}
|
||||
return item
|
||||
})
|
||||
saveConfig({
|
||||
'picBed.list': list
|
||||
})
|
||||
sendToMain(GET_PICBEDS)
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
sendToMain(GET_PICBEDS)
|
||||
useIPCOn(GET_PICBEDS, getPicBeds)
|
||||
})
|
||||
|
||||
function getPicBeds (event: Event, picBeds: IPicBedType[]) {
|
||||
picBed.value = picBeds
|
||||
showPicBedList.value = picBed.value.map(item => {
|
||||
if (item.visible) {
|
||||
return item.name
|
||||
}
|
||||
return null
|
||||
}).filter(item => item) as string[]
|
||||
}
|
||||
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ChoosePicBed'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<ChoosePicBed
|
||||
v-model:showPicBedList="form.showPicBedList"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue'
|
||||
import ChoosePicBed from './ChoosePicBed.vue'
|
||||
|
||||
interface IProps {
|
||||
settings: ISettingForm
|
||||
}
|
||||
const props = defineProps<IProps>()
|
||||
const form = reactive(props.settings)
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CustomAreaSettings'
|
||||
}
|
||||
</script>
|
||||
<style lang='stylus'>
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user