mirror of
https://github.com/Molunerfinn/PicGo.git
synced 2026-09-20 11:17:32 +00:00
Compare commits
60
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cccd2954db | ||
|
|
cfb6146de5 | ||
|
|
45b3227456 | ||
|
|
2450a524ff | ||
|
|
4a29bf2b50 | ||
|
|
24e4a829d8 | ||
|
|
e0d45fa7a2 | ||
|
|
de441a892e | ||
|
|
f1817eb1e1 | ||
|
|
2bd5d0653b | ||
|
|
9841418779 | ||
|
|
7bc70628f9 | ||
|
|
a928b4c8ab | ||
|
|
7df37a2526 | ||
|
|
dacb926f17 | ||
|
|
1ea074e75f | ||
|
|
04140def7c | ||
|
|
c9fe4023f0 | ||
|
|
c8ba547edb | ||
|
|
316928edc3 | ||
|
|
ff35335126 | ||
|
|
ff7336b99e | ||
|
|
917ec73027 | ||
|
|
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 |
+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'
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
custom: ["https://paypal.me/Molunerfinn"]
|
||||
github: ["Molunerfinn"]
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-11]
|
||||
os: [ubuntu-latest, macos-14]
|
||||
|
||||
# create steps
|
||||
steps:
|
||||
@@ -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 }}
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-11]
|
||||
os: [ubuntu-latest, macos-14]
|
||||
|
||||
# create steps
|
||||
steps:
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
18
|
||||
Vendored
+8
-2
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"eslint.enable": true,
|
||||
"eslint.alwaysShowStatus": true,
|
||||
"eslint.format.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
@@ -20,6 +21,11 @@
|
||||
"stylusSupremacy.sortProperties": "grouped",
|
||||
"stylusSupremacy.quoteChar": "\"",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"prettier.enable": false,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
PicGo is an Electron + Vue 3 desktop client. Source lives in `src/`: `src/main` for main-process and IPC logic, `src/renderer` for Vue views, and `src/universal` for shared helpers (`types/`, `events/constants.ts`). `background.ts` wires Electron Builder. Static assets and locale YAML files stay in `public/` (add languages under `public/i18n/`), while `docs/` hosts user-facing guides. Automation scripts live in `scripts/`, and legacy tests sit under `test/unit` (Karma) and `test/e2e` (Spectron).
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `yarn install` — install dependencies; `npm install` is unsupported because native modules are patched for Yarn.
|
||||
- `yarn dev` / `yarn electron:serve` — start the hot-reload dev environment for both processes.
|
||||
- `yarn build` / `yarn electron:build` — package the app into `dist_electron/`; set `ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/` when downloads are slow.
|
||||
- `yarn lint` / `yarn lint:fix` — run or auto-fix ESLint (Standard, TypeScript, Vue rules).
|
||||
- `yarn lint:dpdm` — fail fast on circular dependencies in `src/`.
|
||||
- `yarn gen-i18n` — regenerate typed locales after touching `public/i18n/*.yml`.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
Follow ESLint Standard defaults: two-space indentation, single quotes, trailing commas where allowed, and no stray semicolons. Author new modules in TypeScript. Keep renderer files browser-safe; route Node APIs through IPC helpers such as `src/main/events/picgoCoreIPC.ts`. Name Vue components in PascalCase (`UploadPanel.vue`) and use camelCase for utilities. Centralize IPC event names inside `src/universal/events/constants.ts`, and store enums/types under `src/universal/types/` so they stay reusable.
|
||||
|
||||
## Testing Guidelines
|
||||
Place renderer unit specs in `test/unit/specs` with the `.spec.js` suffix; Karma picks them up via `require.context`. Run them with `npx karma start test/unit/karma.conf.js --single-run` and ensure new renderer folders are covered. Spectron e2e cases live in `test/e2e/specs`; build first (`yarn build`), then run `npx mocha test/e2e/index.js` so Spectron can launch `dist/electron/main.js`. Document any test data, IPC stubs, or fixtures you add to keep suites reproducible.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
Commits follow the PicGo conventional preset enforced by Husky (`yarn lint:dpdm` + Commitlint). Stage your changes and run `yarn cz` to craft messages that pass CI. Pull requests should explain the change, link related issues, and attach UI screenshots or recordings. Note how you validated the work (dev server, build, Karma, Spectron) and call out migration or configuration steps reviewers must perform.
|
||||
|
||||
## Internationalization Tips
|
||||
Add locales by creating `public/i18n/<locale>.yml`, exposing its `LANG_DISPLAY_LABEL`, and registering it in `src/universal/i18n/index.ts`. Finish with `yarn gen-i18n` so the generated typings stay in sync.
|
||||
+144
-3
@@ -1,3 +1,147 @@
|
||||
# :tada: 2.4.0-beta.10 (2025-06-08)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* finish form uploader for picgo server ([9841418](https://github.com/Molunerfinn/PicGo/commit/9841418))
|
||||
* **server:** add support for form upload in PicGo Server ([#1327](https://github.com/Molunerfinn/PicGo/issues/1327)) ([a928b4c](https://github.com/Molunerfinn/PicGo/commit/a928b4c)), closes [#428](https://github.com/Molunerfinn/PicGo/issues/428)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* auto-copy url can't be turned off ([#1300](https://github.com/Molunerfinn/PicGo/issues/1300)) ([dacb926](https://github.com/Molunerfinn/PicGo/commit/dacb926))
|
||||
* encoded url filename unreadable ([2bd5d06](https://github.com/Molunerfinn/PicGo/commit/2bd5d06))
|
||||
* unable to use clip in wayland ([#1301](https://github.com/Molunerfinn/PicGo/issues/1301)) ([7df37a2](https://github.com/Molunerfinn/PicGo/commit/7df37a2))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.9 (2024-12-02)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* clipboard filename missing second ([c9fe402](https://github.com/Molunerfinn/PicGo/commit/c9fe402)), closes [#1293](https://github.com/Molunerfinn/PicGo/issues/1293)
|
||||
* copy text bug ([c8ba547](https://github.com/Molunerfinn/PicGo/commit/c8ba547)), closes [#1210](https://github.com/Molunerfinn/PicGo/issues/1210) [#1280](https://github.com/Molunerfinn/PicGo/issues/1280)
|
||||
* plugin list search bug ([04140de](https://github.com/Molunerfinn/PicGo/commit/04140de)), closes [#1297](https://github.com/Molunerfinn/PicGo/issues/1297)
|
||||
|
||||
|
||||
### :package: Chore
|
||||
|
||||
* update ci macos version ([316928e](https://github.com/Molunerfinn/PicGo/commit/316928e))
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.8 (2024-07-16)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* tencent cos url encode bug ([ff7336b](https://github.com/Molunerfinn/PicGo/commit/ff7336b)), closes [#1265](https://github.com/Molunerfinn/PicGo/issues/1265)
|
||||
|
||||
|
||||
|
||||
# :tada: 2.4.0-beta.7 (2024-04-22)
|
||||
|
||||
|
||||
### :sparkles: Features
|
||||
|
||||
* add startup mode ([aaec99f](https://github.com/Molunerfinn/PicGo/commit/aaec99f)), closes [#915](https://github.com/Molunerfinn/PicGo/issues/915)
|
||||
|
||||
|
||||
### :bug: Bug Fixes
|
||||
|
||||
* config page scroll bug ([8e91582](https://github.com/Molunerfinn/PicGo/commit/8e91582)), closes [#1237](https://github.com/Molunerfinn/PicGo/issues/1237)
|
||||
* tray menu open bug ([50e0a64](https://github.com/Molunerfinn/PicGo/commit/50e0a64)), closes [#1217](https://github.com/Molunerfinn/PicGo/issues/1217)
|
||||
|
||||
|
||||
|
||||
# :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)
|
||||
|
||||
|
||||
@@ -574,6 +718,3 @@
|
||||
### :package: Chore
|
||||
|
||||
* add picgo bump-version ([37f1d34](https://github.com/Molunerfinn/PicGo/commit/37f1d34))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -121,6 +121,12 @@ options:
|
||||
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 的版本发现打开后没反应,请重启电脑即可。
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
<div align="center" markdown="1">
|
||||
<sup>Special thanks to:</sup>
|
||||
<br>
|
||||
<a href="https://go.warp.dev/picgo">
|
||||
<img alt="Warp sponsorship" width="400" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/refs/heads/main/Github/Sponsor/Warp-Github-LG-03.png">
|
||||
</a>
|
||||
|
||||
### [Warp, the intelligent terminal for developers](https://go.warp.dev/picgo)
|
||||
[Available for MacOS, Linux, & Windows](https://go.warp.dev/picgo)<br>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/Molunerfinn/test/master/picgo/New%20LOGO-150.png" alt="">
|
||||
<h1>PicGo</h1>
|
||||
@@ -54,15 +68,14 @@ 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 的贡献 |
|
||||
| [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 的贡献 |
|
||||
| 下载源 | 地址/安装方式 | 平台 | 备注 |
|
||||
| --------------------------------------------- | ----------------------------------------------------------- | ---------- | ----------------------------------------------------------------- |
|
||||
| GitHub Release | https://github.com/Molunerfinn/PicGo/releases | All | 国内下载速度可能会慢 |
|
||||
| [山东大学镜像站](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 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 的贡献 |
|
||||
|
||||
## 应用截图
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# PicGo 2.4.0 Changelog
|
||||
|
||||
## Features
|
||||
- 新增 相册页新增文件名展示,参考 #1050
|
||||
- 新增 相册中和顶部栏窗口中无法展示的图片或者 url 将会展示默认图片,参考#1050
|
||||

|
||||
- 新增 macOS 顶部栏窗口新增文件名展示,参考 #1054
|
||||
- 新增 同种类型图床支持多份配置,上传时可以指定某一份配置进行上传。感谢 @STDSuperman ,参考 #1016
|
||||

|
||||
- 新增 选择图床的菜单可以支持选择该图床类型某一项具体配置
|
||||

|
||||
- 新增 显示 dock 栏图标选项,参考 #1045
|
||||
<img width="787" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/bb9492f1-6522-45ce-ae5d-c614901d8b06" />
|
||||
- 新增 PicGo 修复工具箱,可以自行排查一些问题
|
||||
<img width="324" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/98cf6e64-7313-4ebe-83d0-aa86e4514109" />
|
||||
- 新增 对输出的 URL 进行 encode 转义的选项,参考 #731
|
||||
<img width="775" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/6e0c13dd-3404-4b07-9c99-412321835b27" />
|
||||
- 新增 支持拖拽任意格式文件上传,参考 #1052
|
||||
- 新增 腾讯云 COS 支持 `Endpoint` 配置 和 `极智压缩` 配置。 感谢 @palmcivet @yc910920 的贡献!
|
||||

|
||||
- 新增 `tips` 配置渲染的支持,可以动态渲染 Uploader config 的 tips。支持 markdown 格式。参考 [tcyun uploader config](https://github.com/PicGo/PicGo-Core/blob/dev/src/plugins/uploader/tcyun.ts#L280)
|
||||

|
||||
- 新增 上传界面展示当前图床使用的配置名
|
||||

|
||||
- 新增 相册页工具栏,目前内置 `批量修改图片 URL HOST 的功能` 。参考 #875
|
||||
注意:需要先选中指定的图片,然后会根据已选中的图片进行修改,你可以通过图床筛选功能只筛选出需要修改的图片。
|
||||
例如,你有一批图片都是 `https://www.a.com/...` 打头的 URL,你想把 `www.a.com` 批量修改成 `www.b.com` ,就可以用这个功能
|
||||

|
||||
- 新增 `启动模式`,可以设置启动的时候是否要打开窗口。全平台支持 `静默启动`(默认值) & `打开主窗口`,Windows 和 Linux 额外支持 `打开 Mini 窗口`。参考 #915
|
||||
<img width="577" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/7e63bb5c-44b0-480e-824f-3c2edbed4fbb" />
|
||||
- 新增 PicGo Server 支持表单形式上传图片。参考 #428 ,感谢 @happy-game
|
||||
表单字段为 files。参考截图:
|
||||
<img width="951" alt="image" src="https://github.com/user-attachments/assets/14244f1d-60f5-487f-bde6-6d0e009645fb" />
|
||||
|
||||
## Bug Fixes
|
||||
- 修复 windows 右键菜单配置生成脚本,参考 #1019
|
||||
- 修复 自定义链接 URL encode 问题,参考 #1112
|
||||
- 修复 日志写入可能存在死循环问题,参考 #1101
|
||||
- 修复 拖拽文件到顶部栏图标报错问题,参考 #1107
|
||||
- 修复 文件名 encode 问题。参考 #1121
|
||||
- 修复 GitHub 重名文件上传不再报错。
|
||||
- 修复 beta.2 版本部分样式问题
|
||||
- 修复 重命名窗口某些情况下显示文件名过慢的问题,参考 #1130
|
||||
- 修复 打开配置文件打开的是日志文件的 bug,参考 #1163
|
||||
- 修复 插件配置弹窗打开后无法正确读取和保存配置的问题。
|
||||
- 修复 无法新增图床配置的问题(新增变成了编辑)。参考 #1198,#1196
|
||||
- 修复 macOS 右键菜单消失问题。感谢 @muwoo 。参考 #1179
|
||||
- 修复 macOS 顶部栏窗口右键之后一闪而过的问题。 感谢 @QThans。 参考 #1217
|
||||
- 修复 图床配置页面配置项过多时无法滚动到底部的问题。参考 #1237
|
||||
- 修复 腾讯云 COS URL encode 的问题。参考 #1265
|
||||
- 修复 插件列表无法搜索的问题。 参考 #1297
|
||||
- 修复 剪贴板文件名丢失 `秒` 的问题。 参考 #1293
|
||||
- 修复 macOS 顶部栏点击图片复制链接失效的问题。 参考 #1280 , #1210
|
||||
- 修复 自动复制 URL 这个开关无法被关闭的问题。参考 #1294 ,感谢 @happy-game
|
||||
- 修复 wayland 里无法使用剪贴板图片的问题。参考 #1261 ,感谢 @happy-game
|
||||
- 修复 直接通过 URL 上传图片的时候,带有汉字的 URL 上传后,文件名被 encode 的问题。 参考 #1339
|
||||
|
||||
## Other
|
||||
- 由于 PicGo 存储的 COS 空间被恶意刷大量流量导致欠费,暂时停止 COS 渠道的 PicGo 分发。
|
||||
- 更新至 Vue3
|
||||
- 重构部分代码
|
||||
- 重构部分代码
|
||||
- 补充更详细的快捷键相关日志,参考 #1031
|
||||
- 已知问题:部分按钮样式有点问题,下个版本会修复
|
||||
- 更新 README中的 scoop 下载安装的部分,感谢 @wuhang2003
|
||||
|
||||
----------
|
||||
|
||||
# PicGo 2.4.0 Changelog
|
||||
|
||||
## Features
|
||||
- Add filename display in the gallery (#1050).
|
||||
- Add default placeholders when images/URLs cannot be shown in the gallery or tray window (#1050).
|
||||

|
||||
- Add filename display in the macOS tray window (#1054).
|
||||
- Allow multiple configs per uploader type and choose one for upload (thanks @STDSuperman, #1016).
|
||||

|
||||
- Allow the uploader selection menu to pick a specific config entry for that uploader type.
|
||||

|
||||
- Add Dock icon visibility option (#1045).
|
||||
<img width="787" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/bb9492f1-6522-45ce-ae5d-c614901d8b06" />
|
||||
- Add PicGo Repair Toolbox for self-diagnosis.
|
||||
<img width="324" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/98cf6e64-7313-4ebe-83d0-aa86e4514109" />
|
||||
- Add option to encode/escape output URLs (#731).
|
||||
<img width="775" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/6e0c13dd-3404-4b07-9c99-412321835b27" />
|
||||
- Support drag-and-drop uploads for arbitrary file formats (#1052).
|
||||
- Tencent COS: add Endpoint and Intelligent Compression settings (thanks @palmcivet @yc910920).
|
||||

|
||||
- Add Markdown-rendered tips for uploader config (see tcyun uploader config).
|
||||

|
||||
- Show the active uploader config name in the upload screen.
|
||||

|
||||
- Gallery toolbar: bulk replace image URL host (#875).
|
||||
Note: select images first; you can filter by uploader; e.g., replace `https://www.a.com/...` with `https://www.b.com/...`.
|
||||

|
||||
- Add “Startup Mode”: Silent (default) / Main Window; Windows/Linux also support Mini Window (#915).
|
||||
<img width="577" alt="image" src="https://github.com/Molunerfinn/PicGo/assets/12621342/7e63bb5c-44b0-480e-824f-3c2edbed4fbb" />
|
||||
- PicGo Server: support multipart form uploads (field `files`, #428, thanks @happy-game).
|
||||
<img width="951" alt="image" src="https://github.com/user-attachments/assets/14244f1d-60f5-487f-bde6-6d0e009645fb" />
|
||||
|
||||
## Bug Fixes
|
||||
- Fix Windows context-menu config script generation (#1019).
|
||||
- Fix custom link URL encoding (#1112).
|
||||
- Fix potential logging dead loop (#1101).
|
||||
- Fix drag-to-tray upload error (#1107).
|
||||
- Fix filename encoding (#1121).
|
||||
- Prevent errors on GitHub uploads with duplicate filenames.
|
||||
- Fix beta.2 style issues.
|
||||
- Fix slow filename display in the rename window (#1130).
|
||||
- Fix “open config file” actually opening the log file (#1163).
|
||||
- Fix plugin config dialog not reading/saving correctly.
|
||||
- Fix inability to add new uploader configs (created as edit) (#1198, #1196).
|
||||
- Fix macOS context menu disappearing (thanks @muwoo, #1179).
|
||||
- Fix macOS tray window flashing after right-click (thanks @QThans, #1217).
|
||||
- Fix uploader config page not scrollable when many fields (#1237).
|
||||
- Fix Tencent COS URL encoding (#1265).
|
||||
- Fix plugin list search not working (#1297).
|
||||
- Fix clipboard filename losing “seconds” (#1293).
|
||||
- Fix macOS tray copy-link failing on click (#1280, #1210).
|
||||
- Fix auto-copy URL toggle not turning off (#1294, thanks @happy-game).
|
||||
- Fix Wayland clipboard image upload issue (#1261, thanks @happy-game).
|
||||
- Fix URL uploads with Chinese characters being encoded in filenames (#1339).
|
||||
|
||||
## Other
|
||||
- COS distribution paused due to malicious traffic charges.
|
||||
- Upgrade to Vue3.
|
||||
- Refactor parts of the code.
|
||||
- Refactor parts of the code (again).
|
||||
- Add more detailed hotkey logs (#1031).
|
||||
- Known issue: some button styles were incorrect (fixed in later betas).
|
||||
- Update README scoop installation section (thanks @wuhang2003).
|
||||
@@ -0,0 +1,48 @@
|
||||
# gen_changelog.md
|
||||
|
||||
This guide describes how to generate a consolidated changelog for any PicGo release series (e.g., 2.4.0, 2.5.0) from the GitHub release notes of its betas and finals.
|
||||
|
||||
## Source of truth
|
||||
- Use GitHub release pages only (e.g., `https://github.com/Molunerfinn/PicGo/releases/tag/vX.Y.Z-beta.N` and `vX.Y.Z` if present). You can fetch these via MCP GitHub APIs (e.g., `get_release_by_tag`, `list_releases`) instead of manual browsing.
|
||||
- Do **not** use `CHANGELOG.md` in the repo.
|
||||
- Preserve all text and images exactly as written in the releases.
|
||||
|
||||
## Structure
|
||||
- Target file: `changelog/X.Y.Z.md` (replace with the series version).
|
||||
- Three top-level sections: `## Features`, `## Bug Fixes`, `## Other`.
|
||||
- Do **not** nest content under individual beta version headers; merge all items into these sections.
|
||||
- Keep items in chronological order (early → late) within each section, mirroring the beta sequence.
|
||||
- Keep inline issue references, thanks, and notes as-is.
|
||||
- Remove download-link sections entirely.
|
||||
- After the Chinese sections, insert a line with `----------`, then add a full English translation with the same structure/content (including images).
|
||||
|
||||
## Formatting rules
|
||||
- Markdown, plain ASCII.
|
||||
- Bullet lists only; no numbered lists required.
|
||||
- Indent images under their bullet with two spaces to keep association clear.
|
||||
- Keep inline HTML image tags from the releases (e.g., `<img width="...">`) untouched.
|
||||
- Keep line breaks and multi-line notes intact.
|
||||
- Do not reword or summarize; copy content verbatim except for removing beta headers and download sections.
|
||||
- English translation must mirror the Chinese bullets in order and content (keep images alongside the translated bullets).
|
||||
|
||||
## Regeneration steps
|
||||
1) Collect all release bodies for the target series (e.g., `vX.Y.Z-beta.0…N` and, if present, `vX.Y.Z`) from GitHub releases.
|
||||
2) For each release, copy bullets and images into the appropriate section:
|
||||
- Features ↔ “Feature(s)” or “Features” blocks.
|
||||
- Bug Fixes ↔ “Bug Fixes” blocks.
|
||||
- Other ↔ “Other”, “Notice”, or misc notes that are not features/bugs.
|
||||
3) Omit “国内可下载链接” (or any download links) entirely.
|
||||
4) Drop beta subheadings; keep only section-level bullets in chronological order.
|
||||
5) Ensure images remain adjacent to their bullets with indentation.
|
||||
6) After the Chinese sections are complete, insert `----------` on its own line.
|
||||
7) Append the English translation, preserving bullet order and images, under `## Features`, `## Bug Fixes`, `## Other` again (same section titles, just English content; no “(English)” suffix).
|
||||
8) Save the result to `changelog/X.Y.Z.md`.
|
||||
|
||||
## Quick checklist
|
||||
- [ ] All features present with images kept.
|
||||
- [ ] All bug fixes present.
|
||||
- [ ] All “Other” notes present.
|
||||
- [ ] No beta headers.
|
||||
- [ ] No download links.
|
||||
- [ ] Chronological ordering preserved.
|
||||
- [ ] English translation present with matching bullets/images after `----------`.
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: '',
|
||||
name: 'HomePage',
|
||||
data () {
|
||||
return {
|
||||
version: '',
|
||||
|
||||
+91
-78
@@ -1,27 +1,102 @@
|
||||
{
|
||||
"name": "picgo",
|
||||
"version": "2.4.0-beta.0",
|
||||
"version": "2.4.0-beta.10",
|
||||
"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",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"picgo": "^1.5.11",
|
||||
"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/multer": "^1.4.12",
|
||||
"@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,72 +106,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",
|
||||
"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",
|
||||
"@vue/runtime-dom": "^3.2.45",
|
||||
"conventional-changelog": "^3.1.18",
|
||||
"cz-customizable": "^6.2.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"electron": "^16.0.6",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"eslint": "^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: {}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-5
@@ -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
|
||||
@@ -24,10 +24,11 @@ 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
|
||||
@@ -53,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
|
||||
|
||||
@@ -80,7 +85,7 @@ SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD: Auto Copy URL After Upload
|
||||
SETTINGS_TIPS_PLACEHOLDER_URL: Use $url to represent url position
|
||||
SETTINGS_TIPS_PLACEHOLDER_FILENAME: Use $fileName to represent file name position
|
||||
SETTINGS_TIPS_PLACEHOLDER_EXTNAME: Use $extName to represent file's ext position
|
||||
SETTINGS_TIPS_SUCH_AS: 'Such as: $url/$fileName'
|
||||
SETTINGS_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
|
||||
@@ -111,12 +116,18 @@ 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
|
||||
@@ -129,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
|
||||
@@ -186,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
|
||||
@@ -196,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
|
||||
|
||||
+45
-1
@@ -24,6 +24,7 @@ FIND_NEW_VERSION: 发现新版本
|
||||
NO_MORE_NOTICE: 以后不再提醒
|
||||
SHOW_DEVTOOLS: 打开开发者工具
|
||||
CURRENT_PICBED: 当前图床
|
||||
OPEN_TOOLBOX: 打开修复工具箱
|
||||
|
||||
# ---renderer i18n begin---
|
||||
|
||||
@@ -53,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
|
||||
|
||||
@@ -114,6 +119,12 @@ 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
|
||||
|
||||
@@ -129,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: 等待上传
|
||||
@@ -186,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: 警告
|
||||
@@ -196,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: 快捷键冲突,请重新设置
|
||||
|
||||
+46
-2
@@ -24,6 +24,7 @@ FIND_NEW_VERSION: 發現新版本
|
||||
NO_MORE_NOTICE: 以後不再提醒
|
||||
SHOW_DEVTOOLS: 開啟開發者工具
|
||||
CURRENT_PICBED: 當前圖床
|
||||
OPEN_TOOLBOX: 開啟修復工具箱
|
||||
|
||||
# ---renderer i18n begin---
|
||||
|
||||
@@ -52,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
|
||||
|
||||
@@ -114,6 +119,12 @@ 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
|
||||
|
||||
@@ -129,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: 等待上傳
|
||||
@@ -186,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: 警告
|
||||
@@ -196,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>
|
||||
|
||||
+12
-12
@@ -17,18 +17,18 @@ if [ "$XDG_SESSION_TYPE" = "x11" ]; then
|
||||
echo $filePath
|
||||
fi
|
||||
elif [ "$XDG_SESSION_TYPE" = "wayland" ]; then
|
||||
command -v wl-copy >/dev/null 2>&1 || { echo >&1 "no wl-clipboard"; exit 1; }
|
||||
filePath=`wl-copy -o 2>/dev/null | grep ^file:// | cut -c8-`
|
||||
if [ ! -n "$filePath" ] ;then
|
||||
if
|
||||
wl-copy -t image/png -o >/dev/null 2>&1
|
||||
then
|
||||
wl-copy -t image/png image/png -o >$1 2>/dev/null
|
||||
echo $1
|
||||
else
|
||||
echo "no image"
|
||||
fi
|
||||
command -v wl-paste >/dev/null 2>&1 || { echo >&1 "no wl-clipboard"; exit 1; }
|
||||
isImage=`wl-paste --list-types | grep image`
|
||||
if [ -n "$isImage" ]; then
|
||||
wl-paste --type image/png > $1 2>/dev/null
|
||||
echo $1
|
||||
else
|
||||
echo $filePath
|
||||
echo "no image"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# fallback for unsupported session types
|
||||
echo >&2 "Error: Unsupported session type '$XDG_SESSION_TYPE'."
|
||||
echo >&2 "Solution: The variable of XDG_SESSION_TYPE must set as 'x11' or 'wayland'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+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()
|
||||
+33
-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,21 +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, {
|
||||
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
|
||||
|
||||
app.mixin(mainMixin)
|
||||
app.mixin(dragMixin)
|
||||
|
||||
app.use(VueLazyLoad, {
|
||||
error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg`
|
||||
})
|
||||
Vue.mixin(mainMixin)
|
||||
app.use(ElementUI)
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
app.use(vue3PhotoPreview)
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
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,8 +5,8 @@ import {
|
||||
Tray,
|
||||
dialog,
|
||||
clipboard,
|
||||
systemPreferences,
|
||||
Notification
|
||||
Notification,
|
||||
nativeTheme
|
||||
} from 'electron'
|
||||
import uploader from 'apis/app/uploader'
|
||||
import db, { GalleryDB } from '~/main/apis/core/datastore'
|
||||
@@ -16,13 +16,14 @@ 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 () {
|
||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
||||
const submenu = buildPicBedListMenu()
|
||||
@@ -131,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`
|
||||
}
|
||||
@@ -152,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') {
|
||||
@@ -173,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}`
|
||||
})
|
||||
@@ -189,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)) {
|
||||
@@ -205,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`)
|
||||
@@ -213,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
|
||||
@@ -227,7 +239,9 @@ 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!
|
||||
@@ -243,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) {
|
||||
@@ -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'),
|
||||
@@ -75,13 +76,13 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
|
||||
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'
|
||||
@@ -21,14 +21,7 @@ import path from 'path'
|
||||
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()
|
||||
})
|
||||
})
|
||||
}
|
||||
import { cleanupFormUploaderFiles } from '~/main/utils/cleanupFormUploaderFiles'
|
||||
|
||||
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
|
||||
return new Promise((resolve) => {
|
||||
@@ -93,14 +86,19 @@ class Uploader {
|
||||
let name: undefined | string | null
|
||||
let fileName: string | undefined
|
||||
if (autoRename) {
|
||||
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname
|
||||
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmssSSS') + item.extname
|
||||
} else {
|
||||
fileName = item.fileName
|
||||
}
|
||||
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
|
||||
@@ -129,7 +127,7 @@ class Uploader {
|
||||
}
|
||||
const buffer = nativeImage.toPNG()
|
||||
const baseDir = picgo.baseDir
|
||||
const fileName = `${dayjs().format('YYYYMMDDHHmmSSS')}.png`
|
||||
const fileName = `${dayjs().format('YYYYMMDDHHmmssSSS')}.png`
|
||||
filePath = path.join(baseDir, CLIPBOARD_IMAGE_FOLDER, fileName)
|
||||
await writeFile(filePath, buffer)
|
||||
return await this.upload([filePath])
|
||||
@@ -177,6 +175,9 @@ class Uploader {
|
||||
})
|
||||
}, 500)
|
||||
return false
|
||||
} finally {
|
||||
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
|
||||
cleanupFormUploaderFiles(img)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -5,6 +5,7 @@ import { app as APP } from 'electron'
|
||||
import { getLogger } from '@core/utils/localLogger'
|
||||
import dayjs from 'dayjs'
|
||||
import { T } from '~/main/i18n'
|
||||
import { FORM_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
const STORE_PATH = APP.getPath('userData')
|
||||
const configFilePath = path.join(STORE_PATH, 'data.json')
|
||||
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
|
||||
@@ -131,9 +132,19 @@ function getGalleryDBPath (): {
|
||||
}
|
||||
}
|
||||
|
||||
function getFormImageFolderPath (): string {
|
||||
const STORE_PATH = dbPathDir()
|
||||
const formImagesPath = path.join(STORE_PATH, FORM_IMAGE_FOLDER)
|
||||
if (!fs.existsSync(formImagesPath)) {
|
||||
fs.mkdirSync(formImagesPath, { recursive: true })
|
||||
}
|
||||
return formImagesPath
|
||||
}
|
||||
|
||||
export {
|
||||
dbChecker,
|
||||
dbPathChecker,
|
||||
dbPathDir,
|
||||
getGalleryDBPath
|
||||
getGalleryDBPath,
|
||||
getFormImageFolderPath
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -63,13 +62,14 @@ export default {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -29,7 +29,8 @@ import {
|
||||
OPEN_WINDOW,
|
||||
GET_LANGUAGE_LIST,
|
||||
SET_CURRENT_LANGUAGE,
|
||||
GET_CURRENT_LANGUAGE
|
||||
GET_CURRENT_LANGUAGE,
|
||||
GET_PICBED_CONFIG
|
||||
} from '#/events/constants'
|
||||
|
||||
import { GalleryDB } from 'apis/core/datastore'
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ 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'
|
||||
@@ -95,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 () {
|
||||
@@ -210,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)
|
||||
}
|
||||
|
||||
@@ -1,83 +1,61 @@
|
||||
import { ipcMain, IpcMainEvent } from 'electron'
|
||||
import { IRPCActionType } from '~/universal/types/enum'
|
||||
import { RPC_ACTIONS } from '#/events/constants'
|
||||
import {
|
||||
deleteUploaderConfig,
|
||||
getUploaderConfigList,
|
||||
selectUploaderConfig,
|
||||
updateUploaderConfig
|
||||
} from '~/main/utils/handleUploaderConfig'
|
||||
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 {
|
||||
start () {
|
||||
ipcMain.on(RPC_ACTIONS, (event: IpcMainEvent, action: IRPCActionType, args: any[], callbackId: string) => {
|
||||
try {
|
||||
switch (action) {
|
||||
case IRPCActionType.GET_PICBED_CONFIG_LIST: {
|
||||
const configList = this.getPicBedConfigList(args as IGetUploaderConfigListArgs)
|
||||
this.sendBack(event, action, configList, callbackId)
|
||||
break
|
||||
}
|
||||
case IRPCActionType.DELETE_PICBED_CONFIG: {
|
||||
const res = this.deleteUploaderConfig(args as IDeleteUploaderConfigArgs)
|
||||
this.sendBack(event, action, res, callbackId)
|
||||
break
|
||||
}
|
||||
case IRPCActionType.SELECT_UPLOADER: {
|
||||
this.selectUploaderConfig(args as ISelectUploaderConfigArgs)
|
||||
this.sendBack(event, action, true, callbackId)
|
||||
break
|
||||
}
|
||||
case IRPCActionType.UPDATE_UPLOADER_CONFIG: {
|
||||
this.updateUploaderConfig(args as IUpdateUploaderConfigArgs)
|
||||
this.sendBack(event, action, true, callbackId)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
this.sendBack(event, action, null, callbackId)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.sendBack(event, action, null, callbackId)
|
||||
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) {
|
||||
event.sender.send(RPC_ACTIONS, data, action, callbackId)
|
||||
if (callbackId) {
|
||||
event.sender.send(RPC_ACTIONS, data, action, callbackId)
|
||||
}
|
||||
}
|
||||
|
||||
private getPicBedConfigList (args: IGetUploaderConfigListArgs) {
|
||||
const [type] = args
|
||||
const config = getUploaderConfigList(type)
|
||||
return config
|
||||
start () {
|
||||
ipcMain.on(RPC_ACTIONS, this.rpcEventHandler)
|
||||
}
|
||||
|
||||
private deleteUploaderConfig (args: IDeleteUploaderConfigArgs) {
|
||||
const [type, id] = args
|
||||
const config = deleteUploaderConfig(type, id)
|
||||
return config
|
||||
use (routes: IRPCRoutes) {
|
||||
for (const [action, handler] of routes) {
|
||||
this.routes.set(action, handler)
|
||||
}
|
||||
}
|
||||
|
||||
private selectUploaderConfig (args: ISelectUploaderConfigArgs) {
|
||||
const [type, id] = args
|
||||
const config = selectUploaderConfig(type, id)
|
||||
return config
|
||||
}
|
||||
|
||||
private updateUploaderConfig (args: IUpdateUploaderConfigArgs) {
|
||||
const [type, id, config] = args
|
||||
const res = updateUploaderConfig(type, id, config)
|
||||
return res
|
||||
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',
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import picgo from '@core/picgo'
|
||||
import logger from '@core/picgo/logger'
|
||||
import axios from 'axios'
|
||||
import formUploader from './middlewares/formUploader'
|
||||
|
||||
class Server {
|
||||
private httpServer: http.Server
|
||||
@@ -56,6 +57,8 @@ class Server {
|
||||
success: false
|
||||
}
|
||||
})
|
||||
} else if (formUploader.isFileUpload(request)) {
|
||||
formUploader.handleFileUpload(request, response)
|
||||
} else {
|
||||
let body: string = ''
|
||||
let postObj: IObj
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import http from 'http'
|
||||
import multer from 'multer'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { dbPathDir } from 'apis/core/datastore/dbChecker'
|
||||
import logger from '@core/picgo/logger'
|
||||
import { handleResponse } from '../utils'
|
||||
import routers from '../routerManager'
|
||||
import { FORM_IMAGE_FOLDER } from '~/universal/utils/static'
|
||||
|
||||
// Multer 错误类型定义
|
||||
export interface MulterError extends Error {
|
||||
code: string
|
||||
field?: string
|
||||
storageErrors?: any[]
|
||||
}
|
||||
|
||||
// Multer 中间件适配器类型
|
||||
export type MulterMiddleware = (
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
callback: (error?: MulterError) => void
|
||||
) => void;
|
||||
|
||||
// 扩展 multer 函数的返回类型
|
||||
declare module 'multer' {
|
||||
interface Multer {
|
||||
array(fieldname: string, maxCount?: number): MulterMiddleware;
|
||||
single(fieldname: string): MulterMiddleware;
|
||||
fields(fields: { name: string; maxCount?: number }[]): MulterMiddleware;
|
||||
none(): MulterMiddleware;
|
||||
}
|
||||
}
|
||||
|
||||
class FormUploader {
|
||||
private upload!: multer.Multer
|
||||
private formImagesPath!: string
|
||||
|
||||
constructor () {
|
||||
this.initializeStorage()
|
||||
this.setupMulter()
|
||||
}
|
||||
|
||||
private initializeStorage () {
|
||||
const STORE_PATH = dbPathDir()
|
||||
this.formImagesPath = path.join(STORE_PATH, FORM_IMAGE_FOLDER)
|
||||
if (!fs.existsSync(this.formImagesPath)) {
|
||||
fs.mkdirSync(this.formImagesPath, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
private setupMulter () {
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req: http.IncomingMessage, file: Express.Multer.File, cb: (error: Error | null, destination: string) => void) => {
|
||||
cb(null, this.formImagesPath)
|
||||
},
|
||||
filename: (req: http.IncomingMessage, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => {
|
||||
cb(null, file.originalname || Date.now() + path.extname(file.originalname))
|
||||
}
|
||||
})
|
||||
|
||||
this.upload = multer({
|
||||
storage
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件上传的中间件
|
||||
*/
|
||||
public handleFileUpload = (request: http.IncomingMessage, response: http.ServerResponse): void => {
|
||||
logger.info('[PicGo Server] handling file upload')
|
||||
|
||||
this.upload.array('files')(request, response, async (err?: MulterError) => {
|
||||
if (err) {
|
||||
logger.error('[PicGo Server] file upload error', err)
|
||||
return handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'File upload failed'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const files = request.files
|
||||
if (!files || files.length === 0) {
|
||||
return handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'No files were uploaded'
|
||||
}
|
||||
})
|
||||
}
|
||||
const filePaths = files.map((file) => file.path)
|
||||
logger.info('[PicGo Server] files uploaded: ' + filePaths.join(', '))
|
||||
|
||||
const handler = routers.getHandler(request.url!)
|
||||
handler!({
|
||||
list: filePaths,
|
||||
response
|
||||
})
|
||||
} catch (err: any) {
|
||||
logger.error('[PicGo Server] process upload files error', err)
|
||||
handleResponse({
|
||||
response,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'Failed to process uploaded files'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查请求是否为文件上传
|
||||
*/
|
||||
public isFileUpload (request: http.IncomingMessage): boolean {
|
||||
return !!(request.headers['content-type'] && request.headers['content-type'].includes('multipart/form-data'))
|
||||
}
|
||||
}
|
||||
|
||||
export default new FormUploader()
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { getFormImageFolderPath } from '@core/datastore/dbChecker'
|
||||
import logger from '@core/picgo/logger'
|
||||
|
||||
export const cleanupFormUploaderFiles = (filePathList?: string[]): void => {
|
||||
const formImageFolderPath = getFormImageFolderPath()
|
||||
if (Array.isArray(filePathList)) {
|
||||
filePathList.forEach(async filePath => {
|
||||
try {
|
||||
// 检查文件路径是否在 formImageFolderPath 目录下
|
||||
const relativePath = path.relative(formImageFolderPath, filePath)
|
||||
const isWithinFolder = !relativePath.startsWith('..') && !path.isAbsolute(relativePath)
|
||||
|
||||
if (isWithinFolder && await fs.pathExists(filePath)) {
|
||||
await fs.remove(filePath)
|
||||
logger.info(`[PicGo] Deleted temp file: ${filePath}`)
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error(`[PicGo] Failed to delete temp file ${filePath}:`, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
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) {
|
||||
if (db.get('settings.autoCopyUrl') !== false) {
|
||||
clipboard.writeText(str)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import { uuid } from 'uuidv4'
|
||||
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) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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}$`), '')
|
||||
@@ -21,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,180 +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
|
||||
:label="$T('UPLOADER_CONFIG_NAME')"
|
||||
required
|
||||
prop="_configName"
|
||||
>
|
||||
<el-input
|
||||
type="input"
|
||||
v-model="ruleForm._configName"
|
||||
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<!-- dynamic config -->
|
||||
<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[]) {
|
||||
const config = await this.getCurConfigFormData()
|
||||
const configId = this.$route.params.configId
|
||||
this.ruleForm = Object.assign({}, config)
|
||||
if (val.length > 0) {
|
||||
this.configList = cloneDeep(val).map((item) => {
|
||||
if (!configId) return item
|
||||
let defaultValue = item.default !== undefined
|
||||
? item.default
|
||||
: item.type === 'checkbox'
|
||||
? []
|
||||
: null
|
||||
if (item.type === 'checkbox') {
|
||||
const defaults = item.choices?.filter((i: any) => {
|
||||
return i.checked
|
||||
}).map((i: any) => i.value) || []
|
||||
defaultValue = union(defaultValue, defaults)
|
||||
}
|
||||
if (config && config[item.name] !== undefined) {
|
||||
defaultValue = config[item.name]
|
||||
}
|
||||
this.$set(this.ruleForm, item.name, defaultValue)
|
||||
return item
|
||||
})
|
||||
case 'uploader': {
|
||||
return `picBed.${props.id}`
|
||||
}
|
||||
}
|
||||
|
||||
async getCurConfigFormData () {
|
||||
const configId = this.$route.params.configId
|
||||
const curTypeConfigList = await this.getConfig<IStringKeyMap[]>(`uploader.${this.id}.configList`) || []
|
||||
return curTypeConfigList.find(i => i._id === configId) || {}
|
||||
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="`uploader-config-page-${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(/uploader-config-page-/)
|
||||
if (type === null) {
|
||||
this.$router.push({
|
||||
name: index
|
||||
})
|
||||
} else {
|
||||
const type = index.replace(/uploader-config-page-/, '')
|
||||
this.$router.push({
|
||||
name: 'UploaderConfigPage',
|
||||
params: {
|
||||
type
|
||||
}
|
||||
})
|
||||
// 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
|
||||
|
||||
+470
-406
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
-763
@@ -1,784 +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>
|
||||
<div class="custom-title">
|
||||
{{ $T('SETTINGS_TIPS_PLACEHOLDER_EXTNAME') }}
|
||||
</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"
|
||||
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) && !/\$fileName/.test(value) && !/\$extName/.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.autoCopyUrl === undefined ? true : settings.autoCopyUrl
|
||||
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'>
|
||||
@@ -786,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
|
||||
@@ -798,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
|
||||
@@ -813,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
|
||||
@@ -826,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
|
||||
|
||||
+442
-333
@@ -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,331 +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,
|
||||
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 = `this.src="file://${__static.replace(/\\/g, '/')}/roundLogo.png"`
|
||||
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
|
||||
}
|
||||
})
|
||||
this.getPluginList()
|
||||
this.getSearchResult = debounce(this.getSearchResult, 50)
|
||||
this.needReload = await this.getConfig<boolean>('needReload') || false
|
||||
}
|
||||
|
||||
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(() => {
|
||||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
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.maintainers[0]?.username || '',
|
||||
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')
|
||||
}
|
||||
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'>
|
||||
@@ -451,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;
|
||||
@@ -471,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
|
||||
@@ -500,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>
|
||||
+138
-92
@@ -1,24 +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 v-lazy="item.imgUrl" class="upload-img">
|
||||
<div class="upload-img__title" :title="item.fileName">{{ item.fileName }}</div>
|
||||
<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>
|
||||
@@ -26,99 +61,101 @@
|
||||
</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'
|
||||
import { getRawData } from '@/utils/common'
|
||||
|
||||
@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: ''
|
||||
}
|
||||
|
||||
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!
|
||||
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!
|
||||
await ipcRenderer.invoke(PASTE_TEXT, getRawData(item))
|
||||
const myNotification = new Notification(notification.title, notification)
|
||||
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>
|
||||
|
||||
@@ -179,11 +216,20 @@ 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 8px 4px
|
||||
height 100%
|
||||
|
||||
+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
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
<template>
|
||||
<div id="config-list-view">
|
||||
<div
|
||||
id="config-list-view"
|
||||
class="h-[425px]"
|
||||
>
|
||||
<div class="view-title">
|
||||
{{ $T('SETTINGS') }}
|
||||
</div>
|
||||
<el-row :gutter="15" justify="space-between" align="center" type="flex" class="config-list">
|
||||
<el-row
|
||||
:gutter="15"
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
type="flex"
|
||||
class="config-list"
|
||||
>
|
||||
<el-col
|
||||
class="config-item-col"
|
||||
v-for="item in curConfigList"
|
||||
:key="item._id"
|
||||
class="config-item-col"
|
||||
:span="11"
|
||||
:offset="1"
|
||||
>
|
||||
@@ -15,12 +24,32 @@
|
||||
: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="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">
|
||||
<i class="el-icon-edit" @click="openEditPage(item._id)"></i>
|
||||
<i :class="`el-icon-delete ${curConfigList.length <= 1 ? 'disabled' : ''}`" @click.stop="() => deleteConfig(item._id)"></i>
|
||||
<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>
|
||||
@@ -33,96 +62,128 @@
|
||||
class="config-item config-item-add"
|
||||
@click="addNewConfig"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<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" @click="setDefaultPicBed(type)" round :disabled="defaultPicBed === type">{{ $T('SETTINGS_SET_DEFAULT_PICBED') }}</el-button>
|
||||
<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">
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
<script lang="ts" setup>
|
||||
import { Edit, Delete, Plus } from '@element-plus/icons-vue'
|
||||
import { saveConfig, triggerRPC } from '@/utils/dataSender'
|
||||
import dayjs from 'dayjs'
|
||||
import mixin from '@/utils/ConfirmButtonMixin'
|
||||
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()
|
||||
|
||||
@Component({
|
||||
name: 'UploaderConfigPage',
|
||||
mixins: [mixin]
|
||||
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()
|
||||
})
|
||||
export default class extends Vue {
|
||||
type!: string;
|
||||
curConfigList: IStringKeyMap[] = [];
|
||||
defaultConfigId = '';
|
||||
|
||||
async selectItem (id: string) {
|
||||
await this.triggerRPC<void>(IRPCActionType.SELECT_UPLOADER, this.type, id)
|
||||
this.defaultConfigId = id
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
type.value = $route.params.type as string
|
||||
getCurrentConfigList()
|
||||
})
|
||||
|
||||
created () {
|
||||
this.type = this.$route.params.type
|
||||
this.getCurrentConfigList()
|
||||
}
|
||||
async function getCurrentConfigList () {
|
||||
const configList = await triggerRPC<IUploaderConfigItem>(IRPCActionType.GET_PICBED_CONFIG_LIST, type.value)
|
||||
curConfigList.value = configList?.configList ?? []
|
||||
defaultConfigId.value = configList?.defaultId ?? ''
|
||||
}
|
||||
|
||||
async getCurrentConfigList () {
|
||||
const configList = await this.triggerRPC<IUploaderConfigItem>(IRPCActionType.GET_PICBED_CONFIG_LIST, this.type)
|
||||
this.curConfigList = configList?.configList ?? []
|
||||
this.defaultConfigId = configList?.defaultId ?? ''
|
||||
}
|
||||
|
||||
openEditPage (configId: string) {
|
||||
this.$router.push({
|
||||
name: 'picbeds',
|
||||
params: {
|
||||
type: this.type,
|
||||
configId
|
||||
},
|
||||
query: {
|
||||
defaultConfigId: this.defaultConfigId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
formatTime (time: number):string {
|
||||
return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
async deleteConfig (id: string) {
|
||||
const res = await this.triggerRPC<IUploaderConfigItem | undefined>(IRPCActionType.DELETE_PICBED_CONFIG, this.type, id)
|
||||
if (!res) return
|
||||
this.curConfigList = res.configList
|
||||
this.defaultConfigId = res.defaultId
|
||||
}
|
||||
|
||||
addNewConfig () {
|
||||
this.$router.push({
|
||||
name: 'picbeds',
|
||||
params: {
|
||||
type: this.type,
|
||||
configId: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setDefaultPicBed (type: string) {
|
||||
this.saveConfig({
|
||||
'picBed.current': type,
|
||||
'picBed.uploader': type
|
||||
})
|
||||
// @ts-ignore 来自mixin的数据
|
||||
this.defaultPicBed = type
|
||||
const successNotification = new Notification(this.$T('SETTINGS_DEFAULT_PICBED'), {
|
||||
body: this.$T('TIPS_SET_SUCCEED')
|
||||
})
|
||||
successNotification.onclick = () => {
|
||||
return true
|
||||
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
|
||||
@@ -135,7 +196,7 @@ export default class extends Vue {
|
||||
flex-wrap wrap
|
||||
width: 98%
|
||||
.config-item
|
||||
height 85px
|
||||
height 100px
|
||||
margin-bottom 20px
|
||||
border-radius 4px
|
||||
cursor pointer
|
||||
@@ -181,9 +242,10 @@ export default class extends Vue {
|
||||
.selected
|
||||
border 1px solid #409EFF
|
||||
.set-default-container
|
||||
position absolute
|
||||
bottom 10px
|
||||
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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user