Compare commits

...
21 Commits
Author SHA1 Message Date
PiEgg 8e91582adc 🐛 Fix: config page scroll bug
ISSUES CLOSED: #1237
2024-04-19 09:39:44 +08:00
PiEgg aaec99f466 Feature: add startup mode
ISSUES CLOSED: #915
2024-02-28 15:59:20 +08:00
PiEgg 50e0a64519 🐛 Fix: tray menu open bug
ISSUES CLOSED: #1217
2024-02-25 09:55:41 +08:00
PiEgg 8cede85732 🎉 Release: v2.4.0-beta.6 2023-11-19 09:27:56 +08:00
PiEgg 050a3dd602 🐛 Fix: can't add new config for picbed
ISSUES CLOSED: #1184
2023-10-22 14:00:51 +08:00
木偶andGitHub cd07b33e6b 🐛 Fix: app.asar directroy copy error (#1180)
ISSUES CLOSED: #1179
2023-10-22 13:23:37 +08:00
PiEgg 6d4e1c27d5 🎉 Release: v2.4.0-beta.5 2023-09-10 17:41:19 +08:00
PiEgg 8fc65f4dcc ⬆️ Upgrade: picgo version 2023-09-10 17:40:28 +08:00
PiEggandGitHub 0f7b07dd7b Feature: add gallery toolbox menu (#1177) 2023-09-10 17:35:43 +08:00
PiEgg dca6667a81 🎉 Release: v2.4.0-beta.4 2023-08-26 12:37:03 +08:00
PiEgg 1b92f20a7d Feature: support "tips" option for uploader 2023-08-26 12:33:42 +08:00
barnettyangandGitHub a2320c3503 Feature(tcyun): add slim section (#1165) 2023-08-23 16:46:40 +08:00
PiEgg 2db0fea6ee 🐛 Fix: open config file bug
ISSUES CLOSED: #1163
2023-08-15 20:22:50 +08:00
PiEgg 894d0a243e Feature: add configName for upload page 2023-07-23 11:03:07 +08:00
PiEgg 536b70832e 🎉 Release: v2.4.0-beta.3 2023-07-09 14:57:11 +08:00
PiEgg bc2e92836c 🐛 Fix: rename page bug
ISSUES CLOSED: #1130
2023-07-09 14:19:01 +08:00
浮心物语andGitHub a5ed1d107e 📝 Docs: update scoop install (#1109) 2023-07-09 13:07:47 +08:00
PiEgg e3566b5a8e 🐛 Fix: tailwind css bug 2023-07-09 13:02:16 +08:00
PiEgg 64eb832e06 🎉 Release: v2.4.0-beta.2 2023-07-09 10:13:22 +08:00
PiEgg 10e0584c96 🔨 Refactor: setting page 2023-07-09 10:12:49 +08:00
PiEgg 8d9a4000f3 🐛 Fix: fileName encode bug
ISSUES CLOSED: #1121
2023-05-07 13:48:45 +08:00
81 changed files with 2991 additions and 1405 deletions
+2 -1
View File
@@ -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'
+5 -3
View File
@@ -20,6 +20,8 @@
"stylusSupremacy.sortProperties": "grouped",
"stylusSupremacy.quoteChar": "\"",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"prettier.enable": false
}
+54
View File
@@ -1,3 +1,57 @@
# :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)
+1 -1
View File
@@ -59,7 +59,7 @@ PicGo 本体支持如下图床:
| GitHub Release | https://github.com/Molunerfinn/PicGo/releases | All | 国内下载速度可能会慢 |
| [腾讯云COS](https://cloud.tencent.com/product/cos) | https://github.com/Molunerfinn/PicGo/releases 附在更新日志结尾 | All | 感谢 [腾讯云COS](https://cloud.tencent.com/product/cos) 提供的赞助支持 |
| [山东大学镜像站](https://mirrors.sdu.edu.cn/) | https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo | All | 感谢 [山东大学镜像站](https://mirrors.sdu.edu.cn/) 提供的镜像支持 |
| [Scoop](https://scoop.sh/) | `scoop bucket add helbing https://github.com/helbing/scoop-bucket` & `scoop install picgo` | Windows | 感谢 @helbing 的贡献 |
| [Scoop](https://scoop.sh/) | `scoop bucket add extras` & `scoop install picgo` | Windows | 感谢 @huangnauh@Gladtbam 的贡献 |
| [Chocolatey](https://chocolatey.org/) | `choco install picgo` | Windows | 感谢 @iYato 的贡献 |
| [Homebrew](https://brew.sh/) | `brew install picgo --cask` | macOS | 感谢 @womeimingzi11 的贡献 |
| [AUR](https://aur.archlinux.org/packages/yay) | `yay -S picgo-appimage` | Arch-Linux | 感谢 @houbaron 的贡献 |
+17 -10
View File
@@ -1,6 +1,6 @@
{
"name": "picgo",
"version": "2.4.0-beta.1",
"version": "2.4.0-beta.6",
"private": true,
"scripts": {
"build": "vue-cli-service electron:build",
@@ -15,31 +15,33 @@
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"release": "vue-cli-service electron:build --publish always",
"upload-dist": "node ./scripts/upload-dist.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"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.0.4",
"@picgo/store": "^2.1.0",
"axios": "^0.19.0",
"compare-versions": "^4.1.3",
"core-js": "^3.27.1",
"element-plus": "^2.2.28",
"element-plus": "^2.3.7",
"epipebomb": "^1.0.0",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"marked": "^7.0.4",
"mitt": "^3.0.0",
"picgo": "^1.5.1",
"picgo": "^1.5.6",
"qrcode.vue": "^3.3.3",
"shell-path": "2.1.0",
"uuidv4": "^6.2.11",
"vue": "^3.2.45",
"vue-router": "^4.1.6",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-router": "^4.2.2",
"vue3-lazyload": "^0.3.6",
"vue3-photo-preview": "^0.2.9",
"vue3-photo-preview": "^0.3.0",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
@@ -56,6 +58,7 @@
"@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",
@@ -67,9 +70,11 @@
"@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",
@@ -79,8 +84,10 @@
"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"
},
@@ -99,7 +106,7 @@
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
"commit-msg": "npm run lint:dpdm && commitlint -E HUSKY_GIT_PARAMS"
}
},
"resolutions": {
+2 -1
View File
@@ -1,5 +1,6 @@
module.exports = {
plugins: {
autoprefixer: {}
autoprefixer: {},
tailwindcss: {}
}
}
+19 -6
View File
@@ -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
@@ -28,7 +28,7 @@ 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
@@ -56,6 +56,8 @@ 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
@@ -83,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
@@ -119,6 +121,10 @@ 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
@@ -134,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
@@ -193,10 +206,10 @@ UPDATE_PLUGIN: Update Plugin
# toolbox
TOOLBOX: Toolbox
TOOLBOX_TITLE: Troubleshoot PicGo runtime issues
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_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
@@ -227,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
+14 -1
View File
@@ -56,6 +56,8 @@ BATCH_COPY_LINK_SUCCEED: 批量复制链接成功
FILE_RENAME: 文件改名
COPY_FILE_PATH: 复制文件路径
OPEN_FILE_PATH: 打开文件路径
SUCCESS: 成功
FAILED: 失败
# settings
@@ -119,6 +121,10 @@ 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
@@ -134,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: 等待上传
@@ -227,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: 快捷键冲突,请重新设置
+20 -7
View File
@@ -56,6 +56,8 @@ BATCH_COPY_LINK_SUCCEED: 批量複製連結成功
FILE_RENAME: 檔案改名
COPY_FILE_PATH: 複製檔案路徑
OPEN_FILE_PATH: 打開檔案路徑
SUCCESS: 成功
FAILED: 失敗
# settings
@@ -119,6 +121,10 @@ 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
@@ -134,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: 等待上傳
@@ -193,27 +206,27 @@ UPDATE_PLUGIN: 更新插件
# toolbox
TOOLBOX: 工具箱
TOOLBOX_TITLE: 排查 PicGo 執行時問題
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_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_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_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_TIPS: 剪貼板圖片臨時文件夾路徑是:${path}
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_NOT_EXIST_TIPS: 剪貼板圖片臨時文件夾不存在:${path}
TOOLBOX_CHECK_CLIPBOARD_FILE_PATH_ERROR_TIPS: 請自行創建文件夾:${path}
@@ -227,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: 快捷鍵衝突,請重新設定
+1
View File
@@ -1,3 +1,4 @@
import './renderer/assets/css/tailwind.css'
import { createApp } from 'vue'
import App from './renderer/App.vue'
import router from './renderer/router'
+6
View File
@@ -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
}
+18 -4
View File
@@ -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}]`)
}
}
+49 -28
View File
@@ -16,10 +16,10 @@ 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
@@ -138,7 +138,9 @@ export function createContextMenu () {
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`
}
@@ -154,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') {
@@ -175,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}`
})
@@ -191,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)) {
@@ -230,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!
@@ -246,16 +257,16 @@ 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 (process.platform === 'darwin') {
if (isMacOS) {
if (db.get('settings.showDockIcon') !== false) {
app.dock.show()
app.dock.setMenu(createContextMenu())
@@ -270,25 +281,35 @@ export function createMenu () {
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)
+8 -8
View File
@@ -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,8 +22,8 @@ const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
}
export const uploadClipboardFiles = async (): Promise<string> => {
logger.info('upload clipboard file')
const img = await handleClipboardUploading()
console.log(img)
if (img !== false) {
if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
@@ -42,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'),
@@ -76,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 {
+10 -11
View File
@@ -13,7 +13,7 @@ import { IWindowList } from '#/types/enum'
import util from 'util'
import { IPicGo } from 'picgo'
import { showNotification, calcDurationRange, getClipboardFilePath } from '~/main/utils/common'
import { RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
import logger from '@core/picgo/logger'
import { T } from '~/main/i18n'
import fse from 'fs-extra'
@@ -22,14 +22,6 @@ import { privacyManager } from '~/main/utils/privacyManager'
import writeFile from 'write-file-atomic'
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
const waitForShow = (webcontent: WebContents) => {
return new Promise<void>((resolve) => {
webcontent.on('did-finish-load', () => {
resolve()
})
})
}
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
return new Promise((resolve) => {
const windowId = window.id
@@ -99,8 +91,13 @@ class Uploader {
}
if (rename) {
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
await waitForShow(window.webContents)
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
logger.info('wait for rename window ready...')
ipcMain.on(GET_RENAME_FILE_NAME, (evt) => {
if (evt.sender.id === window.webContents.id) {
logger.info('rename window ready, wait for rename...')
window.webContents.send(RENAME_FILE_NAME, fileName, item.fileName, window.webContents.id)
}
})
name = await waitForRename(window, window.webContents.id)
}
item.fileName = name || fileName
@@ -177,6 +174,8 @@ class Uploader {
})
}, 500)
return false
} finally {
ipcMain.removeAllListeners(GET_RENAME_FILE_NAME)
}
}
}
+19 -7
View File
@@ -5,14 +5,14 @@ import {
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 { T } from '~/main/i18n'
import { isLinux } from '~/universal/utils/common'
// import { URLSearchParams } from 'url'
const windowList = new Map<IWindowList, IWindowListItem>()
@@ -26,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,
@@ -93,9 +108,6 @@ windowList.set(IWindowList.SETTING_WINDOW, {
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)
@@ -117,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,
@@ -132,7 +144,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
}
}
if (db.get('settings.miniWindowOntop')) {
if (db.get('settings.miniWindowOnTop')) {
obj.alwaysOnTop = true
}
return obj
+4
View File
@@ -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`
+20 -5
View File
@@ -18,6 +18,7 @@ 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 {
@@ -26,7 +27,7 @@ class GuiApi implements IGuiApi {
private windowId: number = -1
private settingWindowId: number = -1
private constructor () {
console.log('init guiapi')
console.log('init gui api')
}
public static getInstance (): GuiApi {
@@ -50,7 +51,7 @@ class GuiApi implements IGuiApi {
})
}
private getWebcontentsByWindowId (id: number) {
private getWebContentsByWindowId (id: number) {
return BrowserWindow.fromId(id)?.webContents
}
@@ -59,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)
@@ -75,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'
@@ -94,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 []
@@ -131,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
*/
-1
View File
@@ -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,
+3 -3
View File
@@ -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'
@@ -62,7 +62,7 @@ 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')
@@ -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)
}
+5 -4
View File
@@ -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)
}
})
}
+2
View File
@@ -5,6 +5,7 @@ import { configRouter } from './routes/config'
import { versionRouter } from './routes/version'
import { toolboxRouter } from './routes/toolbox'
import { systemRouter } from './routes/system'
import { galleryToolboxRouter } from './routes/galleryToolbox'
class RPCServer implements IRPCServer {
private routes: IRPCRoutes = new Map()
@@ -53,6 +54,7 @@ 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,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
}
+4 -4
View File
@@ -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`)
}
+17 -2
View File
@@ -37,6 +37,7 @@ 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'
@@ -79,11 +80,25 @@ 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)
+2 -1
View File
@@ -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',
+25 -2
View File
@@ -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
})
+36
View File
@@ -1,6 +1,7 @@
import fs from 'fs-extra'
import db from '~/main/apis/core/datastore'
import { clipboard, Notification, dialog } from 'electron'
import { handleUrlEncode } from '~/universal/utils/common'
export const handleCopyUrl = (str: string): void => {
if (db.get('settings.autoCopy') !== false) {
@@ -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 -1
View File
@@ -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) {
+2 -6
View File
@@ -1,6 +1,5 @@
import { IPasteStyle } from '#/types/enum'
import db from 'apis/core/datastore'
import { handleUrlEncode } from '~/universal/utils/common'
import { handleUrlEncodeWithSetting } from './common'
const formatCustomLink = (customLink: string, item: ImgInfo) => {
const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
@@ -22,10 +21,7 @@ const formatCustomLink = (customLink: string, item: ImgInfo) => {
}
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
let url = item.url || item.imgUrl
if (db.get('settings.encodeOutputURL') !== false) {
url = handleUrlEncode(url)
}
const url = handleUrlEncodeWithSetting(item.url || item.imgUrl)
const _customLink = customLink || '$url'
const tpl = {
markdown: `![](${url})`,
+3
View File
@@ -11,6 +11,9 @@ 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 () => {
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+56 -116
View File
@@ -3,81 +3,35 @@
id="config-form"
:class="props.colorMode === 'white' ? 'white' : ''"
>
<el-form
<BaseConfigForm
ref="$form"
label-position="left"
label-width="50%"
:model="ruleForm"
size="small"
:config="configList"
:form-model="formModel"
>
<el-form-item
:label="$T('UPLOADER_CONFIG_NAME')"
required
prop="_configName"
>
<el-input
v-model="ruleForm._configName"
type="input"
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
/>
</el-form-item>
<!-- dynamic config -->
<el-form-item
v-for="(item, index) in configList"
:key="item.name + index"
:label="item.alias || item.name"
:required="item.required"
:prop="item.name"
>
<el-input
v-if="item.type === 'input' || item.type === 'password'"
v-model="ruleForm[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="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"
:key="choice.name || choice.value || choice"
:label="choice.name || choice.value || choice"
:value="choice.value || choice"
<el-input
v-model="formModel._configName"
type="input"
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
/>
</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"
: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="ruleForm[item.name]"
active-text="yes"
inactive-text="no"
/>
</el-form-item>
</el-form-item>
</template>
<slot />
</el-form>
</BaseConfigForm>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, toRefs } from 'vue'
import { cloneDeep, union } from 'lodash'
import { getConfig } from '@/utils/dataSender'
import { useRoute } from 'vue-router'
import type { FormInstance } from 'element-plus'
import BaseConfigForm from './form/BaseConfigForm.vue'
import { useConfigForm } from '@/hooks/useConfigForm'
interface IProps {
config: any[]
@@ -88,33 +42,14 @@ interface IProps {
const props = defineProps<IProps>()
const $route = useRoute()
const $form = ref<FormInstance>()
const $form = ref<IFormInstance>()
const configList = ref<IPicGoPluginConfig[]>([])
const ruleForm = reactive<IStringKeyMap>({})
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => {
handleConfigChange(val)
}, {
deep: true,
immediate: true
})
function handleConfigChange (val: any) {
handleConfig(val)
}
const formModel = reactive<IStringKeyMap>({})
const isUploader = props.type === 'uploader'
async function validate (): Promise<IStringKeyMap | false> {
return new Promise((resolve) => {
$form.value?.validate((valid: boolean) => {
if (valid) {
resolve(ruleForm)
} else {
resolve(false)
return false
}
})
})
const res = await $form.value?.validate() || false
return res
}
function getConfigType () {
@@ -133,45 +68,50 @@ function getConfigType () {
}
}
async function handleConfig (val: IPicGoPluginConfig[]) {
const config = await getCurConfigFormData()
const configId = $route.params.configId
Object.assign(ruleForm, config)
if (val.length > 0) {
configList.value = cloneDeep(val).map((item) => {
if (!configId) return item
let defaultValue = item.default !== undefined
? item.default
: item.type === 'checkbox'
? []
: null
if (item.type === 'checkbox') {
const defaults = item.choices?.filter((i: any) => {
return i.checked
}).map((i: any) => i.value) || []
defaultValue = union(defaultValue, defaults)
}
if (config && config[item.name] !== undefined) {
defaultValue = config[item.name]
}
ruleForm[item.name] = defaultValue
return item
})
}
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 curTypeConfigList = await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`) || []
return curTypeConfigList.find(i => i._id === 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
getConfigType,
resetConfig
})
</script>
<style lang='stylus'>
.config-form-common-tips
a
color #409EFF
text-decoration none
#config-form
.el-form
label
@@ -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,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>
@@ -2,7 +2,7 @@
<el-form-item>
<template #label>
<el-row align="middle">
{{ props.settingText }}
{{ props.label }}
<template v-if="props.tooltips">
<el-tooltip
class="item"
@@ -10,7 +10,7 @@
:content="props.tooltips"
placement="right"
>
<el-icon style="margin-left: 4px">
<el-icon class="ml-[4px] cursor-pointer hover:text-blue">
<QuestionFilled />
</el-icon>
</el-tooltip>
@@ -28,37 +28,35 @@
<script lang="ts" setup>
import { T as $T } from '@/i18n'
import { saveConfig } from '@/utils/dataSender'
import { ref, watch } from 'vue'
import { QuestionFilled } from '@element-plus/icons-vue'
import { showNotification } from '@/utils/common'
import { useVModel } from '@/hooks/useVModel'
interface IProps {
tooltips?: string
settingProps: string
settingText: string
defaultValue: boolean
settingProps: keyof ISettingForm
label: string
modelValue: boolean
}
const props = defineProps<IProps>()
// to fix v-model blank bug
watch(() => props.defaultValue, (v) => {
value.value = v
})
const emit = defineEmits(['update:modelValue', 'change'])
const value = ref<boolean>(props.defaultValue)
const emit = defineEmits(['change'])
const value = useVModel(props, 'modelValue')
const handleChange = (val: ICheckBoxValueType) => {
saveConfig(props.settingProps, val)
emit('change', val)
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: 'CheckboxFormItem'
name: 'SwitchFormItem'
}
</script>
<style lang='stylus'>
</style>
+19
View File
@@ -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)
})
}
+36
View File
@@ -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 []
}
}
+16
View File
@@ -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)
}
})
}
+29
View File
@@ -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
}
+3 -3
View File
@@ -214,7 +214,7 @@ import {
Close
} from '@element-plus/icons-vue'
import { ElMessage as $message } from 'element-plus'
import { T } from '@/i18n/index'
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'
@@ -226,7 +226,7 @@ import {
IpcRendererEvent,
clipboard
} from 'electron'
import InputBoxDialog from '@/components/InputBoxDialog.vue'
import InputBoxDialog from '@/components/dialog/InputBoxDialog.vue'
import {
MINIMIZE_WINDOW,
CLOSE_WINDOW,
@@ -324,7 +324,7 @@ function openMiniWindow () {
function handleCopyPicBedConfig () {
clipboard.writeText(picBedConfigString.value)
$message.success(T('COPY_PICBED_CONFIG_SUCCEED'))
$message.success($T('COPY_PICBED_CONFIG_SUCCEED'))
}
function getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
+55 -108
View File
@@ -23,7 +23,7 @@
>
<el-col :span="12">
<el-select
v-model="choosedPicBed"
v-model="selectedPicBed"
multiple
collapse-tags
size="small"
@@ -76,33 +76,15 @@
</template>
</el-input>
</el-col>
<el-col :span="4">
<div
class="item-base copy round"
:class="{ active: isMultiple(choosedList)}"
@click="multiCopy"
>
{{ $T('COPY') }}
</div>
</el-col>
<el-col :span="4">
<div
class="item-base delete round"
:class="{ active: isMultiple(choosedList)}"
@click="multiRemove"
>
{{ $T('DELETE') }}
</div>
</el-col>
<el-col :span="4">
<div
class="item-base all-pick round"
:class="{ active: filterList.length > 0}"
@click="toggleSelectAll"
>
{{ isAllSelected ? $T('CANCEL') : $T('SELECT_ALL') }}
</div>
</el-col>
<GalleryToolbar
:selected-list="selectedList"
:filter-list="filterList"
:is-all-selected="isAllSelected"
@multi-copy="multiCopy"
@multi-remove="multiRemove"
@toggle-select-all="toggleSelectAll"
@select-more="selectMore"
/>
</el-row>
</el-col>
</el-row>
@@ -172,7 +154,7 @@
</el-icon>
</el-row>
<el-checkbox
v-model="choosedList[item.id ? item.id : '']"
v-model="selectedList[item.id ? item.id : '']"
@change="(val) => handleChooseImage(val, index)"
/>
</el-row>
@@ -214,10 +196,12 @@ import {
IpcRendererEvent
} from 'electron'
import { computed, nextTick, onActivated, onBeforeUnmount, onBeforeMount, reactive, ref, watch } from 'vue'
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
import { getConfig, saveConfig, sendRPC, sendToMain } from '@/utils/dataSender'
import { onBeforeRouteUpdate } from 'vue-router'
import { T as $T } from '@/i18n/index'
import $$db from '@/utils/db'
import GalleryToolbar from './components/gallery/GalleryToolbar.vue'
import { IRPCActionType } from '~/universal/types/enum'
const images = ref<ImgInfo[]>([])
const dialogVisible = ref(false)
const imgInfo = reactive({
@@ -225,13 +209,13 @@ const imgInfo = reactive({
imgUrl: ''
})
const $confirm = ElMessageBox.confirm
const choosedList: IObjT<boolean> = reactive({})
const selectedList: IObjT<boolean> = reactive({})
const gallerySliderControl = reactive({
visible: false,
index: 0
})
const choosedPicBed = ref<string[]>([])
const lastChoosed = ref<number>(-1)
const selectedPicBed = ref<string[]>([])
const lastSelected = ref<number>(-1)
const isShiftKeyPress = ref<boolean>(false)
const searchText = ref<string>('')
const handleBarActive = ref<boolean>(false)
@@ -246,7 +230,7 @@ const pasteStyleMap = {
const picBed = ref<IPicBedType[]>([])
onBeforeRouteUpdate((to, from) => {
if (from.name === 'gallery') {
clearChoosedList()
clearSelectedList()
}
if (to.name === 'gallery') {
updateGallery()
@@ -254,7 +238,7 @@ onBeforeRouteUpdate((to, from) => {
})
onBeforeMount(async () => {
ipcRenderer.on('updateGallery', () => {
ipcRenderer.on(IRPCActionType.UPDATE_GALLERY, () => {
nextTick(async () => {
updateGallery()
})
@@ -282,12 +266,12 @@ const filterList = computed(() => {
})
const isAllSelected = computed(() => {
const values = Object.values(choosedList)
const values = Object.values(selectedList)
if (values.length === 0) {
return false
} else {
return filterList.value.every(item => {
return choosedList[item.id!]
return selectedList[item.id!]
})
}
})
@@ -297,18 +281,18 @@ function getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
}
function getGallery (): IGalleryItem[] {
if (searchText.value || choosedPicBed.value.length > 0) {
if (searchText.value || selectedPicBed.value.length > 0) {
return images.value
.filter(item => {
let isInChoosedPicBed = true
let isInSelectedPicBed = true
let isIncludesSearchText = true
if (choosedPicBed.value.length > 0) {
isInChoosedPicBed = choosedPicBed.value.some(type => type === item.type)
if (selectedPicBed.value.length > 0) {
isInSelectedPicBed = selectedPicBed.value.some(type => type === item.type)
}
if (searchText.value) {
isIncludesSearchText = item.fileName?.includes(searchText.value) || false
}
return isIncludesSearchText && isInChoosedPicBed
return isIncludesSearchText && isInSelectedPicBed
}).map(item => {
return {
...item,
@@ -334,30 +318,30 @@ async function updateGallery () {
}
watch(() => filterList, () => {
clearChoosedList()
clearSelectedList()
})
function handleChooseImage (val: CheckboxValueType, index: number) {
if (val === true) {
handleBarActive.value = true
if (lastChoosed.value !== -1 && isShiftKeyPress.value) {
const min = Math.min(lastChoosed.value, index)
const max = Math.max(lastChoosed.value, index)
if (lastSelected.value !== -1 && isShiftKeyPress.value) {
const min = Math.min(lastSelected.value, index)
const max = Math.max(lastSelected.value, index)
for (let i = min + 1; i < max; i++) {
const id = filterList.value[i].id!
choosedList[id] = true
selectedList[id] = true
}
}
lastChoosed.value = index
lastSelected.value = index
}
}
function clearChoosedList () {
function clearSelectedList () {
isShiftKeyPress.value = false
Object.keys(choosedList).forEach(key => {
choosedList[key] = false
Object.keys(selectedList).forEach(key => {
selectedList[key] = false
})
lastChoosed.value = -1
lastSelected.value = -1
}
function zoomImage (index: number) {
@@ -445,33 +429,26 @@ async function confirmModify () {
updateGallery()
}
// function choosePicBed (type: string) {
// const idx = choosedPicBed.value.indexOf(type)
// if (idx !== -1) {
// choosedPicBed.value.splice(idx, 1)
// } else {
// choosedPicBed.value.push(type)
// }
// }
function cleanSearch () {
searchText.value = ''
}
function isMultiple (obj: IObj) {
return Object.values(obj).some(item => item)
}
function toggleSelectAll () {
const result = !isAllSelected.value
filterList.value.forEach(item => {
choosedList[item.id!] = result
selectedList[item.id!] = result
})
}
function selectMore () {
sendRPC(IRPCActionType.GET_GALLERY_MENU_LIST, filterList.value.filter(item => {
return selectedList[item.id!]
}))
}
function multiRemove () {
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
const multiRemoveNumber = Object.values(choosedList).filter(item => item).length
// selectedList -> { [id]: true or false }; true means selected. false means not selected.
const multiRemoveNumber = Object.values(selectedList).filter(item => item).length
if (multiRemoveNumber) {
$confirm($T('TIPS_WILL_REMOVE_CHOOSED_IMAGES', {
m: multiRemoveNumber
@@ -481,10 +458,10 @@ function multiRemove () {
type: 'warning'
}).then(async () => {
const files: IResult<ImgInfo>[] = []
const imageIDList = Object.keys(choosedList)
const imageIDList = Object.keys(selectedList)
for (let i = 0; i < imageIDList.length; i++) {
const key = imageIDList[i]
if (choosedList[key]) {
if (selectedList[key]) {
const file = await $$db.getById<ImgInfo>(key)
if (file) {
files.push(file)
@@ -492,9 +469,9 @@ function multiRemove () {
}
}
}
clearChoosedList()
clearSelectedList()
// TODO: check this
// choosedList = {} // 只有删除才能将这个置空
// selectedList = {} // 只有删除才能将这个置空
const obj = {
title: $T('OPERATION_SUCCEED'),
body: ''
@@ -512,18 +489,18 @@ function multiRemove () {
}
async function multiCopy () {
if (Object.values(choosedList).some(item => item)) {
if (Object.values(selectedList).some(item => item)) {
const copyString: string[] = []
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
const imageIDList = Object.keys(choosedList)
// selectedList -> { [id]: true or false }; true means selected. false means not selected.
const imageIDList = Object.keys(selectedList)
for (let i = 0; i < imageIDList.length; i++) {
const key = imageIDList[i]
if (choosedList[key]) {
if (selectedList[key]) {
const item = await $$db.getById<ImgInfo>(key)
if (item) {
const txt = await ipcRenderer.invoke(PASTE_TEXT, item)
copyString.push(txt)
choosedList[key] = false
selectedList[key] = false
}
}
}
@@ -586,36 +563,6 @@ export default {
height 100%
.cursor-pointer
cursor pointer
.item-base
background #2E2E2E
text-align center
padding 5px 0
cursor pointer
font-size 13px
transition all .2s ease-in-out
height: 28px
box-sizing: border-box
&.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
#gallery-view
position relative
.round
@@ -659,7 +606,7 @@ export default {
transform scale(1.1)
&-img
width 100%
object-fit fill
object-fit contain
&__tool-panel
color #ddd
margin-bottom 4px
+45 -860
View File
@@ -24,553 +24,48 @@
label-width="50%"
size="small"
>
<el-form-item
:label="$T('SETTINGS_CHOOSE_LANGUAGE')"
>
<!-- <el-button type="primary" round size="small" @click="openFile('data.json')">{{ $T('SETTINGS_CLICK_TO_OPEN') }}</el-button> -->
<el-select
v-model="currentLanguage"
size="small"
style="width: 100%"
:placeholder="$T('SETTINGS_CHOOSE_LANGUAGE')"
@change="handleLanguageChange"
>
<el-option
v-for="item in languageList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_OPEN_CONFIG_FILE')"
>
<el-button
type="primary"
round
size="small"
@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="small"
@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="small"
@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="small"
@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="small"
@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="small"
@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="small"
@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-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-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-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-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-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-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-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-form-item>
<el-form-item>
<template #label>
<el-row align="middle">
{{ $T('SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD') }}
<el-tooltip
class="item"
effect="dark"
:content="$T('BUILTIN_CLIPBOARD_TIPS')"
placement="right"
>
<el-icon style="margin-left: 4px">
<QuestionFilled />
</el-icon>
</el-tooltip>
</el-row>
</template>
<el-switch
v-model="form.useBuiltinClipboard"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="useBuiltinClipboardChange"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_ENCODE_OUTPUT_URL')"
>
<el-switch
v-model="form.encodeOutputURL"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="handleEncodeOutputURL"
/>
</el-form-item>
<checkbox-form-item
v-if="os === 'darwin'"
:default-value="form.showDockIcon"
setting-props="settings.showDockIcon"
:setting-text="$T('SETTINGS_SHOW_DOCK_ICON')"
@change="handleShowDockIcon"
<SelectAreaSettings
:settings="form"
/>
<ButtonAreaSettings
v-model:proxy="proxy"
:settings="form"
/>
<SwitchAreaSettings
:settings="form"
/>
<CustomAreaSettings
:settings="form"
/>
<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"
:key="item.name"
:label="item.name"
/>
</el-checkbox-group>
</el-form-item>
</el-form>
</el-row>
</el-col>
</el-row>
<el-dialog
v-model="customLinkVisible"
:title="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
:modal-append-to-body="false"
>
<el-form
ref="$customLink"
label-position="top"
:model="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
v-model="customLink.value"
class="align-center"
:autofocus="true"
/>
</el-form-item>
</el-form>
<div>
{{ $T('SETTINGS_TIPS_SUCH_AS') }}[$fileName]($url)
</div>
<template #footer>
<el-button
round
@click="cancelCustomLink"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmCustomLink"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="proxyVisible"
:title="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
:modal-append-to-body="false"
width="70%"
>
<el-form
label-position="right"
:model="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-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-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-form-item>
</el-form>
<template #footer>
<el-button
round
@click="cancelProxy"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmProxy"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="checkUpdateVisible"
:title="$T('SETTINGS_CHECK_UPDATE')"
: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>
<template #footer>
<el-button
round
@click="cancelCheckVersion"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmCheckVersion"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="logFileVisible"
:title="$T('SETTINGS_SET_LOG_FILE')"
: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="small"
@click="openFile('picgo.log')"
>
{{ $T('SETTINGS_CLICK_TO_OPEN') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_LOG_LEVEL')"
>
<el-select
v-model="form.logLevel"
multiple
collapse-tags
style="width: 100%;"
>
<el-option
v-for="(value, key) of logLevel"
:key="key"
:label="value"
:value="key"
:disabled="handleLevelDisabled(key)"
/>
</el-select>
</el-form-item>
<el-form-item
:label="`${$T('SETTINGS_LOG_FILE_SIZE')} (MB)`"
>
<el-input-number
v-model="form.logFileSizeLimit"
style="width: 100%;"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}10`"
:controls="false"
:min="1"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button
round
@click="cancelLogLevelSetting"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmLogLevelSetting"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="serverVisible"
class="server-dialog"
width="60%"
:title="$T('SETTINGS_SET_PICGO_SERVER')"
: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-form-item>
<template v-if="server.enable">
<el-form-item
:label="$T('SETTINGS_SET_SERVER_HOST')"
>
<el-input
v-model="server.host"
type="input"
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_HOST')"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_SET_SERVER_PORT')"
>
<el-input
v-model="server.port"
type="number"
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_PORT')"
/>
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button
round
@click="cancelServerSetting"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmServerSetting"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ElForm, ElMessage as $message, FormRules } from 'element-plus'
import { Reading, QuestionFilled } from '@element-plus/icons-vue'
import pkg from 'root/package.json'
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 { i18nManager, T as $T } from '@/i18n/index'
import { enforceNumber } from '~/universal/utils/common'
import { compare } from 'compare-versions'
import { STABLE_RELEASE_URL, BETA_RELEASE_URL } from '#/utils/static'
import { computed, onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue'
import { getConfig, saveConfig, sendRPC, sendToMain, triggerRPC } from '@/utils/dataSender'
import { useRouter } from 'vue-router'
import { SHORTKEY_PAGE } from '@/router/config'
import { IRPCActionType } from '~/universal/types/enum'
import CheckboxFormItem from '@/components/settings/CheckboxFormItem.vue'
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'
const $customLink = ref<InstanceType<typeof ElForm> | null>(null)
const customLinkRule = (rule: any, value: string, callback: (arg0?: Error) => void) => {
if (!/\$url/.test(value) && !/\$fileName/.test(value) && !/\$extName/.test(value)) {
return callback(new Error($T('TIPS_MUST_CONTAINS_URL')))
} else {
return callback()
}
}
const $router = useRouter()
const form = reactive<ISettingForm>({
updateHelper: false,
showUpdateTip: false,
showPicBedList: [],
autoStart: false,
rename: false,
autoRename: false,
uploadNotification: false,
miniWindowOntop: false,
miniWindowOnTop: false,
logLevel: ['all'],
autoCopyUrl: true,
checkBetaUpdate: true,
@@ -578,72 +73,21 @@ const form = reactive<ISettingForm>({
language: 'zh-CN',
logFileSizeLimit: 10,
encodeOutputURL: true,
showDockIcon: true
})
const languageList = i18nManager.languageList.map(item => ({
label: item.label,
value: item.value
}))
const currentLanguage = ref('zh-CN')
const picBed = ref<IPicBedType[]>([])
const logFileVisible = ref(false)
const customLinkVisible = ref(false)
const checkUpdateVisible = ref(false)
const serverVisible = ref(false)
const proxyVisible = ref(false)
const customLink = reactive({
value: '$url'
})
const shortKey = reactive<IShortKeyMap>({
upload: ''
showDockIcon: true,
customLink: '$url',
npmProxy: '',
npmRegistry: '',
server: {
port: 36677,
host: '127.0.0.1',
enable: true
},
startupMode: IStartupMode.HIDE
})
const proxy = ref('')
const npmRegistry = ref('')
const npmProxy = ref('')
const rules = reactive<FormRules>({
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
})
const logLevel = {
all: $T('SETTINGS_LOG_LEVEL_ALL'),
success: $T('SETTINGS_LOG_LEVEL_SUCCESS'),
error: $T('SETTINGS_LOG_LEVEL_ERROR'),
info: $T('SETTINGS_LOG_LEVEL_INFO'),
warn: $T('SETTINGS_LOG_LEVEL_WARN'),
none: $T('SETTINGS_LOG_LEVEL_NONE')
}
const server = ref({
port: 36677,
host: '127.0.0.1',
enable: true
})
const version = pkg.version
const latestVersion = ref('')
const os = ref('')
const needUpdate = computed(() => {
if (latestVersion.value) {
return compareVersion2Update(version, latestVersion.value)
} else {
return false
}
})
onBeforeMount(() => {
os.value = process.platform
sendToMain(GET_PICBEDS)
ipcRenderer.on(GET_PICBEDS, getPicBeds)
initData()
})
@@ -652,31 +96,26 @@ async function initData () {
if (config !== undefined) {
const settings = config.settings || {}
const picBed = config.picBed
form.updateHelper = settings.showUpdateTip || false
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.miniWindowOnTop = settings.miniWindowOnTop || false
form.logLevel = initLogLevel(settings.logLevel || [])
form.autoCopyUrl = settings.autoCopy === undefined ? true : settings.autoCopy
form.checkBetaUpdate = settings.checkBetaUpdate === undefined ? true : settings.checkBetaUpdate
form.useBuiltinClipboard = settings.useBuiltinClipboard === undefined ? false : settings.useBuiltinClipboard
form.language = settings.language ?? 'zh-CN'
form.encodeOutputURL = settings.encodeOutputURL === undefined ? true : settings.encodeOutputURL
currentLanguage.value = settings.language ?? 'zh-CN'
customLink.value = settings.customLink || '$url'
shortKey.upload = settings.shortKey.upload
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 || ''
npmRegistry.value = settings.registry || ''
npmProxy.value = settings.proxy || ''
server.value = settings.server || {
port: 36677,
host: '127.0.0.1',
enable: true
}
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)
}
}
@@ -691,264 +130,10 @@ function initLogLevel (logLevel: string | string[]) {
return logLevel
}
function getPicBeds (event: Event, picBeds: IPicBedType[]) {
picBed.value = picBeds
form.showPicBedList = picBed.value.map(item => {
if (item.visible) {
return item.name
}
return null
}).filter(item => item) as string[]
}
function openFile (file: string) {
sendToMain(PICGO_OPEN_FILE, file)
}
function openLogSetting () {
logFileVisible.value = true
}
async function cancelCustomLink () {
customLinkVisible.value = false
customLink.value = await getConfig<string>('settings.customLink') || '$url'
}
function confirmCustomLink () {
$customLink.value?.validate((valid: boolean) => {
if (valid) {
saveConfig('settings.customLink', customLink.value)
customLinkVisible.value = false
sendToMain('updateCustomLink')
} else {
return false
}
})
}
async function cancelProxy () {
proxyVisible.value = false
proxy.value = await getConfig<string>('picBed.proxy') || ''
}
function confirmProxy () {
proxyVisible.value = false
saveConfig({
'picBed.proxy': proxy.value,
'settings.proxy': npmProxy.value,
'settings.registry': npmRegistry.value
})
const successNotification = new Notification($T('SETTINGS_SET_PROXY_AND_MIRROR'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
}
function updateHelperChange (val: ICheckBoxValueType) {
saveConfig('settings.showUpdateTip', val)
}
function checkBetaUpdateChange (val: ICheckBoxValueType) {
saveConfig('settings.checkBetaUpdate', val)
}
function useBuiltinClipboardChange (val: ICheckBoxValueType) {
saveConfig('settings.useBuiltinClipboard', val)
}
function handleShowPicBedListChange (val: ICheckBoxValueType[]) {
const list = picBed.value.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else {
item.visible = true
}
return item
})
saveConfig({
'picBed.list': list
})
sendToMain(GET_PICBEDS)
}
function handleAutoStartChange (val: ICheckBoxValueType) {
saveConfig('settings.autoStart', val)
sendToMain('autoStart', val)
}
function handleRename (val: ICheckBoxValueType) {
saveConfig({
'settings.rename': val
})
}
function handleAutoRename (val: ICheckBoxValueType) {
saveConfig({
'settings.autoRename': val
})
}
function compareVersion2Update (current: string, latest: string): boolean {
return compare(current, latest, '<')
}
async function checkUpdate () {
checkUpdateVisible.value = true
const version = await triggerRPC<string>(IRPCActionType.GET_LATEST_VERSION, form.checkBetaUpdate)
if (version) {
latestVersion.value = version
} else {
latestVersion.value = $T('TIPS_NETWORK_ERROR')
}
}
function confirmCheckVersion () {
if (needUpdate.value) {
sendToMain(OPEN_URL, form.checkBetaUpdate ? BETA_RELEASE_URL : STABLE_RELEASE_URL)
}
checkUpdateVisible.value = false
}
function cancelCheckVersion () {
checkUpdateVisible.value = false
}
function handleUploadNotification (val: ICheckBoxValueType) {
saveConfig({
'settings.uploadNotification': val
})
}
function handleMiniWindowOntop (val: ICheckBoxValueType) {
saveConfig('settings.miniWindowOntop', val)
$message.info($T('TIPS_NEED_RELOAD'))
}
function handleAutoCopyUrl (val: ICheckBoxValueType) {
saveConfig('settings.autoCopy', val)
const successNotification = new Notification($T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
}
function handleEncodeOutputURL (val: ICheckBoxValueType) {
saveConfig('settings.encodeOutputURL', val)
const successNotification = new Notification($T('SETTINGS_ENCODE_OUTPUT_URL'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
}
function confirmLogLevelSetting () {
if (form.logLevel.length === 0) {
return $message.error($T('TIPS_PLEASE_CHOOSE_LOG_LEVEL'))
}
saveConfig({
'settings.logLevel': form.logLevel,
'settings.logFileSizeLimit': form.logFileSizeLimit
})
const successNotification = new Notification($T('SETTINGS_SET_LOG_FILE'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
logFileVisible.value = false
}
async function cancelLogLevelSetting () {
logFileVisible.value = false
let logLevel = await getConfig<string | string[]>('settings.logLevel')
const logFileSizeLimit = await getConfig<number>('settings.logFileSizeLimit') || 10
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
form.logLevel = logLevel
form.logFileSizeLimit = logFileSizeLimit
}
function confirmServerSetting () {
server.value.port = parseInt(server.value.port as unknown as string, 10)
saveConfig({
'settings.server': server.value
})
const successNotification = new Notification($T('SETTINGS_SET_PICGO_SERVER'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
serverVisible.value = false
sendToMain('updateServer')
}
async function cancelServerSetting () {
serverVisible.value = false
server.value = await getConfig('settings.server') || {
port: 36677,
host: '127.0.0.1',
enable: true
}
}
function handleLevelDisabled (val: string) {
const currentLevel = val
let flagLevel
const result = 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 (form.logLevel.length > 0) {
if (val === 'all' || val === 'none') {
return true
}
}
return false
}
function handleLanguageChange (val: string) {
i18nManager.setCurrentLanguage(val)
saveConfig({
'settings.language': val
})
sendToMain(GET_PICBEDS)
}
function handleShowDockIcon (val: ICheckBoxValueType) {
sendRPC(IRPCActionType.SHOW_DOCK_ICON, val)
}
function goConfigPage () {
sendToMain(OPEN_URL, 'https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
openURL('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
}
function goShortCutPage () {
$router.push({
name: SHORTKEY_PAGE
})
}
onBeforeUnmount(() => {
ipcRenderer.removeListener(GET_PICBEDS, getPicBeds)
})
</script>
<script lang="ts">
export default {
+6 -1
View File
@@ -179,7 +179,7 @@
<template #footer>
<el-button
round
@click="dialogVisible = false"
@click="handleCancelConfig"
>
{{ $T('CANCEL') }}
</el-button>
@@ -450,6 +450,11 @@ async function handleConfirmConfig () {
}
}
function handleCancelConfig () {
dialogVisible.value = false
$configForm.value?.resetConfig()
}
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}`)
+52 -22
View File
@@ -1,20 +1,37 @@
<template>
<div id="rename-page">
<div class="p-[20px] flex flex-col justify-between">
<el-form
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"
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">
<div class="w-full flex justify-end mt-[20px]">
<el-button
round
size="small"
@@ -35,35 +52,52 @@
</div>
</template>
<script lang="ts" setup>
import { RENAME_FILE_NAME } from '#/events/constants'
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'
import { onBeforeUnmount, onBeforeMount, ref } from 'vue'
const fileName = ref('')
const originName = ref('')
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: ''
})
const handleFileName = (event: IpcRendererEvent, newName: string, _originName: string, _id: string) => {
form.fileName = newName
form.originName = _originName
id.value = _id
}
useIPCOn(RENAME_FILE_NAME, handleFileName)
onBeforeMount(() => {
ipcRenderer.on(RENAME_FILE_NAME, (event: IpcRendererEvent, newName: string, _originName: string, _id: string) => {
fileName.value = newName
originName.value = _originName
id.value = _id
})
ipcRenderer.send(GET_RENAME_FILE_NAME)
})
function confirmName () {
sendToMain(`${RENAME_FILE_NAME}${id.value}`, fileName.value)
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}`, originName.value)
sendToMain(`${RENAME_FILE_NAME}${id.value}`, form.originName)
}
onBeforeUnmount(() => {
ipcRenderer.removeAllListeners('rename')
ipcRenderer.removeAllListeners(RENAME_FILE_NAME)
})
</script>
@@ -73,10 +107,6 @@ export default {
}
</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>
+1
View File
@@ -119,6 +119,7 @@ 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'
const list = ref<IShortKeyConfig[]>([])
const keyBindingVisible = ref(false)
+17 -14
View File
@@ -5,8 +5,8 @@
:span="20"
:offset="2"
>
<div class="view-title">
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }}
<div class="view-title text-[22px]">
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }} - {{ configName }}
<el-icon
style="cursor: pointer; margin-left: 4px;"
@click="handleChangePicBed"
@@ -23,7 +23,7 @@
>
<div
id="upload-dragger"
@click="openUplodWindow"
@click="openUploadWindow"
>
<el-icon>
<UploadFilled />
@@ -100,25 +100,25 @@
</template>
<script lang="ts" setup>
// import { Component, Vue, Watch } from 'vue-property-decorator'
import { UploadFilled, CaretBottom } from '@element-plus/icons-vue'
import {
ipcRenderer,
IpcRendererEvent
} from 'electron'
import { ref, onBeforeMount, onBeforeUnmount, watch } from 'vue'
import { T as $T } from '@/i18n'
import $bus from '@/utils/bus'
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
import { CaretBottom, UploadFilled } from '@element-plus/icons-vue'
import {
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'
import { ElMessage as $message } from 'element-plus'
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
const dragover = ref(false)
const progress = ref(0)
const showProgress = ref(false)
@@ -126,6 +126,7 @@ 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) {
@@ -205,7 +206,7 @@ function handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer)
}
}
function openUplodWindow () {
function openUploadWindow () {
document.getElementById('file-uploader')!.click()
}
@@ -262,11 +263,13 @@ function handleInputBoxValue (val: string) {
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[]) {
+8 -4
View File
@@ -1,5 +1,8 @@
<template>
<div id="config-list-view">
<div
id="config-list-view"
class="h-[425px]"
>
<div class="view-title">
{{ $T('SETTINGS') }}
</div>
@@ -193,7 +196,7 @@ export default {
flex-wrap wrap
width: 98%
.config-item
height 85px
height 100px
margin-bottom 20px
border-radius 4px
cursor pointer
@@ -239,9 +242,10 @@ export default {
.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>
@@ -0,0 +1,113 @@
<template>
<ButtonFormItem
:label="$T('SETTINGS_OPEN_CONFIG_FILE')"
:button-label="$T('SETTINGS_CLICK_TO_OPEN')"
@click="openFile('data.json')"
/>
<ButtonFormItem
:label="$T('SETTINGS_SET_LOG_FILE')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="logFileDialogVisible = true"
/>
<ButtonFormItem
:label="$T('SETTINGS_SET_SHORTCUT')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="goShortCutPage"
/>
<ButtonFormItem
:label="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="customLinkDialogVisible = true"
/>
<ButtonFormItem
:label="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="proxySettingDialogVisible = true"
/>
<ButtonFormItem
:label="$T('SETTINGS_SET_SERVER')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="serverSettingDialogVisible = true"
/>
<ButtonFormItem
:label="$T('SETTINGS_CHECK_UPDATE')"
:button-label="$T('SETTINGS_CLICK_TO_CHECK')"
@click="checkUpdateDialogVisible = true"
/>
<LogSettingDialog
v-model="logFileDialogVisible"
v-model:log-level="form.logLevel"
v-model:log-file-size-limit="form.logFileSizeLimit"
/>
<CustomLinkDialog
v-model="customLinkDialogVisible"
v-model:custom-link="form.customLink"
/>
<ProxySettingDialog
v-model="proxySettingDialogVisible"
v-model:proxy="proxyString"
v-model:npm-proxy="form.npmProxy"
v-model:npm-registry="form.npmRegistry"
/>
<ServerSettingsDialog
v-model="serverSettingDialogVisible"
v-model:host="form.server.host"
v-model:port="form.server.port"
v-model:enable="form.server.enable"
/>
<CheckUpdateDialog
v-model="checkUpdateDialogVisible"
v-model:checkBetaUpdate="form.checkBetaUpdate"
/>
</template>
<script lang="ts" setup>
import ButtonFormItem from '@/components/settings/ButtonFormItem.vue'
import CustomLinkDialog from './CustomLinkDialog.vue'
import ProxySettingDialog from './ProxySettingDialog.vue'
import ServerSettingsDialog from './ServerSettingsDialog.vue'
import CheckUpdateDialog from './CheckUpdateDialog.vue'
import { T as $T } from '@/i18n'
import { sendToMain } from '@/utils/dataSender'
import { reactive, ref } from 'vue'
import { PICGO_OPEN_FILE } from '~/universal/events/constants'
import LogSettingDialog from './LogSettingDialog.vue'
import { useRouter } from 'vue-router'
import { SHORTKEY_PAGE } from '@/router/config'
import { useVModel } from '@/hooks/useVModel'
const $router = useRouter()
interface IProps {
settings: ISettingForm
proxy: string
}
const props = defineProps<IProps>()
const form = reactive(props.settings)
const proxyString = useVModel(props, 'proxy')
const logFileDialogVisible = ref(false)
const customLinkDialogVisible = ref(false)
const proxySettingDialogVisible = ref(false)
const serverSettingDialogVisible = ref(false)
const checkUpdateDialogVisible = ref(false)
function openFile (file: string) {
sendToMain(PICGO_OPEN_FILE, file)
}
function goShortCutPage () {
$router.push({
name: SHORTKEY_PAGE
})
}
</script>
<script lang="ts">
export default {
name: 'ButtonAreaSettings'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,105 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="$T('SETTINGS_CHECK_UPDATE')"
:append-to-body="true"
>
<div>
{{ $T('SETTINGS_CURRENT_VERSION') }}: {{ version }}
</div>
<div>
{{ $T('SETTINGS_NEWEST_VERSION') }}: {{ latestVersion ? latestVersion : `${$T('SETTINGS_GETING')}...` }}
</div>
<div v-if="needUpdate">
{{ $T('SETTINGS_TIPS_HAS_NEW_VERSION') }}
</div>
<template #footer>
<el-button
size="default"
round
@click="cancelCheckVersion"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmCheckVersion"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import { T as $T } from '@/i18n'
import { STABLE_RELEASE_URL, BETA_RELEASE_URL } from '#/utils/static'
import pkg from 'root/package.json'
import { compare } from 'compare-versions'
import { IRPCActionType } from '~/universal/types/enum'
import { useVModel } from '@/hooks/useVModel'
import { triggerRPC } from '@/utils/dataSender'
import { openURL } from '@/utils/common'
interface IProps {
modelValue: boolean
checkBetaUpdate: boolean
}
const props = defineProps<IProps>()
const dialogVisible = useVModel(props, 'modelValue')
const checkBetaUpdate = useVModel(props, 'checkBetaUpdate')
const version = pkg.version
const latestVersion = ref('')
function compareVersion2Update (current: string, latest: string): boolean {
return compare(current, latest, '<')
}
const needUpdate = computed(() => {
if (latestVersion.value) {
return compareVersion2Update(version, latestVersion.value)
} else {
return false
}
})
watch(() => dialogVisible.value, (value) => {
if (value) {
checkUpdate()
}
})
async function checkUpdate () {
const version = await triggerRPC<string>(IRPCActionType.GET_LATEST_VERSION, checkBetaUpdate.value)
if (version) {
latestVersion.value = version
} else {
latestVersion.value = $T('TIPS_NETWORK_ERROR')
}
}
function confirmCheckVersion () {
if (needUpdate.value) {
openURL(checkBetaUpdate.value ? BETA_RELEASE_URL : STABLE_RELEASE_URL)
}
dialogVisible.value = false
}
function cancelCheckVersion () {
latestVersion.value = ''
dialogVisible.value = false
}
</script>
<script lang="ts">
export default {
name: 'CheckUpdateDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,118 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
:append-to-body="true"
>
<el-form
ref="$customLink"
label-position="top"
:model="form"
:rules="rules"
size="small"
>
<el-form-item
prop="customLink"
>
<div class="flex flex-col mb-[8px]">
<div>
{{ $T('SETTINGS_TIPS_PLACEHOLDER_URL') }}
</div>
<div>
{{ $T('SETTINGS_TIPS_PLACEHOLDER_FILENAME') }}
</div>
<div>
{{ $T('SETTINGS_TIPS_PLACEHOLDER_EXTNAME') }}
</div>
</div>
<el-input
v-model="form.customLink"
class="align-center"
:autofocus="true"
size="default"
/>
</el-form-item>
</el-form>
<div>
{{ $T('SETTINGS_TIPS_SUCH_AS') }}[$fileName]($url)
</div>
<template #footer>
<el-button
size="default"
round
@click="cancelCustomLink"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmCustomLink"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { T as $T } from '@/i18n'
import { useVModel } from '@/hooks/useVModel'
import { saveConfig, sendToMain } from '@/utils/dataSender'
import { ElForm, FormRules } from 'element-plus'
import { useVModelValues } from '@/hooks/useVModelValues'
interface IProps {
modelValue: boolean
customLink: string
}
const props = defineProps<IProps>()
const [form, updateProps] = useVModelValues<IProps>(props, ['customLink'])
const $customLink = ref<InstanceType<typeof ElForm> | null>(null)
const dialogVisible = useVModel(props, 'modelValue')
const customLinkRule = (rule: any, value: string, callback: (arg0?: Error) => void) => {
if (!/\$url/.test(value) && !/\$fileName/.test(value) && !/\$extName/.test(value)) {
return callback(new Error($T('TIPS_MUST_CONTAINS_URL')))
} else {
return callback()
}
}
const rules = reactive<FormRules>({
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
})
function confirmCustomLink () {
$customLink.value?.validate((valid: boolean) => {
if (valid) {
saveConfig('settings.customLink', form.customLink)
dialogVisible.value = false
sendToMain('updateCustomLink')
updateProps()
} else {
return false
}
})
}
async function cancelCustomLink () {
dialogVisible.value = false
form.customLink = props.customLink
}
</script>
<script lang="ts">
export default {
name: 'CustomLinkDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,156 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="$T('SETTINGS_SET_LOG_FILE')"
:append-to-body="true"
width="500px"
>
<el-form
label-position="right"
label-width="150px"
>
<el-form-item
:label="$T('SETTINGS_LOG_FILE')"
>
<el-button
type="primary"
round
size="small"
@click="openFile('picgo.log')"
>
{{ $T('SETTINGS_CLICK_TO_OPEN') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_LOG_LEVEL')"
>
<el-select
v-model="form.logLevel"
multiple
collapse-tags
style="width: 100%;"
>
<el-option
v-for="(value, key) of logLevelMap"
:key="key"
:label="value"
:value="key"
:disabled="handleLevelDisabled(key)"
/>
</el-select>
</el-form-item>
<el-form-item
:label="`${$T('SETTINGS_LOG_FILE_SIZE')} (MB)`"
>
<el-input-number
v-model="form.logFileSizeLimit"
style="width: 100%;"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}10`"
:controls="false"
:min="1"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button
size="default"
round
@click="cancelLogLevelSetting"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmLogLevelSetting"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { T as $T } from '@/i18n'
import { saveConfig } from '@/utils/dataSender'
import { useVModel } from '@/hooks/useVModel'
import { openFile, showNotification } from '@/utils/common'
import { ElMessage as $message } from 'element-plus'
import { useVModelValues } from '@/hooks/useVModelValues'
const logLevelMap = {
all: $T('SETTINGS_LOG_LEVEL_ALL'),
success: $T('SETTINGS_LOG_LEVEL_SUCCESS'),
error: $T('SETTINGS_LOG_LEVEL_ERROR'),
info: $T('SETTINGS_LOG_LEVEL_INFO'),
warn: $T('SETTINGS_LOG_LEVEL_WARN'),
none: $T('SETTINGS_LOG_LEVEL_NONE')
}
interface IProps {
modelValue: boolean
logLevel: string[]
logFileSizeLimit: number
}
const props = defineProps<IProps>()
const dialogVisible = useVModel(props, 'modelValue')
const [form, updateProps] = useVModelValues<IProps>(props, ['logFileSizeLimit', 'logLevel'])
async function cancelLogLevelSetting () {
dialogVisible.value = false
let logLevel = props.logLevel as string | string[]
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
form.logLevel = logLevel
form.logFileSizeLimit = props.logFileSizeLimit
}
function confirmLogLevelSetting () {
if (form.logLevel.length === 0) {
return $message.error($T('TIPS_PLEASE_CHOOSE_LOG_LEVEL'))
}
saveConfig({
'settings.logLevel': form.logLevel,
'settings.logFileSizeLimit': form.logFileSizeLimit
})
showNotification($T('SETTINGS_SET_LOG_FILE'), $T('TIPS_SET_SUCCEED'))
updateProps()
dialogVisible.value = false
}
function handleLevelDisabled (val: string) {
const currentLevel = val
let flagLevel
const result = form.logLevel.some((item: any) => {
if (item === 'all' || item === 'none') {
flagLevel = item
}
return (item === 'all' || item === 'none')
})
if (result) {
if (currentLevel !== flagLevel) {
return true
}
} else if (form.logLevel.length > 0) {
if (val === 'all' || val === 'none') {
return true
}
}
return false
}
</script>
<script lang="ts">
export default {
name: 'LogSettingDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,103 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
:append-to-body="true"
width="70%"
>
<el-form
label-position="right"
label-width="120px"
>
<el-form-item
:label="$T('SETTINGS_UPLOAD_PROXY')"
>
<el-input
v-model="form.proxy"
:autofocus="true"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}http://127.0.0.1:1080`"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_PLUGIN_INSTALL_PROXY')"
>
<el-input
v-model="form.npmProxy"
:autofocus="true"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}http://127.0.0.1:1080`"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_PLUGIN_INSTALL_MIRROR')"
>
<el-input
v-model="form.npmRegistry"
:autofocus="true"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}https://registry.npmmirror.com`"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button
size="default"
round
@click="cancelProxy"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmProxy"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { T as $T } from '@/i18n'
import { useVModel } from '@/hooks/useVModel'
import { saveConfig } from '@/utils/dataSender'
import { useVModelValues } from '@/hooks/useVModelValues'
import { showNotification } from '@/utils/common'
interface IProps {
modelValue: boolean
proxy: string
npmProxy: string
npmRegistry: string
}
const props = defineProps<IProps>()
const [form, updateProps] = useVModelValues<IProps>(props, ['proxy', 'npmProxy', 'npmRegistry'])
const dialogVisible = useVModel(props, 'modelValue')
function confirmProxy () {
dialogVisible.value = false
saveConfig({
'picBed.proxy': form.proxy,
'settings.npmProxy': form.npmProxy,
'settings.npmRegistry': form.npmRegistry
})
showNotification($T('SETTINGS_SET_PROXY_AND_MIRROR'), $T('TIPS_SET_SUCCEED'))
updateProps()
}
async function cancelProxy () {
dialogVisible.value = false
form.proxy = props.proxy || ''
form.npmProxy = props.npmProxy || ''
form.npmRegistry = props.npmRegistry || ''
}
</script>
<script lang="ts">
export default {
name: 'ProxySettingDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,108 @@
<template>
<el-dialog
v-model="dialogVisible"
class="server-dialog"
width="60%"
:title="$T('SETTINGS_SET_PICGO_SERVER')"
:append-to-body="true"
>
<div class="notice-text">
{{ $T('SETTINGS_TIPS_SERVER_NOTICE') }}
</div>
<el-form
label-position="right"
label-width="120px"
>
<el-form-item
:label="$T('SETTINGS_ENABLE_SERVER')"
>
<el-switch
v-model="form.enable"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
/>
</el-form-item>
<template v-if="form.enable">
<el-form-item
:label="$T('SETTINGS_SET_SERVER_HOST')"
>
<el-input
v-model="form.host"
type="input"
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_HOST')"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_SET_SERVER_PORT')"
>
<el-input
v-model="form.port"
type="number"
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_PORT')"
/>
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button
size="default"
round
@click="cancelServerSetting"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmServerSetting"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { T as $T } from '@/i18n'
import { useVModel } from '@/hooks/useVModel'
import { useVModelValues } from '@/hooks/useVModelValues'
import { saveConfig, sendToMain } from '@/utils/dataSender'
import { showNotification } from '@/utils/common'
interface IProps {
modelValue: boolean
port: number
host: string
enable: boolean
}
const props = defineProps<IProps>()
const dialogVisible = useVModel(props, 'modelValue')
const [form, updateProps] = useVModelValues<IProps>(props, ['port', 'host', 'enable'])
function confirmServerSetting () {
form.port = parseInt(form.port as unknown as string, 10)
saveConfig({
'settings.server': form
})
showNotification($T('SETTINGS_SET_PICGO_SERVER'), $T('TIPS_SET_SUCCEED'))
dialogVisible.value = false
sendToMain('updateServer')
updateProps()
}
async function cancelServerSetting () {
dialogVisible.value = false
form.port = props.port
form.host = props.host
form.enable = props.enable
}
</script>
<script lang="ts">
export default {
name: 'ServerSettingsDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,71 @@
<template>
<el-form-item
:style="{ marginRight: '-64px' }"
:label="$T('CHOOSE_SHOWED_PICBED')"
>
<el-checkbox-group
v-model="showPicBedList"
@change="handleShowPicBedListChange"
>
<el-checkbox
v-for="item in picBed"
:key="item.name"
:label="item.name"
/>
</el-checkbox-group>
</el-form-item>
</template>
<script lang="ts" setup>
import { onBeforeMount, ref } from 'vue'
import { T as $T } from '@/i18n'
import { GET_PICBEDS } from '#/events/constants'
import { saveConfig, sendToMain } from '@/utils/dataSender'
import { useIPCOn } from '@/hooks/useIPC'
import { useVModel } from '@/hooks/useVModel'
interface IProps {
showPicBedList: string[]
}
const props = defineProps<IProps>()
const picBed = ref<IPicBedType[]>([])
const showPicBedList = useVModel(props, 'showPicBedList')
function handleShowPicBedListChange (val: ISwitchValueType[]) {
const list = picBed.value.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else {
item.visible = true
}
return item
})
saveConfig({
'picBed.list': list
})
sendToMain(GET_PICBEDS)
}
onBeforeMount(() => {
sendToMain(GET_PICBEDS)
useIPCOn(GET_PICBEDS, getPicBeds)
})
function getPicBeds (event: Event, picBeds: IPicBedType[]) {
picBed.value = picBeds
showPicBedList.value = picBed.value.map(item => {
if (item.visible) {
return item.name
}
return null
}).filter(item => item) as string[]
}
</script>
<script lang="ts">
export default {
name: 'ChoosePicBed'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,22 @@
<template>
<ChoosePicBed
v-model:showPicBedList="form.showPicBedList"
/>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import ChoosePicBed from './ChoosePicBed.vue'
interface IProps {
settings: ISettingForm
}
const props = defineProps<IProps>()
const form = reactive(props.settings)
</script>
<script lang="ts">
export default {
name: 'CustomAreaSettings'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,74 @@
<template>
<SelectFormItem
v-model="form.language"
:list="languageList"
:label="$T('SETTINGS_CHOOSE_LANGUAGE')"
:placeholder="$T('SETTINGS_CHOOSE_LANGUAGE')"
@change="handleLanguageChange"
/>
<SelectFormItem
v-model="form.startupMode"
:list="startupModeList"
:label="$T('SETTINGS_STARTUP_MODE')"
@change="handleChangeStartupMode"
/>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import { T as $T, i18nManager } from '@/i18n'
import { saveConfig, sendToMain } from '@/utils/dataSender'
import { GET_PICBEDS } from '~/universal/events/constants'
import SelectFormItem from '@/components/settings/SelectFormItem.vue'
import { IStartupMode } from '~/universal/types/enum'
import { isMacOS } from '~/universal/utils/common'
interface IProps {
settings: ISettingForm
}
const props = defineProps<IProps>()
const form = reactive(props.settings)
const languageList = i18nManager.languageList.map(item => ({
label: item.label,
value: item.value
}))
const startupModeList = [
{
label: $T('SETTINGS_STARTUP_MODE_MAIN_WINDOW'),
value: IStartupMode.SHOW_MAIN_WINDOW
},
{
label: $T('SETTINGS_STARTUP_MODE_MINI_WINDOW'),
value: IStartupMode.SHOW_MINI_WINDOW,
hide: isMacOS
},
{
label: $T('SETTINGS_STARTUP_MODE_HIDE'),
value: IStartupMode.HIDE
}
].filter(item => !item.hide)
function handleLanguageChange (val: string) {
i18nManager.setCurrentLanguage(val)
saveConfig({
'settings.language': val
})
sendToMain(GET_PICBEDS)
}
function handleChangeStartupMode (val: IStartupMode) {
saveConfig({
'settings.startupMode': val
})
}
</script>
<script lang="ts">
export default {
name: 'SelectAreaSettings'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,101 @@
<template>
<SwitchFormItem
v-model="form.showUpdateTip"
setting-props="showUpdateTip"
:label="$T('SETTINGS_OPEN_UPDATE_HELPER')"
/>
<SwitchFormItem
v-show="form.showUpdateTip"
v-model="form.checkBetaUpdate"
setting-props="checkBetaUpdate"
:label="$T('SETTINGS_ACCEPT_BETA_UPDATE')"
/>
<SwitchFormItem
v-model="form.autoStart"
setting-props="autoStart"
:label="$T('SETTINGS_LAUNCH_ON_BOOT')"
@change="handleAutoStartChange"
/>
<SwitchFormItem
v-model="form.rename"
setting-props="rename"
:label="$T('SETTINGS_RENAME_BEFORE_UPLOAD')"
/>
<SwitchFormItem
v-model="form.autoRename"
setting-props="autoRename"
:label="$T('SETTINGS_TIMESTAMP_RENAME')"
/>
<SwitchFormItem
v-model="form.uploadNotification"
setting-props="uploadNotification"
:label="$T('SETTINGS_OPEN_UPLOAD_TIPS')"
/>
<SwitchFormItem
v-if="os !== 'darwin'"
v-model="form.miniWindowOnTop"
:label="$T('SETTINGS_MINI_WINDOW_ON_TOP')"
setting-props="miniWindowOnTop"
@change="handleMiniWindowOnTop"
/>
<SwitchFormItem
v-model="form.autoCopyUrl"
:label="$T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD')"
setting-props="autoCopyUrl"
/>
<SwitchFormItem
v-model="form.useBuiltinClipboard"
:label="$T('SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD')"
:tooltips="$T('BUILTIN_CLIPBOARD_TIPS')"
setting-props="useBuiltinClipboard"
/>
<SwitchFormItem
v-model="form.encodeOutputURL"
:label="$T('SETTINGS_ENCODE_OUTPUT_URL')"
setting-props="encodeOutputURL"
/>
<SwitchFormItem
v-if="os === 'darwin'"
v-model="form.showDockIcon"
setting-props="showDockIcon"
:label="$T('SETTINGS_SHOW_DOCK_ICON')"
@change="handleShowDockIcon"
/>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { T as $T } from '@/i18n/index'
import { ElMessage as $message } from 'element-plus'
import { sendRPC, sendToMain } from '@/utils/dataSender'
import { IRPCActionType } from '~/universal/types/enum'
import SwitchFormItem from '@/components/settings/SwitchFormItem.vue'
const os = ref(process.platform)
interface IProps {
settings: ISettingForm
}
const props = defineProps<IProps>()
const form = reactive(props.settings)
function handleAutoStartChange (val: ISwitchValueType) {
sendToMain('autoStart', val)
}
function handleMiniWindowOnTop () {
$message.info($T('TIPS_NEED_RELOAD'))
}
function handleShowDockIcon (val: ISwitchValueType) {
sendRPC(IRPCActionType.SHOW_DOCK_ICON, val)
}
</script>
<script lang="ts">
export default {
name: 'SwitchAreaSettings'
}
</script>
<style lang='stylus'>
</style>
+6 -8
View File
@@ -43,16 +43,17 @@
</template>
<script lang="ts" setup>
import { IRPCActionType } from '~/universal/types/enum'
import { ref, onBeforeUnmount, onBeforeMount } from 'vue'
import { ref, onBeforeMount } from 'vue'
import { T as $T } from '@/i18n/index'
import { sendToMain, triggerRPC } from '@/utils/dataSender'
import { useRoute, useRouter } from 'vue-router'
import ConfigForm from '@/components/ConfigForm.vue'
// import mixin from '@/utils/ConfirmButtonMixin'
import {
ipcRenderer,
IpcRendererEvent
} from 'electron'
import { GET_PICBED_CONFIG } from '~/universal/events/constants'
import { useIPCOn } from '@/hooks/useIPC'
const type = ref('')
const config = ref<IPicGoPluginConfig[]>([])
const picBedName = ref('')
@@ -61,9 +62,10 @@ const $router = useRouter()
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
type.value = $route.params.type as string
useIPCOn(GET_PICBED_CONFIG, getPicBeds)
onBeforeMount(() => {
sendToMain('getPicBedConfig', $route.params.type)
ipcRenderer.on('getPicBedConfig', getPicBeds)
sendToMain(GET_PICBED_CONFIG, $route.params.type)
})
const handleConfirm = async () => {
@@ -85,10 +87,6 @@ function getPicBeds (event: IpcRendererEvent, _config: IPicGoPluginConfig[], nam
picBedName.value = name
}
onBeforeUnmount(() => {
ipcRenderer.removeListener('getPicBedConfig', getPicBeds)
})
</script>
<script lang="ts">
export default {
+19
View File
@@ -1,4 +1,6 @@
import { isReactive, isRef, toRaw, unref } from 'vue'
import { sendToMain } from './dataSender'
import { OPEN_URL, PICGO_OPEN_FILE } from '~/universal/events/constants'
const isDevelopment = process.env.NODE_ENV !== 'production'
/* eslint-disable camelcase */
@@ -51,3 +53,20 @@ export const getRawData = (args: any): any => {
}
return args
}
export const showNotification = (title: string, body: string) => {
const notification = new Notification(title, {
body
})
notification.onclick = () => {
return true
}
}
export const openFile = (fileName: string) => {
sendToMain(PICGO_OPEN_FILE, fileName)
}
export const openURL = (url: string) => {
sendToMain(OPEN_URL, url)
}
+3 -3
View File
@@ -1,6 +1,6 @@
import { ipcRenderer, IpcRendererEvent } from 'electron'
import { PICGO_SAVE_CONFIG, PICGO_GET_CONFIG, RPC_ACTIONS } from '#/events/constants'
import { uuid } from 'uuidv4'
import { PICGO_GET_CONFIG, PICGO_SAVE_CONFIG, RPC_ACTIONS } from '#/events/constants'
import { IpcRendererEvent, ipcRenderer } from 'electron'
import { v4 as uuid } from 'uuid'
import { IRPCActionType } from '~/universal/types/enum'
import { getRawData } from './common'
+6 -6
View File
@@ -1,15 +1,15 @@
import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
import { ipcRenderer, IpcRendererEvent } from 'electron'
import { uuid } from 'uuidv4'
import {
PICGO_GET_BY_ID_DB,
PICGO_GET_DB,
PICGO_INSERT_DB,
PICGO_INSERT_MANY_DB,
PICGO_UPDATE_BY_ID_DB,
PICGO_GET_BY_ID_DB,
PICGO_REMOVE_BY_ID_DB
PICGO_REMOVE_BY_ID_DB,
PICGO_UPDATE_BY_ID_DB
} from '#/events/constants'
import { IGalleryDB } from '#/types/extra-vue'
import { IFilter, IGetResult, IObject, IResult } from '@picgo/store/dist/types'
import { IpcRendererEvent, ipcRenderer } from 'electron'
import { v4 as uuid } from 'uuid'
import { getRawData } from './common'
export class GalleryDB implements IGalleryDB {
async get<T> (filter?: IFilter): Promise<IGetResult<T>> {
+1 -1
View File
@@ -1,4 +1,4 @@
import { uuid } from 'uuidv4'
import { v4 as uuid } from 'uuid'
export const completeUploaderMetaConfig = (originData: IStringKeyMap): IStringKeyMap => {
return Object.assign({
+2
View File
@@ -29,12 +29,14 @@ export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
export const PASTE_TEXT = 'PASTE_TEXT'
export const SET_MINI_WINDOW_POS = 'SET_MINI_WINDOW_POS'
export const RENAME_FILE_NAME = 'RENAME_FILE_NAME'
export const GET_RENAME_FILE_NAME = 'GET_RENAME_FILE_NAME'
export const SHOW_MAIN_PAGE_QRCODE = 'SHOW_MAIN_PAGE_QRCODE'
export const SHOW_MAIN_PAGE_DONATION = 'SHOW_MAIN_PAGE_DONATION'
export const FORCE_UPDATE = 'FORCE_UPDATE'
export const OPEN_WINDOW = 'OPEN_WINDOW'
export const GET_PICBEDS = 'GET_PICBEDS'
export const RPC_ACTIONS = 'RPC_ACTIONS'
export const GET_PICBED_CONFIG = 'GET_PICBED_CONFIG'
// i18n
export const GET_CURRENT_LANGUAGE = 'GET_CURRENT_LANGUAGE'
export const GET_LANGUAGE_LIST = 'GET_LANGUAGE_LIST'
+11
View File
@@ -72,6 +72,11 @@ export enum IRPCActionType {
OPEN_FILE = 'OPEN_FILE',
COPY_TEXT = 'COPY_TEXT',
SHOW_DOCK_ICON = 'SHOW_DOCK_ICON',
// gallery and toolbox rpc
UPDATE_GALLERY = 'UPDATE_GALLERY',
GET_GALLERY_MENU_LIST = 'GET_GALLERY_MENU_LIST',
OPEN_CONFIG_DIALOG = 'OPEN_CONFIG_DIALOG',
}
export enum IToolboxItemType {
@@ -87,3 +92,9 @@ export enum IToolboxItemCheckStatus {
SUCCESS = 'success',
ERROR = 'error',
}
export enum IStartupMode {
SHOW_MAIN_WINDOW = 'SHOW_SETTING_WINDOW',
SHOW_MINI_WINDOW = 'SHOW_MINI_WINDOW',
HIDE = 'HIDE'
}
+1 -1
View File
@@ -15,7 +15,7 @@ declare module 'vue/types/vue' {
}
}
declare module '@vue/runtime-core' {
declare module 'vue' {
interface ComponentCustomProperties {
$http: typeof axios
$builtInPicBed: string[]
+11
View File
@@ -54,6 +54,8 @@ interface ILocales {
FILE_RENAME: string
COPY_FILE_PATH: string
OPEN_FILE_PATH: string
SUCCESS: string
FAILED: string
SETTINGS: string
SETTINGS_OPEN_CONFIG_FILE: string
SETTINGS_CLICK_TO_OPEN: string
@@ -114,6 +116,10 @@ interface ILocales {
SELECTED_SETTING_HINT: string
SETTINGS_ENCODE_OUTPUT_URL: string
SETTINGS_SHOW_DOCK_ICON: string
SETTINGS_STARTUP_MODE: string
SETTINGS_STARTUP_MODE_MAIN_WINDOW: string
SETTINGS_STARTUP_MODE_MINI_WINDOW: string
SETTINGS_STARTUP_MODE_HIDE: string
SHORTCUT_NAME: string
SHORTCUT_BIND: string
SHORTCUT_STATUS: string
@@ -125,6 +131,11 @@ interface ILocales {
SHORTCUT_DISABLE: string
SHORTCUT_EDIT: string
SHORTCUT_CHANGE_UPLOAD: string
CHANGE_IMAGE_URL_HOST: string
NEW_IMAGE_URL_HOST: string
SELECTED_IMAGE_URL_HOST: string
CHANGE_IMAGE_URL_HOST_RESULT: string
CHANGE_IMAGE_URL_HOST_WARN: string
WAIT_TO_UPLOAD: string
ALREADY_UPLOAD: string
PICTURE_UPLOAD: string
+1
View File
@@ -8,6 +8,7 @@ type IToolboxCheckArgs = [type: import('./enum').IToolboxItemType]
type IOpenFileArgs = [filePath: string]
type ICopyTextArgs = [text: string]
type IShowDockIconArgs = [visible: boolean]
type IGetGalleryMenuListArgs = [selectedList: IGalleryItem[]]
interface IRPCServer {
start: () => void
+16 -1
View File
@@ -1,4 +1,7 @@
// global
type FN = (...args: any) => any
interface IObj {
[propName: string]: any
}
@@ -162,9 +165,19 @@ interface IPicGoPluginConfig {
name?: string
value?: any
}[]
/** support markdown */
tips?: string
[propName: string]: any
}
interface IPicGoPluginShowConfigDialogOption {
title: string
config: IPicGoPluginConfig[]
tips?: string
confirmText?: string
cancelText?: string
}
interface IPicGoPluginOriginConfig {
name: string
type: string
@@ -219,6 +232,8 @@ interface IGuiApi {
upload: (input: IUploadOption) => Promise<ImgInfo[]>
showNotification: (options?: IShowNotificationOption) => void
showMessageBox: (options?: IShowMessageBoxOption) => Promise<IShowMessageBoxResult>
showConfigDialog: <T extends IStringKeyMap>(options: IPicGoPluginShowConfigDialogOption) => Promise<T | false>
galleryDB: import('@picgo/store').DBStore
}
interface IShowInputBoxOption {
value?: string
@@ -430,4 +445,4 @@ interface IUploaderConfigItem {
type IUploaderConfigListItem = IStringKeyMap & IUploaderListItemMetaInfo
type ICheckBoxValueType = boolean | string | number
type ISwitchValueType = boolean | string | number
+15 -2
View File
@@ -1,11 +1,11 @@
interface ISettingForm {
updateHelper: boolean
showUpdateTip: boolean
showPicBedList: string[]
autoStart: boolean
rename: boolean
autoRename: boolean
uploadNotification: boolean
miniWindowOntop: boolean
miniWindowOnTop: boolean
logLevel: string[]
autoCopyUrl: boolean
checkBetaUpdate: boolean
@@ -14,6 +14,15 @@ interface ISettingForm {
logFileSizeLimit: number
encodeOutputURL: boolean
showDockIcon: boolean
customLink: string
npmProxy: string
npmRegistry: string
server: {
port: number
host: string
enable: boolean
}
startupMode: import('#/types/enum').IStartupMode
}
interface IShortKeyMap {
@@ -33,3 +42,7 @@ interface IToolboxItem {
type IToolboxMap = {
[id in import('#/types/enum').IToolboxItemType]: IToolboxItem
}
interface IFormInstance {
validate: () => Promise<IStringKeyMap | false>
}
+4
View File
@@ -51,3 +51,7 @@ export const trimValues = (obj: IStringKeyMap) => {
})
return newObj
}
export const isMacOS = process.platform === 'darwin'
export const isWindows = process.platform === 'win32'
export const isLinux = process.platform === 'linux'
+22
View File
@@ -0,0 +1,22 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./public/index.html',
'./src/**/*.{js,ts,jsx,tsx,vue}'
],
theme: {
extend: {
colors: {
blue: '#49B1F5',
green: '#44B363',
red: '#F15140',
yellow: '#F1BE48'
}
}
},
plugins: [],
corePlugins: {
// due to https://github.com/tailwindlabs/tailwindcss/issues/6602 - buttons disappear
preflight: false
}
}
+515 -121
View File
File diff suppressed because it is too large Load Diff