Compare commits

..
Author SHA1 Message Date
PiEgg 1848bff091 🎉 Release: v2.3.1-beta.1 2022-01-05 23:13:59 +08:00
PiEgg 56e814a96a 📦 Chore: update ci build scripts 2022-01-05 23:13:24 +08:00
PiEgg b93b4cffe2 🎉 Release: v2.3.1-beta.0 2022-01-05 22:58:53 +08:00
PiEgg f2a4197ca2 📦 Chore: add mac-arm64 build support 2022-01-05 12:54:02 +08:00
PiEgg 34b3656605 🐛 Fix: mini window drag bug 2022-01-05 12:18:50 +08:00
PiEgg ea20d3b971 📦 Chore: update electron from v6 -> v16 2022-01-04 23:59:35 +08:00
PiEgg 459953f391 🎉 Release: v2.3.0 2021-09-11 11:29:50 +08:00
PiEgg 75e3edcd87 Feature: add open devtool option 2021-08-29 19:04:43 +08:00
PiEgg 5895889059 🐛 Fix: shift key function in gallery page 2021-08-28 21:50:46 +08:00
PiEgg 58420c8c3b 📝 Docs: update FAQ 2021-08-28 17:36:14 +08:00
PiEgg 6c6f84779a 🐛 Fix: urlEncode bug when copy
ISSUES CLOSED: #731
2021-08-23 23:21:58 +08:00
PiEgg a676c083fe 🐛 Fix: some bugs
ISSUES CLOSED: #722
2021-08-21 10:42:52 +08:00
PiEgg 2d3e779e0b 🎉 Release: v2.3.0-beta.8 2021-08-13 20:38:50 +08:00
PiEgg 20d3cf987c 🐛 Fix: settings bug
ISSUES CLOSED: #710
2021-08-13 00:19:43 +08:00
PiEgg ae692632a5 🐛 Fix: upload clipboard images via http should return list
ISSUES CLOSED: #721
2021-08-12 22:17:14 +08:00
PiEgg 019efd354c 🎉 Release: v2.3.0-beta.7 2021-08-01 17:04:21 +08:00
PiEgg 7030f7a764 Feature: finish custom config path
ISSUES CLOSED: #255
2021-08-01 17:02:54 +08:00
PiEgg 96a63ea11a 🐛 Fix: bug of gallery db for plugin 2021-08-01 15:44:59 +08:00
PiEgg f1eb7f4d70 🐛 Fix: gallery db bug 2021-08-01 14:50:25 +08:00
PiEgg 6ddd660d89 Feature: add gallery db 2021-07-27 11:58:24 +08:00
PiEgg c70c3aff78 🚧 WIP: add gallery db 2021-07-27 00:15:11 +08:00
PiEgg 76964ff1a5 🚧 WIP: gallery db in progress 2021-07-25 23:25:36 +08:00
PiEgg 12cecc27e7 🐛 Fix: multiple uploading in the same time will cause rename failed 2021-07-14 23:57:09 +08:00
PiEgg bdf523a060 🐛 Fix: windows ia32 && x64 build options 2021-07-14 23:46:33 +08:00
PiEgg 49e5f343f4 🐛 Fix: enable plugin should reload
ISSUES CLOSED: #659
2021-07-10 19:38:05 +08:00
PiEgg 1657542144 Feature: add win32 support
ISSUES CLOSED: #632
2021-07-10 00:36:31 +08:00
PiEgg 8e5e9ec59a 🔨 Refactor: move guiApi to singleton 2021-07-10 00:25:34 +08:00
PiEgg 06b67e50b9 🐛 Fix: multiple uploading in the same time will cause output conflict
ISSUES CLOSED: #666
2021-05-09 10:52:14 +08:00
PiEgg cf895deb8d 🔨 Refactor: picgo server the way of uploading 2021-05-07 01:04:17 +08:00
PiEgg ab762ef465 🐛 Fix: uploader error in linux
ISSUES CLOSED: #627
2021-04-25 23:29:30 +08:00
PiEgg 92022a6e34 🐛 Fix: use uploader first 2021-04-24 18:16:13 +08:00
78 changed files with 8386 additions and 6076 deletions
+18 -6
View File
@@ -6,20 +6,32 @@ module.exports = {
env: {
node: true
},
parser: "vue-eslint-parser",
'extends': [
parser: 'vue-eslint-parser',
extends: [
'plugin:vue/essential',
'@vue/standard',
'@vue/typescript'
],
'plugins': ['@typescript-eslint'],
plugins: ['@typescript-eslint'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
"indent": "off",
"@typescript-eslint/indent": ["error", 2]
indent: 'off',
'no-async-promise-executor': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/indent': ['error', 2]
},
parserOptions: {
parser: '@typescript-eslint/parser'
}
},
overrides: [
{
files: ['*.ts', '*.vue'],
rules: {
'no-undef': 'off' // https://typescript-eslint.io/docs/linting/troubleshooting/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
}
}
],
ignorePatterns: ['src/**/*.d.ts']
}
+9 -3
View File
@@ -23,7 +23,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-10.15]
os: [ubuntu-latest, macos-11]
# create steps
steps:
@@ -48,8 +48,14 @@ jobs:
yarn
yarn global add xvfb-maybe
- name: Build & release app
- name: Build & release app linux
if: matrix.os == 'ubuntu-latest'
run: |
npm run release
yarn release
- name: Build & release app mac
if: matrix.os == 'macos-11'
run: |
yarn build --arm64
yarn release
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
+58
View File
@@ -0,0 +1,58 @@
# main.yml
# Workflow's name
name: Build
# Workflow's trigger
on: workflow_dispatch
# Workflow's jobs
jobs:
# job's id
release:
# job's name
name: build and release electron app
# the type of machine to run the job on
runs-on: ${{ matrix.os }}
# create a build matrix for jobs
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-11]
# create steps
steps:
# step1: check out repository
- name: Check out git repository
uses: actions/checkout@v2
# step2: install node env
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- name: Install system deps
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
# step3: yarn
- name: Yarn install
run: |
yarn
yarn global add xvfb-maybe
- name: Build & release app linux
if: matrix.os == 'ubuntu-latest'
run: |
yarn release
- name: Build & release app mac
if: matrix.os == 'macos-11'
run: |
yarn build --arm64
yarn release
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
+78
View File
@@ -1,3 +1,81 @@
## :tada: 2.3.1-beta.1 (2022-01-05)
### :package: Chore
* update ci build scripts ([56e814a](https://github.com/Molunerfinn/PicGo/commit/56e814a))
## :tada: 2.3.1-beta.0 (2022-01-05)
### :bug: Bug Fixes
* mini window drag bug ([34b3656](https://github.com/Molunerfinn/PicGo/commit/34b3656))
### :package: Chore
* add mac-arm64 build support ([f2a4197](https://github.com/Molunerfinn/PicGo/commit/f2a4197))
* update electron from v6 -> v16 ([ea20d3b](https://github.com/Molunerfinn/PicGo/commit/ea20d3b))
# :tada: 2.3.0 (2021-09-11)
### :sparkles: Features
* add open devtool option ([75e3edc](https://github.com/Molunerfinn/PicGo/commit/75e3edc))
### :bug: Bug Fixes
* shift key function in gallery page ([5895889](https://github.com/Molunerfinn/PicGo/commit/5895889))
* some bugs ([a676c08](https://github.com/Molunerfinn/PicGo/commit/a676c08)), closes [#722](https://github.com/Molunerfinn/PicGo/issues/722)
* urlEncode bug when copy ([6c6f847](https://github.com/Molunerfinn/PicGo/commit/6c6f847)), closes [#731](https://github.com/Molunerfinn/PicGo/issues/731)
### :pencil: Documentation
* update FAQ ([58420c8](https://github.com/Molunerfinn/PicGo/commit/58420c8))
# :tada: 2.3.0-beta.8 (2021-08-13)
### :bug: Bug Fixes
* settings bug ([20d3cf9](https://github.com/Molunerfinn/PicGo/commit/20d3cf9)), closes [#710](https://github.com/Molunerfinn/PicGo/issues/710)
* upload clipboard images via http should return list ([ae69263](https://github.com/Molunerfinn/PicGo/commit/ae69263)), closes [#721](https://github.com/Molunerfinn/PicGo/issues/721)
# :tada: 2.3.0-beta.7 (2021-08-01)
### :sparkles: Features
* add gallery db ([6ddd660](https://github.com/Molunerfinn/PicGo/commit/6ddd660))
* add win32 support ([1657542](https://github.com/Molunerfinn/PicGo/commit/1657542)), closes [#632](https://github.com/Molunerfinn/PicGo/issues/632)
* finish custom config path ([7030f7a](https://github.com/Molunerfinn/PicGo/commit/7030f7a)), closes [#255](https://github.com/Molunerfinn/PicGo/issues/255)
### :bug: Bug Fixes
* bug of gallery db for plugin ([96a63ea](https://github.com/Molunerfinn/PicGo/commit/96a63ea))
* enable plugin should reload ([49e5f34](https://github.com/Molunerfinn/PicGo/commit/49e5f34)), closes [#659](https://github.com/Molunerfinn/PicGo/issues/659)
* gallery db bug ([f1eb7f4](https://github.com/Molunerfinn/PicGo/commit/f1eb7f4))
* multiple uploading in the same time will cause output conflict ([06b67e5](https://github.com/Molunerfinn/PicGo/commit/06b67e5)), closes [#666](https://github.com/Molunerfinn/PicGo/issues/666)
* multiple uploading in the same time will cause rename failed ([12cecc2](https://github.com/Molunerfinn/PicGo/commit/12cecc2))
* uploader error in linux ([ab762ef](https://github.com/Molunerfinn/PicGo/commit/ab762ef)), closes [#627](https://github.com/Molunerfinn/PicGo/issues/627)
* use uploader first ([92022a6](https://github.com/Molunerfinn/PicGo/commit/92022a6))
* windows ia32 && x64 build options ([bdf523a](https://github.com/Molunerfinn/PicGo/commit/bdf523a))
# :tada: 2.3.0-beta.6 (2021-04-24)
+4
View File
@@ -57,3 +57,7 @@ PicGo 在 Mac 上是一个顶部栏应用,在 dock 栏是不会有图标的。
1. 先自行搜索 error 里的报错信息,往往你能百度或者谷歌出问题原因,不必开 issue。
2. 如果有带有 `401``403``40X` 状态码字样的,不用怀疑,就是你配置写错了,仔细检查配置,看看是否多了空格之类的。
3. 如果带有 `HttpError``RequestError``socket hang up` 等字样的说明这是网络问题,我无法帮你解决网络问题,请检查你自己的网络,是否有代理,DNS 设置是否正常等。
## 10. macOS版本安装完之后没有主界面
请找到PicGo在顶部栏的图标,然后右键(触摸板双指点按,或者鼠标右键),即可找到「打开详细窗口」的菜单。
+4 -3
View File
@@ -19,13 +19,14 @@ init:
- git config --global core.autocrlf input
install:
- ps: Install-Product node 12 x64
- ps: Install-Product node 16 x64
- git reset --hard HEAD
- yarn
- node --version
build_script:
#- yarn test
- npm run release
- yarn build --win --ia32
- yarn release
test: off
test: false
+2 -1
View File
@@ -1,5 +1,6 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
],
plugins: ['@babel/plugin-proposal-optional-chaining']
}
+3 -3
View File
@@ -22,7 +22,7 @@
.col-xs-10.col-xs-offset-1.col-md-7.col-md-offset-0
img(:src="item.url")
.col-xs-10.col-xs-offset-1.col-md-5.col-md-offset-0.display-list__content
.display-list__title {{ item.title }}
.display-list__title {{ item.title }}
.display-list__desc {{ item.desc }}
.row.ex-width.info
.col-xs-10.col-xs-offset-1
@@ -94,7 +94,7 @@ html,
h1
margin 0
padding 0
font-family "Source Sans Pro","Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
font-family "Source Sans Pro","Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif
#app
position relative
.mask
@@ -213,4 +213,4 @@ h1
font-size 25px
&__desc
margin-top 12px
</style>
</style>
+26 -17
View File
@@ -1,11 +1,12 @@
{
"name": "picgo",
"version": "2.3.0-beta.6",
"version": "2.3.1-beta.1",
"private": true,
"scripts": {
"dev": "vue-cli-service electron:serve",
"build": "vue-cli-service electron:build",
"lint": "vue-cli-service lint",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
@@ -34,52 +35,60 @@
]
},
"dependencies": {
"@picgo/store": "^1.0.3",
"axios": "^0.19.0",
"core-js": "^3.3.2",
"element-ui": "^2.13.0",
"fix-path": "^2.1.0",
"fs-extra": "^8.1.0",
"fs-extra": "^10.0.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"picgo": "^1.4.19",
"picgo": "^1.4.24",
"qrcode.vue": "^1.7.0",
"uuidv4": "^6.2.11",
"vue": "^2.6.10",
"vue-gallery": "^2.0.1",
"vue-lazyload": "^1.2.6",
"vue-router": "^3.1.3"
},
"devDependencies": {
"@commitlint/cli": "^8.2.0",
"@picgo/bump-version": "^1.0.3",
"@types/fs-extra": "^8.0.1",
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@picgo/bump-version": "^1.1.2",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^6.5.0",
"@types/lowdb": "^1.0.9",
"@types/node": "10.17.6",
"@types/node": "^16.10.2",
"@types/request-promise-native": "^1.0.17",
"@types/semver": "^7.3.8",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-plugin-router": "^4.0.0",
"@vue/cli-plugin-typescript": "^4.0.0",
"@vue/cli-plugin-typescript": "^4.5.13",
"@vue/cli-service": "^4.0.0",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/eslint-config-typescript": "^4.0.0",
"commitizen": "^4.0.3",
"@vue/eslint-config-standard": "^6.1.0",
"@vue/eslint-config-typescript": "^7.0.0",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"electron": "^6.0.0",
"electron": "^16.0.6",
"electron-devtools-installer": "^3.2.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"eslint": "^7.32.0",
"eslint-config-standard": ">=16.0.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.0.0",
"husky": "^3.1.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"typescript": "~3.7.3",
"vue-cli-plugin-electron-builder": "^1.4.2",
"typescript": "^4.4.3",
"vue-cli-plugin-electron-builder": "^2.1.1",
"vue-property-decorator": "^8.3.0",
"vue-template-compiler": "^2.6.10"
},
"resolutions": {
"@types/node": "12.0.2"
"@types/node": "^16.10.2"
}
}
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# grab the paths
scriptPath=$(echo $0 | awk '{ print substr( $0, 1, length($0)-6 ) }')"windows10.ps1"
imagePath=$(echo $1 | awk '{ print substr( $0, 1, length($0)-18 ) }')
imageName=$(echo $1 | awk '{ print substr( $0, length($0)-17, length($0) ) }')
# run the powershell script
res=$(powershell.exe -noprofile -noninteractive -nologo -sta -executionpolicy unrestricted -file $(wslpath -w $scriptPath) $(wslpath -w $imagePath)"\\"$imageName)
# note that there is a return symbol in powershell result
noImage=$(echo "no image\r")
# check whether image exists
if [ "$res" = "$noImage" ] ;then
echo "no image"
else
echo $(wslpath -u $res)
fi
+2 -4
View File
@@ -1,7 +1,6 @@
import Vue from 'vue'
import App from './renderer/App.vue'
import router from './renderer/router'
import db from '#/datastore/index'
import ElementUI from 'element-ui'
import { webFrame } from 'electron'
import 'element-ui/lib/theme-chalk/index.css'
@@ -10,14 +9,13 @@ import axios from 'axios'
import mainMixin from './renderer/utils/mainMixin'
import bus from '@/utils/bus'
import { initTalkingData } from './renderer/utils/analytics'
import db from './renderer/utils/db'
webFrame.setVisualZoomLevelLimits(1, 1)
webFrame.setLayoutZoomLevelLimits(0, 0)
Vue.config.productionTip = false
Vue.prototype.$builtInPicBed = [
'smms',
'weibo',
'imgur',
'qiniu',
'tcyun',
@@ -25,7 +23,7 @@ Vue.prototype.$builtInPicBed = [
'aliyun',
'github'
]
Vue.prototype.$db = db
Vue.prototype.$$db = db
Vue.prototype.$http = axios
Vue.prototype.$bus = bus
+1 -1
View File
@@ -6,7 +6,7 @@ The lowest level APIs that are not dependent on each other. The upper APIs depen
## app
Provide key API interfaces for PicGo application, including uploader, window management, shortcut key system, etc
Provide key API interfaces for PicGo application, including uploader, window management, shortcut key system, remotes handler, etc
## gui
+14 -6
View File
@@ -4,7 +4,7 @@ import {
} from 'electron'
import logger from '@core/picgo/logger'
import GuiApi from '../../gui'
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import shortKeyService from './shortKeyService'
import picgo from '@core/picgo'
@@ -16,10 +16,12 @@ class ShortKeyHandler {
this.isInModifiedMode = flag
})
}
init () {
this.initBuiltInShortKey()
this.initPluginsShortKey()
}
private initBuiltInShortKey () {
const commands = db.get('settings.shortKey') as IShortKeyConfigs
Object.keys(commands)
@@ -34,10 +36,11 @@ class ShortKeyHandler {
}
})
}
private initPluginsShortKey () {
// get enabled plugin
const pluginList = picgo.pluginLoader.getList()
for (let item of pluginList) {
for (const item of pluginList) {
const plugin = picgo.pluginLoader.getPlugin(item)
// if a plugin has commands
if (plugin && plugin.commands) {
@@ -46,7 +49,7 @@ class ShortKeyHandler {
continue
}
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
for (let cmd of commands) {
for (const cmd of commands) {
const command = `${item}:${cmd.name}`
if (db.has(`settings.shortKey[${command}]`)) {
const commandConfig = db.get(`settings.shortKey.${command}`) as IShortKeyConfig
@@ -63,6 +66,7 @@ class ShortKeyHandler {
}
}
}
private registerShortKey (config: IShortKeyConfig | IPluginShortKeyConfig, command: string, handler: IShortKeyHandler, writeFlag: boolean) {
shortKeyService.registerCommand(command, handler)
if (config.key) {
@@ -85,6 +89,7 @@ class ShortKeyHandler {
})
}
}
// enable or disable shortKey
bindOrUnbindShortKey (item: IShortKeyConfig, from: string): boolean {
const command = `${from}:${item.name}`
@@ -108,6 +113,7 @@ class ShortKeyHandler {
}
}
}
// update shortKey bindings
updateShortKey (item: IShortKeyConfig, oldKey: string, from: string): boolean {
const command = `${from}:${item.name}`
@@ -121,6 +127,7 @@ class ShortKeyHandler {
})
return true
}
private async handler (command: string) {
if (this.isInModifiedMode) {
return
@@ -130,13 +137,13 @@ class ShortKeyHandler {
} else if (command.includes('picgo-plugin-')) {
const handler = shortKeyService.getShortKeyHandler(command)
if (handler) {
const guiApi = new GuiApi()
return handler(picgo, guiApi)
return handler(picgo, GuiApi.getInstance())
}
} else {
logger.warn(`can not find command: ${command}`)
}
}
registerPluginShortKey (pluginName: string) {
const plugin = picgo.pluginLoader.getPlugin(pluginName)
if (plugin && plugin.commands) {
@@ -145,7 +152,7 @@ class ShortKeyHandler {
return
}
const commands = plugin.commands(picgo) as IPluginShortKeyConfig[]
for (let cmd of commands) {
for (const cmd of commands) {
const command = `${pluginName}:${cmd.name}`
if (db.has(`settings.shortKey[${command}]`)) {
const commandConfig = db.get(`settings.shortKey[${command}]`) as IShortKeyConfig
@@ -156,6 +163,7 @@ class ShortKeyHandler {
}
}
}
unregisterPluginShortKey (pluginName: string) {
const commands = db.get('settings.shortKey') as IShortKeyConfigs
const keyList = Object.keys(commands)
@@ -4,15 +4,18 @@ class ShortKeyService {
registerCommand (command: string, handler: IShortKeyHandler) {
this.commandList.set(command, handler)
}
unregisterCommand (command: string) {
this.commandList.delete(command)
}
getShortKeyHandler (command: string): IShortKeyHandler | null {
const handler = this.commandList.get(command)
if (handler) return handler
logger.warn(`cannot find command: ${command}`)
return null
}
getCommandList () {
return [...this.commandList.keys()]
}
+8 -6
View File
@@ -9,11 +9,11 @@ import {
} from 'electron'
import uploader from 'apis/app/uploader'
import getPicBeds from '~/main/utils/getPicBeds'
import db from '#/datastore'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import picgo from '@core/picgo'
import pasteTemplate from '#/utils/pasteTemplate'
import pasteTemplate from '~/main/utils/pasteTemplate'
import pkg from 'root/package.json'
import { handleCopyUrl } from '~/main/utils/common'
import { privacyManager } from '~/main/utils/privacyManager'
@@ -162,8 +162,8 @@ export function createTray () {
if (process.platform === 'darwin') {
toggleWindow(bounds)
setTimeout(() => {
let img = clipboard.readImage()
let obj: ImgInfo[] = []
const img = clipboard.readImage()
const obj: ImgInfo[] = []
if (!img.isEmpty()) {
// 从剪贴板来的图片默认转为png
// @ts-ignore
@@ -201,6 +201,8 @@ export function createTray () {
tray!.setImage(`${__static}/menubar.png`)
})
// drop-files only be supported in macOS
// so the tray window must be available
tray.on('drop-files', async (event: Event, files: string[]) => {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
@@ -210,7 +212,7 @@ export function createTray () {
if (imgs !== false) {
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i]))
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl!,
@@ -219,7 +221,7 @@ export function createTray () {
setTimeout(() => {
notification.show()
}, i * 100)
db.insert('uploaded', imgs[i])
await GalleryDB.getInstance().insert(imgs[i])
}
handleCopyUrl(pasteText.join('\n'))
trayWindow.webContents.send('dragFiles', imgs)
+13 -11
View File
@@ -5,27 +5,28 @@ import {
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import uploader from '.'
import pasteTemplate from '#/utils/pasteTemplate'
import db from '#/datastore'
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'
export const uploadClipboardFiles = async (): Promise<string> => {
const win = windowManager.getAvailableWindow()
let img = await uploader.setWebContents(win!.webContents).upload()
const img = await uploader.setWebContents(win!.webContents).upload()
if (img !== false) {
if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
handleCopyUrl(pasteTemplate(pasteStyle, img[0]))
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl!,
icon: img[0].imgUrl
})
notification.show()
db.insert('uploaded', img[0])
trayWindow.webContents.send('clipboardFiles', [])
trayWindow.webContents.send('uploadFiles', img)
await GalleryDB.getInstance().insert(img[0])
// trayWindow just be created in mac/windows, not in linux
trayWindow?.webContents?.send('clipboardFiles', [])
trayWindow?.webContents?.send('uploadFiles', img)
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
}
@@ -51,7 +52,7 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i]))
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl!,
@@ -60,11 +61,12 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
setTimeout(() => {
notification.show()
}, i * 100)
db.insert('uploaded', imgs[i])
await GalleryDB.getInstance().insert(imgs[i])
result.push(handleUrlEncode(imgs[i].imgUrl!))
}
handleCopyUrl(pasteText.join('\n'))
windowManager.get(IWindowList.TRAY_WINDOW)!.webContents.send('uploadFiles', imgs)
// 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')
}
+10 -20
View File
@@ -6,7 +6,7 @@ import {
} from 'electron'
import dayjs from 'dayjs'
import picgo from '@core/picgo'
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import util from 'util'
@@ -16,7 +16,7 @@ import { TALKING_DATA_EVENT } from '~/universal/events/constants'
import logger from '@core/picgo/logger'
const waitForShow = (webcontent: WebContents) => {
return new Promise<void>((resolve, reject) => {
return new Promise<void>((resolve) => {
webcontent.on('did-finish-load', () => {
resolve()
})
@@ -24,7 +24,7 @@ const waitForShow = (webcontent: WebContents) => {
}
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
const windowId = window.id
ipcMain.once(`rename${id}`, (evt: Event, newName: string) => {
resolve(newName)
@@ -54,7 +54,7 @@ const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) =>
class Uploader {
private webContents: WebContents | null = null
private uploading: boolean = false
// private uploading: boolean = false
constructor () {
this.init()
}
@@ -68,7 +68,7 @@ class Uploader {
picgo.on('uploadProgress', progress => {
this.webContents?.send('uploadProgress', progress)
})
picgo.on('beforeTransform', ctx => {
picgo.on('beforeTransform', () => {
if (db.get('settings.uploadNotification')) {
const notification = new Notification({
title: '上传进度',
@@ -86,7 +86,7 @@ class Uploader {
let name: undefined | string | null
let fileName: string | undefined
if (autoRename) {
fileName = dayjs().add(index, 'second').format('YYYYMMDDHHmmss') + item.extname
fileName = dayjs().add(index, 'ms').format('YYYYMMDDHHmmSSS') + item.extname
} else {
fileName = item.fileName
}
@@ -109,33 +109,23 @@ class Uploader {
}
async upload (img?: IUploadOption): Promise<ImgInfo[]|false> {
if (this.uploading) {
showNotification({
title: '上传失败',
body: '前序上传还在继续,请稍后再试'
})
return Promise.resolve(false)
}
try {
const startTime = Date.now()
this.uploading = true
const output = await picgo.upload(img)
this.uploading = false
if (Array.isArray(output) && output.every((item: ImgInfo) => item.imgUrl)) {
if (Array.isArray(output) && output.some((item: ImgInfo) => item.imgUrl)) {
if (this.webContents) {
handleTalkingData(this.webContents, {
fromClipboard: !img,
type: db.get('picBed.current') || 'smms',
type: db.get('picBed.uploader') || db.get('picBed.current') || 'smms',
count: img ? img.length : 1,
duration: Date.now() - startTime
} as IAnalyticsData)
}
return output
return output.filter(item => item.imgUrl)
} else {
return false
}
} catch (e) {
this.uploading = false
} catch (e: any) {
logger.error(e)
setTimeout(() => {
showNotification({
+4 -4
View File
@@ -9,16 +9,16 @@ const isDevelopment = process.env.NODE_ENV !== 'production'
export const TRAY_WINDOW_URL = isDevelopment
? (process.env.WEBPACK_DEV_SERVER_URL as string)
: `picgo://./index.html`
: 'picgo://./index.html'
export const SETTING_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload`
: `picgo://./index.html#main-page/upload`
: 'picgo://./index.html#main-page/upload'
export const MINI_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page`
: `picgo://./index.html#mini-page`
: 'picgo://./index.html#mini-page'
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
: `picgo://./index.html#rename-page`
: 'picgo://./index.html#rename-page'
+11 -7
View File
@@ -8,7 +8,7 @@ import {
import { IWindowListItem } from '#/types/electron'
import bus from '@core/bus'
import { CREATE_APP_MENU } from '@core/bus/constants'
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { app } from 'electron'
@@ -28,7 +28,8 @@ windowList.set(IWindowList.TRAY_WINDOW, {
transparent: true,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: true,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
backgroundThrottling: false
}
@@ -60,7 +61,8 @@ windowList.set(IWindowList.SETTING_WINDOW, {
titleBarStyle: 'hidden',
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
webSecurity: false
}
@@ -93,7 +95,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
isValid: process.platform !== 'darwin',
multiple: false,
options () {
let obj: IBrowserWindowOptions = {
const obj: IBrowserWindowOptions = {
height: 64,
width: 64,
show: process.platform === 'linux',
@@ -105,7 +107,8 @@ windowList.set(IWindowList.MINI_WINDOW, {
icon: `${__static}/logo.png`,
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true
}
}
@@ -124,7 +127,7 @@ windowList.set(IWindowList.RENAME_WINDOW, {
isValid: true,
multiple: true,
options () {
let options: IBrowserWindowOptions = {
const options: IBrowserWindowOptions = {
height: 175,
width: 300,
show: true,
@@ -132,7 +135,8 @@ windowList.set(IWindowList.RENAME_WINDOW, {
resizable: false,
vibrancy: 'ultra-dark',
webPreferences: {
nodeIntegration: true,
nodeIntegration: !!process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegrationInWorker: true,
backgroundThrottling: false
}
@@ -34,6 +34,7 @@ class WindowManager implements IWindowManager {
return null
}
}
get (name: IWindowList) {
if (this.has(name)) {
return this.windowMap.get(name)!
@@ -42,9 +43,11 @@ class WindowManager implements IWindowManager {
return window
}
}
has (name: IWindowList) {
return this.windowMap.has(name)
}
// useless
// delete (name: IWindowList) {
// const window = this.windowMap.get(name)
@@ -60,6 +63,7 @@ class WindowManager implements IWindowManager {
this.windowIdMap.delete(id)
}
}
getAvailableWindow () {
const miniWindow = this.windowMap.get(IWindowList.MINI_WINDOW)
if (miniWindow && miniWindow.isVisible()) {
+4 -4
View File
@@ -14,7 +14,7 @@ export const uploadWithClipboardFiles = (): Promise<{
success: boolean,
result?: string[]
}> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
bus.once(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, (result: string) => {
if (result) {
return resolve({
@@ -35,7 +35,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
success: boolean,
result?: string[]
}> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
bus.once(UPLOAD_WITH_FILES_RESPONSE, (result: string[]) => {
if (result.length) {
return resolve({
@@ -55,7 +55,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
// get available window id:
// miniWindow or settingWindow or trayWindow
export const getWindowId = (): Promise<number> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
resolve(id)
})
@@ -65,7 +65,7 @@ export const getWindowId = (): Promise<number> => {
// get settingWindow id:
export const getSettingWindowId = (): Promise<number> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
bus.once(GET_SETTING_WINDOW_ID_RESPONSE, (id: number) => {
resolve(id)
})
+136
View File
@@ -0,0 +1,136 @@
import fs from 'fs-extra'
import path from 'path'
import { app as APP } from 'electron'
import { getLogger } from '@core/utils/localLogger'
import dayjs from 'dayjs'
const STORE_PATH = APP.getPath('userData')
const configFilePath = path.join(STORE_PATH, 'data.json')
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
export const defaultConfigPath = configFilePath
let _configFilePath = ''
let hasCheckPath = false
const errorMsg = {
broken: 'PicGo 配置文件损坏,已经恢复为默认配置',
brokenButBackup: 'PicGo 配置文件损坏,已经恢复为备份配置'
}
/** ensure notification list */
if (!global.notificationList) global.notificationList = []
function dbChecker () {
if (process.type !== 'renderer') {
// db save bak
try {
const { dbPath, dbBackupPath } = getGalleryDBPath()
if (fs.existsSync(dbPath)) {
fs.copyFileSync(dbPath, dbBackupPath)
}
} catch (e) {
console.error(e)
}
const configFilePath = dbPathChecker()
if (!fs.existsSync(configFilePath)) {
return
}
let configFile: string = '{}'
const optionsTpl = {
title: '注意',
body: ''
}
// config save bak
try {
configFile = fs.readFileSync(configFilePath, { encoding: 'utf-8' })
JSON.parse(configFile)
} catch (e) {
fs.unlinkSync(configFilePath)
if (fs.existsSync(configFileBackupPath)) {
try {
configFile = fs.readFileSync(configFileBackupPath, { encoding: 'utf-8' })
JSON.parse(configFile)
fs.writeFileSync(configFilePath, configFile, { encoding: 'utf-8' })
const stats = fs.statSync(configFileBackupPath)
optionsTpl.body = `${errorMsg.brokenButBackup}\n备份文件版本:${dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')}`
global.notificationList?.push(optionsTpl)
return
} catch (e) {
optionsTpl.body = errorMsg.broken
global.notificationList?.push(optionsTpl)
return
}
}
optionsTpl.body = errorMsg.broken
global.notificationList?.push(optionsTpl)
return
}
fs.writeFileSync(configFileBackupPath, configFile, { encoding: 'utf-8' })
}
}
/**
* Get config path
*/
function dbPathChecker (): string {
if (_configFilePath) {
return _configFilePath
}
// defaultConfigPath
_configFilePath = defaultConfigPath
// if defaultConfig path is not exit
// do not parse the content of config
if (!fs.existsSync(defaultConfigPath)) {
return _configFilePath
}
try {
const configString = fs.readFileSync(defaultConfigPath, { encoding: 'utf-8' })
const config = JSON.parse(configString)
const userConfigPath: string = config.configPath || ''
if (userConfigPath) {
if (fs.existsSync(userConfigPath) && userConfigPath.endsWith('.json')) {
_configFilePath = userConfigPath
return _configFilePath
}
}
return _configFilePath
} catch (e) {
const picgoLogPath = path.join(defaultConfigPath, 'picgo.log')
const logger = getLogger(picgoLogPath)
if (!hasCheckPath) {
const optionsTpl = {
title: '注意',
body: '自定义文件解析出错,请检查路径内容是否正确'
}
global.notificationList?.push(optionsTpl)
hasCheckPath = true
}
logger('error', e)
console.error(e)
_configFilePath = defaultConfigPath
return _configFilePath
}
}
function dbPathDir () {
return path.dirname(dbPathChecker())
}
function getGalleryDBPath (): {
dbPath: string
dbBackupPath: string
} {
const configPath = dbPathChecker()
const dbPath = path.join(path.dirname(configPath), 'picgo.db')
const dbBackupPath = path.join(path.dirname(dbPath), 'picgo.bak.db')
return {
dbPath,
dbBackupPath
}
}
export {
dbChecker,
dbPathChecker,
dbPathDir,
getGalleryDBPath
}
@@ -2,33 +2,27 @@ import Datastore from 'lowdb'
// @ts-ignore
import LodashId from 'lodash-id'
import FileSync from 'lowdb/adapters/FileSync'
import path from 'path'
import fs from 'fs-extra'
import { remote, app } from 'electron'
import { dbChecker } from './dbChecker'
import { dbPathChecker, dbPathDir, getGalleryDBPath } from './dbChecker'
import { DBStore } from '@picgo/store'
const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')
const STORE_PATH = dbPathDir()
if (process.type !== 'renderer') {
if (!fs.pathExistsSync(STORE_PATH)) {
fs.mkdirpSync(STORE_PATH)
}
dbChecker()
if (!fs.pathExistsSync(STORE_PATH)) {
fs.mkdirpSync(STORE_PATH)
}
const CONFIG_PATH: string = dbPathChecker()
const DB_PATH: string = getGalleryDBPath().dbPath
class DB {
// TODO: use JSONStore with @picgo/store
class ConfigStore {
private db: Datastore.LowdbSync<Datastore.AdapterSync>
constructor () {
const adapter = new FileSync(path.join(STORE_PATH, '/data.json'))
const adapter = new FileSync(CONFIG_PATH)
this.db = Datastore(adapter)
this.db._.mixin(LodashId)
if (!this.db.has('uploaded').value()) {
this.db.set('uploaded', []).write()
}
if (!this.db.has('picBed').value()) {
this.db.set('picBed', {
current: 'smms', // deprecated
@@ -48,33 +42,64 @@ class DB {
}).write()
}
}
read () {
return this.db.read()
}
get (key = '') {
return this.read().get(key).value()
}
set (key: string, value: any) {
return this.read().set(key, value).write()
}
has (key: string) {
return this.read().has(key).value()
}
insert (key: string, value: any): void {
// @ts-ignore
return this.read().get(key).insert(value).write()
}
unset (key: string, value: any): boolean {
return this.read().get(key).unset(value).value()
}
getById (key: string, id: string) {
// @ts-ignore
return this.read().get(key).getById(id).value()
}
removeById (key: string, id: string) {
// @ts-ignore
return this.read().get(key).removeById(id).write()
}
getConfigPath () {
return CONFIG_PATH
}
}
export default new DB()
export default new ConfigStore()
// v2.3.0 add gallery db
class GalleryDB {
private static instance: DBStore
private constructor () {
console.log('init gallery db')
}
public static getInstance (): DBStore {
if (!GalleryDB.instance) {
GalleryDB.instance = new DBStore(DB_PATH, 'gallery')
}
return GalleryDB.instance
}
}
export {
GalleryDB
}
+6 -7
View File
@@ -1,14 +1,13 @@
import PicGoCore from '~/universal/types/picgo'
import {
app
} from 'electron'
import path from 'path'
import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
import pkg from 'root/package.json'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo') as typeof PicGoCore
const STORE_PATH = app.getPath('userData')
const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
const CONFIG_PATH = dbPathChecker()
dbChecker()
const picgo = new PicGo(CONFIG_PATH)
picgo.saveConfig({
@@ -16,7 +15,7 @@ picgo.saveConfig({
PICGO_ENV: 'GUI'
})
// @ts-ignore
global.PICGO_GUI_VERSION = pkg.version
picgo.GUI_VERSION = global.PICGO_GUI_VERSION
export default picgo! as PicGoCore
+32
View File
@@ -0,0 +1,32 @@
import fs from 'fs-extra'
import dayjs from 'dayjs'
import util from 'util'
/**
* for local log before picgo inited
*/
const getLogger = (logPath: string) => {
if (!fs.existsSync(logPath)) {
fs.ensureFileSync(logPath)
}
return (type: string, ...msg: any[]) => {
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ${type.toUpperCase()}] `
msg.forEach((item: ILogArgvTypeWithError) => {
if (typeof item === 'object' && type === 'error') {
log += `\n------Error Stack Begin------\n${util.format(item.stack)}\n-------Error Stack End------- `
} else {
if (typeof item === 'object') {
item = JSON.stringify(item)
}
log += `${item} `
}
})
log += '\n'
// A synchronized approach to avoid log msg sequence errors
fs.appendFileSync(logPath, log)
}
}
export {
getLogger
}
+75 -23
View File
@@ -4,9 +4,10 @@ import {
Notification,
ipcMain
} from 'electron'
import db from '#/datastore'
import db, { GalleryDB } from 'apis/core/datastore'
import { dbPathChecker, defaultConfigPath, getGalleryDBPath } from 'apis/core/datastore/dbChecker'
import uploader from 'apis/app/uploader'
import pasteTemplate from '#/utils/pasteTemplate'
import pasteTemplate from '~/main/utils/pasteTemplate'
import { handleCopyUrl } from '~/main/utils/common'
import {
getWindowId,
@@ -15,19 +16,32 @@ import {
import {
SHOW_INPUT_BOX
} from '~/universal/events/constants'
import { DBStore } from '@picgo/store'
// Cross-process support may be required in the future
class GuiApi implements IGuiApi {
private static instance: GuiApi
private windowId: number = -1
private settingWindowId: number = -1
private constructor () {
console.log('init guiapi')
}
public static getInstance (): GuiApi {
if (!GuiApi.instance) {
GuiApi.instance = new GuiApi()
}
return GuiApi.instance
}
private async showSettingWindow () {
this.settingWindowId = await getSettingWindowId()
const settingWindow = BrowserWindow.fromId(this.settingWindowId)
if (settingWindow.isVisible()) {
if (settingWindow?.isVisible()) {
return true
}
settingWindow.show()
return new Promise<void>((resolve, reject) => {
settingWindow?.show()
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve()
}, 1000) // TODO: a better way to wait page loaded.
@@ -35,7 +49,7 @@ class GuiApi implements IGuiApi {
}
private getWebcontentsByWindowId (id: number) {
return BrowserWindow.fromId(id).webContents
return BrowserWindow.fromId(id)?.webContents
}
async showInputBox (options: IShowInputBoxOption = {
@@ -43,33 +57,29 @@ class GuiApi implements IGuiApi {
placeholder: ''
}) {
await this.showSettingWindow()
this.getWebcontentsByWindowId(this.settingWindowId)
.send(SHOW_INPUT_BOX, options)
return new Promise<string>((resolve, reject) => {
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)
})
})
}
showFileExplorer (options: IShowFileExplorerOption = {}) {
return new Promise<string>(async (resolve, reject) => {
this.windowId = await getWindowId()
dialog.showOpenDialog(BrowserWindow.fromId(this.windowId), options, (filename: string) => {
resolve(filename)
})
})
async showFileExplorer (options: IShowFileExplorerOption = {}) {
this.windowId = await getWindowId()
const res = await dialog.showOpenDialog(BrowserWindow.fromId(this.windowId)!, options)
return res.filePaths?.[0]
}
async upload (input: IUploadOption) {
this.windowId = await getWindowId()
const webContents = this.getWebcontentsByWindowId(this.windowId)
const imgs = await uploader.setWebContents(webContents).upload(input)
const imgs = await uploader.setWebContents(webContents!).upload(input)
if (imgs !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
pasteText.push(pasteTemplate(pasteStyle, imgs[i]))
pasteText.push(pasteTemplate(pasteStyle, imgs[i], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: imgs[i].imgUrl as string,
@@ -78,11 +88,11 @@ class GuiApi implements IGuiApi {
setTimeout(() => {
notification.show()
}, i * 100)
db.insert('uploaded', imgs[i])
await GalleryDB.getInstance().insert(imgs[i])
}
handleCopyUrl(pasteText.join('\n'))
webContents.send('uploadFiles', imgs)
webContents.send('updateGallery')
webContents?.send('uploadFiles', imgs)
webContents?.send('updateGallery')
return imgs
}
return []
@@ -105,10 +115,10 @@ class GuiApi implements IGuiApi {
type: 'info',
buttons: ['Yes', 'No']
}) {
return new Promise<IShowMessageBoxResult>(async (resolve, reject) => {
return new Promise<IShowMessageBoxResult>(async (resolve) => {
this.windowId = await getWindowId()
dialog.showMessageBox(
BrowserWindow.fromId(this.windowId),
BrowserWindow.fromId(this.windowId)!,
options
).then((res) => {
resolve({
@@ -118,6 +128,48 @@ class GuiApi implements IGuiApi {
})
})
}
/**
* get picgo config/data path
*/
async getConfigPath () {
const currentConfigPath = dbPathChecker()
const galleryDBPath = getGalleryDBPath().dbPath
return {
defaultConfigPath,
currentConfigPath,
galleryDBPath
}
}
get galleryDB (): DBStore {
return new Proxy<DBStore>(GalleryDB.getInstance(), {
get (target, prop: keyof DBStore) {
if (prop === 'removeById') {
return new Proxy(GalleryDB.getInstance().removeById, {
apply (target, ctx, args) {
return new Promise((resolve) => {
const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({
title: '警告',
message: '有插件正在试图删除一些相册图片,是否继续',
type: 'info',
buttons: ['Yes', 'No']
}).then(res => {
if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args))
} else {
resolve(undefined)
}
})
})
}
})
}
return Reflect.get(target, prop)
}
})
}
}
export default GuiApi
+1 -1
View File
@@ -28,7 +28,7 @@ function initEventCenter () {
[GET_SETTING_WINDOW_ID]: busCallGetSettingWindowId,
[CREATE_APP_MENU]: createMenu
}
for (let i in eventList) {
for (const i in eventList) {
bus.on(i, eventList[i])
}
}
+82 -6
View File
@@ -1,20 +1,33 @@
import {
app,
ipcMain,
shell,
Notification,
IpcMainEvent
IpcMainEvent,
BrowserWindow
} from 'electron'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import uploader from 'apis/app/uploader'
import pasteTemplate from '#/utils/pasteTemplate'
import db from '#/datastore'
import pasteTemplate from '~/main/utils/pasteTemplate'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import server from '~/main/server'
import getPicBeds from '~/main/utils/getPicBeds'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import bus from '@core/bus'
import {
TOGGLE_SHORTKEY_MODIFIED_MODE
TOGGLE_SHORTKEY_MODIFIED_MODE,
OPEN_DEVTOOLS,
SHOW_MINI_PAGE_MENU,
MINIMIZE_WINDOW,
CLOSE_WINDOW,
SHOW_MAIN_PAGE_MENU,
SHOW_UPLOAD_PAGE_MENU,
OPEN_USER_STORE_FILE,
OPEN_URL,
RELOAD_APP,
SHOW_PLUGIN_PAGE_MENU,
SET_MINI_WINDOW_POS
} from '#/events/constants'
import {
uploadClipboardFiles,
@@ -22,6 +35,10 @@ import {
} from '~/main/apis/app/uploader/apis'
import picgoCoreIPC from './picgoCoreIPC'
import { handleCopyUrl } from '~/main/utils/common'
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildUploadPageMenu } from './remotes/menu'
import path from 'path'
const STORE_PATH = app.getPath('userData')
export default {
listen () {
@@ -32,7 +49,7 @@ export default {
const img = await uploader.setWebContents(trayWindow.webContents).upload()
if (img !== false) {
const pasteStyle = db.get('settings.pasteStyle') || 'markdown'
handleCopyUrl(pasteTemplate(pasteStyle, img[0]))
handleCopyUrl(pasteTemplate(pasteStyle, img[0], db.get('settings.customLink')))
const notification = new Notification({
title: '上传成功',
body: img[0].imgUrl!,
@@ -40,7 +57,7 @@ export default {
icon: img[0].imgUrl
})
notification.show()
db.insert('uploaded', img[0])
await GalleryDB.getInstance().insert(img[0])
trayWindow.webContents.send('clipboardFiles', [])
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('updateGallery')
@@ -141,6 +158,65 @@ export default {
ipcMain.on('updateServer', () => {
server.restart()
})
ipcMain.on(OPEN_DEVTOOLS, (event: IpcMainEvent) => {
event.sender.openDevTools()
})
// menu & window methods
ipcMain.on(SHOW_MINI_PAGE_MENU, () => {
const window = windowManager.get(IWindowList.MINI_WINDOW)!
const menu = buildMiniPageMenu()
menu.popup({
window
})
})
ipcMain.on(SHOW_MAIN_PAGE_MENU, () => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildMainPageMenu()
menu.popup({
window
})
})
ipcMain.on(SHOW_UPLOAD_PAGE_MENU, () => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildUploadPageMenu()
menu.popup({
window
})
})
ipcMain.on(SHOW_PLUGIN_PAGE_MENU, (evt: IpcMainEvent, plugin: IPicGoPlugin) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const menu = buildPluginPageMenu(plugin)
menu.popup({
window
})
})
ipcMain.on(MINIMIZE_WINDOW, () => {
const window = BrowserWindow.getFocusedWindow()
window?.minimize()
})
ipcMain.on(CLOSE_WINDOW, () => {
const window = BrowserWindow.getFocusedWindow()
if (process.platform === 'linux') {
window?.hide()
} else {
window?.close()
}
})
ipcMain.on(OPEN_USER_STORE_FILE, (evt: IpcMainEvent, filePath: string) => {
const abFilePath = path.join(STORE_PATH, filePath)
shell.openPath(abFilePath)
})
ipcMain.on(OPEN_URL, (evt: IpcMainEvent, url: string) => {
shell.openExternal(url)
})
ipcMain.on(RELOAD_APP, () => {
app.relaunch()
app.exit(0)
})
ipcMain.on(SET_MINI_WINDOW_POS, (evt: IpcMainEvent, pos: IMiniWindowPos) => {
const window = BrowserWindow.getFocusedWindow()
window?.setBounds(pos)
})
},
dispose () {}
}
+146 -71
View File
@@ -5,10 +5,10 @@ import {
shell,
IpcMainEvent,
ipcMain,
app
clipboard
} from 'electron'
import PicGoCore from '~/universal/types/picgo'
import { IPicGoHelperType } from '#/types/enum'
import { IPasteStyle, IPicGoHelperType } from '#/types/enum'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import picgo from '@core/picgo'
import { handleStreamlinePluginName } from '~/universal/utils/common'
@@ -16,18 +16,30 @@ import { IGuiMenuItem } from 'picgo/dist/src/types'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import { showNotification } from '~/main/utils/common'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
import {
PICGO_SAVE_CONFIG,
PICGO_GET_CONFIG,
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_OPEN_FILE,
PASTE_TEXT
} from '#/events/constants'
import { GalleryDB } from 'apis/core/datastore'
import { IObject, IFilter } from '@picgo/store/dist/types'
import pasteTemplate from '../utils/pasteTemplate'
// eslint-disable-next-line
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
// const PluginHandler = requireFunc('picgo/dist/lib/PluginHandler').default
const STORE_PATH = app.getPath('userData')
const STORE_PATH = path.dirname(dbPathChecker())
// const CONFIG_PATH = path.join(STORE_PATH, '/data.json')
type PicGoNotice = {
title: string,
body: string[]
}
interface GuiMenuItem {
label: string
handle: (arg0: PicGoCore, arg1: GuiApi) => Promise<void>
@@ -50,7 +62,7 @@ const getConfig = (name: string, type: IPicGoHelperType, ctx: PicGoCore) => {
}
const handleConfigWithFunction = (config: any[]) => {
for (let i in config) {
for (const i in config) {
if (typeof config[i].default === 'function') {
config[i].default = config[i].default()
}
@@ -64,7 +76,7 @@ const handleConfigWithFunction = (config: any[]) => {
const getPluginList = (): IPicGoPlugin[] => {
const pluginList = picgo.pluginLoader.getFullList()
const list = []
for (let i in pluginList) {
for (const i in pluginList) {
const plugin = picgo.pluginLoader.getPlugin(pluginList[i])!
const pluginPath = path.join(STORE_PATH, `/node_modules/${pluginList[i]}`)
const pluginPKG = requireFunc(path.join(pluginPath, 'package.json'))
@@ -142,39 +154,37 @@ const handlePluginInstall = () => {
})
}
const handlePluginUninstall = () => {
ipcMain.on('uninstallPlugin', async (event: IpcMainEvent, msg: string) => {
const dispose = handleNPMError()
const res = await picgo.pluginHandler.uninstall([msg])
if (res.success) {
event.sender.send('uninstallSuccess', res.body[0])
shortKeyHandler.unregisterPluginShortKey(res.body[0])
} else {
showNotification({
title: '插件卸载失败',
body: res.body as string
})
}
event.sender.send('hideLoading')
dispose()
})
const handlePluginUninstall = async (fullName: string) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const dispose = handleNPMError()
const res = await picgo.pluginHandler.uninstall([fullName])
if (res.success) {
window.webContents.send('uninstallSuccess', res.body[0])
shortKeyHandler.unregisterPluginShortKey(res.body[0])
} else {
showNotification({
title: '插件卸载失败',
body: res.body as string
})
}
window.webContents.send('hideLoading')
dispose()
}
const handlePluginUpdate = () => {
ipcMain.on('updatePlugin', async (event: IpcMainEvent, msg: string) => {
const dispose = handleNPMError()
const res = await picgo.pluginHandler.update([msg])
if (res.success) {
event.sender.send('updateSuccess', res.body[0])
} else {
showNotification({
title: '插件更新失败',
body: res.body as string
})
}
event.sender.send('hideLoading')
dispose()
})
const handlePluginUpdate = async (fullName: string) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const dispose = handleNPMError()
const res = await picgo.pluginHandler.update([fullName])
if (res.success) {
window.webContents.send('updateSuccess', res.body[0])
} else {
showNotification({
title: '插件更新失败',
body: res.body as string
})
}
window.webContents.send('hideLoading')
dispose()
}
const handleNPMError = (): IDispose => {
@@ -207,15 +217,15 @@ const handleGetPicBedConfig = () => {
})
}
// TODO: remove it
const handlePluginActions = () => {
ipcMain.on('pluginActions', (event: IpcMainEvent, name: string, label: string) => {
const plugin = picgo.pluginLoader.getPlugin(name)
const guiApi = new GuiApi()
if (plugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = plugin.guiMenu(picgo)
menu.forEach(item => {
if (item.label === label) {
item.handle(picgo, guiApi)
item.handle(picgo, GuiApi.getInstance())
}
})
}
@@ -224,43 +234,104 @@ const handlePluginActions = () => {
const handleRemoveFiles = () => {
ipcMain.on('removeFiles', (event: IpcMainEvent, files: ImgInfo[]) => {
const guiApi = new GuiApi()
setTimeout(() => {
picgo.emit('remove', files, guiApi)
picgo.emit('remove', files, GuiApi.getInstance())
}, 500)
})
}
const handlePicGoSaveData = () => {
ipcMain.on('picgoSaveData', (event: IpcMainEvent, data: IObj) => {
const handlePicGoSaveConfig = () => {
ipcMain.on(PICGO_SAVE_CONFIG, (event: IpcMainEvent, data: IObj) => {
picgo.saveConfig(data)
})
}
const handlePicGoGetConfig = () => {
ipcMain.on(PICGO_GET_CONFIG, (event: IpcMainEvent, key: string | undefined, callbackId: string) => {
const result = picgo.getConfig(key)
event.sender.send(PICGO_GET_CONFIG, result, callbackId)
})
}
const handleImportLocalPlugin = () => {
ipcMain.on('importLocalPlugin', (event: IpcMainEvent) => {
ipcMain.on('importLocalPlugin', async (event: IpcMainEvent) => {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
dialog.showOpenDialog(settingWindow, {
const res = await dialog.showOpenDialog(settingWindow, {
properties: ['openDirectory']
}, async (filePath: string[]) => {
if (filePath.length > 0) {
const res = await picgo.pluginHandler.install(filePath)
if (res.success) {
const list = getPluginList()
event.sender.send('pluginList', list)
showNotification({
title: '导入插件成功',
body: ''
})
} else {
showNotification({
title: '导入插件失败',
body: res.body as string
})
}
}
event.sender.send('hideLoading')
})
const filePaths = res.filePaths
if (filePaths.length > 0) {
const res = await picgo.pluginHandler.install(filePaths)
if (res.success) {
const list = getPluginList()
event.sender.send('pluginList', list)
showNotification({
title: '导入插件成功',
body: ''
})
} else {
showNotification({
title: '导入插件失败',
body: res.body as string
})
}
}
event.sender.send('hideLoading')
})
}
const handlePicGoGalleryDB = () => {
ipcMain.on(PICGO_GET_DB, async (event: IpcMainEvent, filter: IFilter, callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.get(filter)
event.sender.send(PICGO_GET_DB, res, callbackId)
})
ipcMain.on(PICGO_INSERT_DB, async (event: IpcMainEvent, value: IObject, callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.insert(value)
event.sender.send(PICGO_INSERT_DB, res, callbackId)
})
ipcMain.on(PICGO_INSERT_MANY_DB, async (event: IpcMainEvent, value: IObject[], callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.insertMany(value)
event.sender.send(PICGO_INSERT_MANY_DB, res, callbackId)
})
ipcMain.on(PICGO_UPDATE_BY_ID_DB, async (event: IpcMainEvent, id: string, value: IObject[], callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.updateById(id, value)
event.sender.send(PICGO_UPDATE_BY_ID_DB, res, callbackId)
})
ipcMain.on(PICGO_GET_BY_ID_DB, async (event: IpcMainEvent, id: string, callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.getById(id)
event.sender.send(PICGO_GET_BY_ID_DB, res, callbackId)
})
ipcMain.on(PICGO_REMOVE_BY_ID_DB, async (event: IpcMainEvent, id: string, callbackId: string) => {
const dbStore = GalleryDB.getInstance()
const res = await dbStore.removeById(id)
event.sender.send(PICGO_REMOVE_BY_ID_DB, res, callbackId)
})
ipcMain.handle(PASTE_TEXT, async (item: ImgInfo, copy = true) => {
const pasteStyle = picgo.getConfig<IPasteStyle>('settings.pasteStyle') || IPasteStyle.MARKDOWN
const customLink = picgo.getConfig<string>('settings.customLink')
const txt = pasteTemplate(pasteStyle, item, customLink)
if (copy) {
clipboard.writeText(txt)
}
return txt
})
}
const handleOpenFile = () => {
ipcMain.on(PICGO_OPEN_FILE, (event: IpcMainEvent, fileName: string) => {
const abFilePath = path.join(STORE_PATH, fileName)
shell.openPath(abFilePath)
})
}
@@ -268,12 +339,16 @@ export default {
listen () {
handleGetPluginList()
handlePluginInstall()
handlePluginUninstall()
handlePluginUpdate()
handleGetPicBedConfig()
handlePluginActions()
handleRemoveFiles()
handlePicGoSaveData()
handlePicGoSaveConfig()
handlePicGoGetConfig()
handlePicGoGalleryDB()
handleImportLocalPlugin()
}
handleOpenFile()
},
// TODO: separate to single file
handlePluginUninstall,
handlePluginUpdate
}
+286
View File
@@ -0,0 +1,286 @@
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from 'apis/app/window/constants'
import { Menu, BrowserWindow, app, dialog } from 'electron'
import getPicBeds from '~/main/utils/getPicBeds'
import picgo from '@core/picgo'
import {
uploadClipboardFiles
} from '~/main/apis/app/uploader/apis'
import { privacyManager } from '~/main/utils/privacyManager'
import pkg from 'root/package.json'
import GuiApi from 'apis/gui'
import PicGoCore from '~/universal/types/picgo'
import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN } from '~/universal/events/constants'
import picgoCoreIPC from '~/main/events/picgoCoreIPC'
interface GuiMenuItem {
label: string
handle: (arg0: PicGoCore, arg1: GuiApi) => Promise<void>
}
const buildMiniPageMenu = () => {
const picBeds = getPicBeds()
const current = picgo.getConfig('picBed.uploader')
const submenu = picBeds.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: current === item.type,
click () {
picgo.saveConfig({
'picBed.current': item.type,
'picBed.uploader': item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
}
}
})
const template = [
{
label: '打开详细窗口',
click () {
windowManager.get(IWindowList.SETTING_WINDOW)!.show()
if (windowManager.has(IWindowList.MINI_WINDOW)) {
windowManager.get(IWindowList.MINI_WINDOW)!.hide()
}
}
},
{
label: '选择默认图床',
type: 'submenu',
submenu
},
{
label: '剪贴板图片上传',
click () {
uploadClipboardFiles()
}
},
{
label: '隐藏窗口',
click () {
BrowserWindow.getFocusedWindow()!.hide()
}
},
{
label: '隐私协议',
click () {
privacyManager.show(false)
}
},
{
label: '重启应用',
click () {
app.relaunch()
app.exit(0)
}
},
{
role: 'quit',
label: '退出'
}
]
// @ts-ignore
return Menu.buildFromTemplate(template)
}
const buildMainPageMenu = () => {
const template = [
{
label: '关于',
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
}
},
{
label: '赞助PicGo',
click () {
// TODO: show donation
}
},
{
label: '生成图床配置二维码',
click () {
// TODO: qrcode
// _this.qrcodeVisible = true
}
},
{
label: '隐私协议',
click () {
privacyManager.show(false)
}
}
]
// @ts-ignore
return Menu.buildFromTemplate(template)
}
const buildUploadPageMenu = () => {
const picBeds = getPicBeds()
const currentPicBed = picgo.getConfig('picBed.uploader')
const submenu = picBeds.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: currentPicBed === item.type,
click () {
picgo.saveConfig({
'picBed.current': item.type,
'picBed.uploader': item.type
})
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.get(IWindowList.SETTING_WINDOW)!.webContents.send('syncPicBed')
}
}
}
})
// @ts-ignore
return Menu.buildFromTemplate(submenu)
}
// TODO: separate to single file
const handleRestoreState = (item: string, name: string): void => {
if (item === 'uploader') {
const current = picgo.getConfig('picBed.current')
if (current === name) {
picgo.saveConfig({
'picBed.current': 'smms',
'picBed.uploader': 'smms'
})
}
}
if (item === 'transformer') {
const current = picgo.getConfig('picBed.transformer')
if (current === name) {
picgo.saveConfig({
'picBed.transformer': 'path'
})
}
}
}
const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const menu = [{
label: '启用插件',
enabled: !plugin.enabled,
click () {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: true
})
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, true)
}
}, {
label: '禁用插件',
enabled: plugin.enabled,
click () {
picgo.saveConfig({
[`picgoPlugins.${plugin.fullName}`]: false
})
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, false)
if (plugin.config.transformer.name) {
handleRestoreState('transformer', plugin.config.transformer.name)
}
if (plugin.config.uploader.name) {
handleRestoreState('uploader', plugin.config.uploader.name)
}
}
}, {
label: '卸载插件',
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
picgoCoreIPC.handlePluginUninstall(plugin.fullName)
}
}, {
label: '更新插件',
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
picgoCoreIPC.handlePluginUpdate(plugin.fullName)
}
}]
for (const i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
label: `配置${i} - ${plugin.config[i].fullName || plugin.config[i].name}`,
click () {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const currentType = i
const configName = plugin.config[i].fullName || plugin.config[i].name
const config = plugin.config[i].config
window.webContents.send(PICGO_CONFIG_PLUGIN, currentType, configName, config)
}
}
menu.push(obj)
}
}
// handle transformer
if (plugin.config.transformer.name) {
const currentTransformer = picgo.getConfig<string>('picBed.transformer') || 'path'
const pluginTransformer = plugin.config.transformer.name
const obj = {
label: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`,
click () {
const transformer = plugin.config.transformer.name
const currentTransformer = picgo.getConfig<string>('picBed.transformer') || 'path'
if (currentTransformer === transformer) {
picgo.saveConfig({
'picBed.transformer': 'path'
})
} else {
picgo.saveConfig({
'picBed.transformer': transformer
})
}
}
}
menu.push(obj)
}
// plugin custom menus
if (plugin.guiMenu) {
menu.push({
// @ts-ignore
type: 'separator'
})
for (const i of plugin.guiMenu) {
menu.push({
label: i.label,
click () {
// ipcRenderer.send('pluginActions', plugin.fullName, i.label)
const picgPlugin = picgo.pluginLoader.getPlugin(plugin.fullName)
if (picgPlugin?.guiMenu?.(picgo)?.length) {
const menu: GuiMenuItem[] = picgPlugin.guiMenu(picgo)
menu.forEach(item => {
if (item.label === i.label) {
item.handle(picgo, GuiApi.getInstance())
}
})
}
}
})
}
}
// @ts-ignore
return Menu.buildFromTemplate(menu)
}
export {
buildMiniPageMenu,
buildMainPageMenu,
buildUploadPageMenu,
buildPluginPageMenu
}
+3 -3
View File
@@ -1,14 +1,14 @@
import { app } from 'electron'
import fse from 'fs-extra'
import path from 'path'
import dayjs from 'dayjs'
import util from 'util'
const STORE_PATH = app.getPath('userData')
import { dbPathDir } from 'apis/core/datastore/dbChecker'
const STORE_PATH = dbPathDir()
const LOG_PATH = path.join(STORE_PATH, '/picgo.log')
// since the error may occur in picgo-core
// so we can't use the log from picgo
const loggerWriter = (error: Error) => {
export const loggerWriter = (error: Error) => {
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ERROR] startup error`
if (error?.stack) {
log += `\n------Error Stack Begin------\n${util.format(error.stack)}\n-------Error Stack End-------\n`
+23 -10
View File
@@ -16,7 +16,8 @@ import busEventList from '~/main/events/busEventList'
import { IWindowList } from 'apis/app/window/constants'
import windowManager from 'apis/app/window/windowManager'
import {
updateShortKeyFromVersion212
updateShortKeyFromVersion212,
migrateGalleryFromVersion230
} from '~/main/migrate'
import {
uploadChoosedFiles,
@@ -29,10 +30,11 @@ import server from '~/main/server/index'
import updateChecker from '~/main/utils/updateChecker'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import { getUploadFiles } from '~/main/utils/handleArgv'
import db from '#/datastore'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import bus from '@core/bus'
import { privacyManager } from '~/main/utils/privacyManager'
import logger from 'apis/core/picgo/logger'
import picgo from 'apis/core/picgo'
const isDevelopment = process.env.NODE_ENV !== 'production'
@@ -54,7 +56,7 @@ const handleStartUpFiles = (argv: string[], cwd: string) => {
}
class LifeCycle {
private beforeReady () {
private async beforeReady () {
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
// fix the $PATH in macOS
fixPath()
@@ -62,15 +64,18 @@ class LifeCycle {
ipcList.listen()
busEventList.listen()
updateShortKeyFromVersion212(db, db.get('settings.shortKey'))
await migrateGalleryFromVersion230(db, GalleryDB.getInstance(), picgo)
}
private onReady () {
app.on('ready', async () => {
const readyFunction = async () => {
console.log('on ready')
createProtocol('picgo')
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
} catch (e: any) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
@@ -92,15 +97,21 @@ class LifeCycle {
handleStartUpFiles(process.argv, process.cwd())
}
if (global.notificationList?.length > 0) {
while (global.notificationList.length) {
if (global.notificationList && global.notificationList?.length > 0) {
while (global.notificationList?.length) {
const option = global.notificationList.pop()
const notice = new Notification(option!)
notice.show()
}
}
})
}
if (!app.isReady()) {
app.on('ready', readyFunction)
} else {
readyFunction()
}
}
private onRunning () {
app.on('second-instance', (event, commandLine, workingDirectory) => {
logger.info('detect second instance')
@@ -135,6 +146,7 @@ class LifeCycle {
process.env.XDG_CURRENT_DESKTOP = 'Unity'
}
}
private onQuit () {
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
@@ -162,12 +174,13 @@ class LifeCycle {
}
}
}
launchApp () {
async launchApp () {
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
this.beforeReady()
await this.beforeReady()
this.onReady()
this.onRunning()
this.onQuit()
+24 -3
View File
@@ -1,6 +1,10 @@
import DB from '#/datastore'
import { DBStore } from '@picgo/store'
import ConfigStore from '~/main/apis/core/datastore'
import path from 'path'
import fse from 'fs-extra'
import PicGoCore from '#/types/picgo'
// from v2.1.2
const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
// #557 极端情况可能会出现配置不存在,需要重新写入
if (shortKeyConfig === undefined) {
const defaultShortKeyConfig = {
@@ -28,6 +32,23 @@ const updateShortKeyFromVersion212 = (db: typeof DB, shortKeyConfig: IShortKeyCo
return false
}
const migrateGalleryFromVersion230 = async (configDB: typeof ConfigStore, galleryDB: DBStore, picgo: PicGoCore) => {
const originGallery: ImgInfo[] = configDB.get('uploaded')
const configPath = configDB.getConfigPath()
const configBakPath = path.join(path.dirname(configPath), 'config.bak.json')
// migrate gallery from config to gallery db
if (originGallery && Array.isArray(originGallery) && originGallery?.length > 0) {
if (fse.existsSync(configBakPath)) {
fse.copyFileSync(configPath, configBakPath)
}
await galleryDB.insertMany(originGallery)
picgo.saveConfig({
uploaded: []
})
}
}
export {
updateShortKeyFromVersion212
updateShortKeyFromVersion212,
migrateGalleryFromVersion230
}
+10 -3
View File
@@ -29,6 +29,7 @@ class Server {
}
this.httpServer = http.createServer(this.handleRequest)
}
private checkIfConfigIsValid (config: IObj | undefined) {
if (config && config.port && config.host && (config.enable !== undefined)) {
return true
@@ -36,6 +37,7 @@ class Server {
return false
}
}
private handleRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
if (request.method === 'POST') {
if (!routers.getHandler(request.url!)) {
@@ -57,8 +59,8 @@ class Server {
request.on('end', () => {
try {
postObj = (body === '') ? {} : JSON.parse(body)
} catch (err) {
logger.error(`[PicGo Server]`, err)
} catch (err: any) {
logger.error('[PicGo Server]', err)
return handleResponse({
response,
body: {
@@ -67,7 +69,7 @@ class Server {
}
})
}
logger.info(`[PicGo Server] get the request`)
logger.info('[PicGo Server] get the request', body)
const handler = routers.getHandler(request.url!)
handler!({
...postObj,
@@ -81,6 +83,7 @@ class Server {
response.end()
}
}
// port as string is a bug
private listen = (port: number | string) => {
logger.info(`[PicGo Server] is listening at ${port}`)
@@ -102,17 +105,21 @@ class Server {
}
})
}
startup () {
console.log('startup', this.config.enable)
if (this.config.enable) {
this.listen(this.config.port)
}
}
shutdown (hasStarted?: boolean) {
this.httpServer.close()
if (!hasStarted) {
logger.info('[PicGo Server] shutdown')
}
}
restart () {
this.config = picgo.getConfig('settings.server')
this.shutdown()
+1
View File
@@ -4,6 +4,7 @@ class Router {
get (url: string, callback: routeHandler): void {
this.router.set(url, callback)
}
post (url: string, callback: routeHandler): void {
this.router.set(url, callback)
}
+19 -14
View File
@@ -1,12 +1,16 @@
import router from './router'
import {
uploadWithClipboardFiles,
uploadWithFiles
} from '@core/bus/apis'
import {
handleResponse
} from './utils'
import logger from '@core/picgo/logger'
import windowManager from 'apis/app/window/windowManager'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import path from 'path'
import { dbPathDir } from 'apis/core/datastore/dbChecker'
const STORE_PATH = dbPathDir()
const LOG_PATH = path.join(STORE_PATH, 'picgo.log')
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
router.post('/upload', async ({
response,
@@ -19,13 +23,13 @@ router.post('/upload', async ({
if (list.length === 0) {
// upload with clipboard
logger.info('[PicGo Server] upload clipboard file')
const res = await uploadWithClipboardFiles()
if (res.success) {
const res = await uploadClipboardFiles()
if (res) {
handleResponse({
response,
body: {
success: true,
result: res.result
result: [res]
}
})
} else {
@@ -33,7 +37,7 @@ router.post('/upload', async ({
response,
body: {
success: false,
message: 'upload error'
message: errorMessage
}
})
}
@@ -45,13 +49,14 @@ router.post('/upload', async ({
path: item
}
})
const res = await uploadWithFiles(pathList)
if (res.success) {
const win = windowManager.getAvailableWindow()
const res = await uploadChoosedFiles(win.webContents, pathList)
if (res.length) {
handleResponse({
response,
body: {
success: true,
result: res.result
result: res
}
})
} else {
@@ -59,18 +64,18 @@ router.post('/upload', async ({
response,
body: {
success: false,
message: 'upload error'
message: errorMessage
}
})
}
}
} catch (err) {
} catch (err: any) {
logger.error(err)
handleResponse({
response,
body: {
success: false,
message: err
message: errorMessage
}
})
}
+17 -18
View File
@@ -1,19 +1,12 @@
import fs from 'fs-extra'
import path from 'path'
import os from 'os'
import { remote, app } from 'electron'
import pkg from 'root/package.json'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')
function injectPicGoVersion () {
global.PICGO_GUI_VERSION = pkg.version
global.PICGO_CORE_VERSION = pkg.dependencies.picgo.replace('^', '')
}
const configPath = dbPathChecker()
const CONFIG_DIR = path.dirname(configPath)
function beforeOpen () {
injectPicGoVersion()
if (process.platform === 'darwin') {
resolveMacWorkFlow()
}
@@ -40,8 +33,8 @@ function resolveMacWorkFlow () {
* 初始化剪贴板生成图片的脚本
*/
function resolveClipboardImageGenerator () {
let clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(STORE_PATH, 'windows10.ps1'))) {
const clipboardFiles = getClipboardFiles()
if (!fs.pathExistsSync(path.join(CONFIG_DIR, 'windows10.ps1'))) {
clipboardFiles.forEach(item => {
fs.copyFileSync(item.origin, item.dest)
})
@@ -52,26 +45,32 @@ function resolveClipboardImageGenerator () {
}
function diffFilesAndUpdate (filePath1: string, filePath2: string) {
let file1 = fs.readFileSync(filePath1)
let file2 = fs.readFileSync(filePath2)
try {
const file1 = fs.existsSync(filePath1) && fs.readFileSync(filePath1)
const file2 = fs.existsSync(filePath1) && fs.readFileSync(filePath2)
if (!file1.equals(file2)) {
if (!file1 || !file2 || !file1.equals(file2)) {
fs.copyFileSync(filePath1, filePath2)
}
} catch (e) {
console.error(e)
fs.copyFileSync(filePath1, filePath2)
}
}
function getClipboardFiles () {
let files = [
const files = [
'/linux.sh',
'/mac.applescript',
'/windows.ps1',
'/windows10.ps1'
'/windows10.ps1',
'/wsl.sh'
]
return files.map(item => {
return {
origin: path.join(__static, item),
dest: path.join(STORE_PATH, item)
dest: path.join(CONFIG_DIR, item)
}
})
}
+2 -2
View File
@@ -1,4 +1,4 @@
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import { clipboard, Notification, dialog } from 'electron'
export const handleCopyUrl = (str: string): void => {
@@ -34,7 +34,7 @@ export const showNotification = (options: IPrivateShowNotificationOption = {
}
export const showMessageBox = (options: any) => {
return new Promise<IShowMessageBoxResult>(async (resolve, reject) => {
return new Promise<IShowMessageBoxResult>(async (resolve) => {
dialog.showMessageBox(
options
).then((res) => {
+1 -1
View File
@@ -39,7 +39,7 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger
path: item
}
} else {
let tempPath = path.join(cwd, item)
const tempPath = path.join(cwd, item)
if (fs.existsSync(tempPath)) {
return {
path: tempPath
+31
View File
@@ -0,0 +1,31 @@
import { IPasteStyle } from '#/types/enum'
const formatCustomLink = (customLink: string, item: ImgInfo) => {
const fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
const url = item.url || item.imgUrl
const formatObj = {
url,
fileName
}
const keys = Object.keys(formatObj) as ['url', 'fileName']
keys.forEach(item => {
if (customLink.indexOf(`$${item}`) !== -1) {
const reg = new RegExp(`\\$${item}`, 'g')
customLink = customLink.replace(reg, formatObj[item])
}
})
return customLink
}
export default (style: IPasteStyle, item: ImgInfo, customLink: string | undefined) => {
const url = item.url || item.imgUrl
const _customLink = customLink || '$url'
const tpl = {
markdown: `![](${url})`,
HTML: `<img src="${url}"/>`,
URL: url,
UBB: `[IMG]${url}[/IMG]`,
Custom: formatCustomLink(_customLink, item)
}
return tpl[style]
}
+1 -1
View File
@@ -1,4 +1,4 @@
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import { ipcMain } from 'electron'
import { showMessageBox } from '~/main/utils/common'
import { SHOW_PRIVACY_MESSAGE } from '~/universal/events/constants'
+1 -1
View File
@@ -1,5 +1,5 @@
import { dialog, shell } from 'electron'
import db from '#/datastore'
import db from '~/main/apis/core/datastore'
import axios from 'axios'
import pkg from 'root/package.json'
import { lt } from 'semver'
+20 -23
View File
@@ -8,31 +8,28 @@
</el-switch>
</div>
</template>
<script>
export default {
name: 'choose-pic-bed',
props: {
type: String,
label: String
},
data () {
return {
value: false
}
},
created () {
if (this.type === this.$db.get('picBed.current')) {
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component({
name: 'choose-pic-bed'
})
export default class extends Vue {
value = false
@Prop() type!: string
@Prop() label!: string
async created () {
const current = await this.getConfig<string>('picBed.current')
if (this.type === current) {
this.value = true
}
},
methods: {
choosePicBed (val) {
this.letPicGoSaveData({
'picBed.current': this.type,
'picBed.uploader': this.type
})
this.$emit('update:choosed', this.type)
}
}
choosePicBed () {
this.saveConfig({
'picBed.current': this.type,
'picBed.uploader': this.type
})
this.$emit('update:choosed', this.type)
}
}
</script>
+40 -17
View File
@@ -72,7 +72,7 @@ import { cloneDeep, union } from 'lodash'
})
export default class extends Vue {
@Prop() private config!: any[]
@Prop() readonly type!: string
@Prop() readonly type!: 'uploader' | 'transformer' | 'plugin'
@Prop() readonly id!: string
configList = []
ruleForm = {}
@@ -81,13 +81,49 @@ export default class extends Vue {
immediate: true
})
handleConfigChange (val: any) {
this.handleConfig(val)
}
async validate () {
return new Promise((resolve) => {
// @ts-ignore
this.$refs.form.validate((valid: boolean) => {
if (valid) {
resolve(this.ruleForm)
} else {
resolve(false)
return false
}
})
})
}
getConfigType () {
switch (this.type) {
case 'plugin': {
return this.id
}
case 'uploader': {
return `picBed.${this.id}`
}
case 'transformer': {
return `transformer.${this.id}`
}
default:
return 'unknown'
}
}
async handleConfig (val: any) {
this.ruleForm = Object.assign({}, {})
const config = this.$db.get(`picBed.${this.id}`)
const config = await this.getConfig<IPicGoPluginConfig>(this.getConfigType())
if (val.length > 0) {
this.configList = cloneDeep(val).map((item: any) => {
let defaultValue = item.default !== undefined
? item.default : item.type === 'checkbox'
? [] : null
? item.default
: item.type === 'checkbox'
? []
: null
if (item.type === 'checkbox') {
const defaults = item.choices.filter((i: any) => {
return i.checked
@@ -102,19 +138,6 @@ export default class extends Vue {
})
}
}
async validate () {
return new Promise((resolve, reject) => {
// @ts-ignore
this.$refs.form.validate((valid: boolean) => {
if (valid) {
resolve(this.ruleForm)
} else {
resolve(false)
return false
}
})
})
}
}
</script>
<style lang='stylus'>
+7 -1
View File
@@ -15,7 +15,7 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { remote, ipcRenderer, IpcRendererEvent } from 'electron'
import { ipcRenderer, IpcRendererEvent } from 'electron'
import {
SHOW_INPUT_BOX,
SHOW_INPUT_BOX_RESPONSE
@@ -30,30 +30,36 @@ export default class extends Vue {
title: '',
placeholder: ''
}
created () {
ipcRenderer.on(SHOW_INPUT_BOX, this.ipcEventHandler)
this.$bus.$on(SHOW_INPUT_BOX, this.initInputBoxValue)
}
ipcEventHandler (evt: IpcRendererEvent, options: IShowInputBoxOption) {
this.initInputBoxValue(options)
}
initInputBoxValue (options: IShowInputBoxOption) {
this.inputBoxValue = options.value || ''
this.inputBoxOptions.title = options.title || ''
this.inputBoxOptions.placeholder = options.placeholder || ''
this.showInputBoxVisible = true
}
handleInputBoxCancel () {
// TODO: RPCServer
this.showInputBoxVisible = false
ipcRenderer.send(SHOW_INPUT_BOX, '')
this.$bus.$emit(SHOW_INPUT_BOX_RESPONSE, '')
}
handleInputBoxConfirm () {
this.showInputBoxVisible = false
ipcRenderer.send(SHOW_INPUT_BOX, this.inputBoxValue)
this.$bus.$emit(SHOW_INPUT_BOX_RESPONSE, this.inputBoxValue)
}
beforeDestroy () {
ipcRenderer.removeListener(SHOW_INPUT_BOX, this.ipcEventHandler)
this.$bus.$off(SHOW_INPUT_BOX)
+24 -125
View File
@@ -17,6 +17,7 @@
:default-active="defaultActive"
@select="handleSelect"
:unique-opened="true"
@open="handleGetPicPeds"
>
<el-menu-item index="upload">
<i class="el-icon-upload"></i>
@@ -91,35 +92,6 @@
</el-col>
</el-row>
</el-dialog>
<el-dialog
title="自定义链接格式"
:visible.sync="customLinkVisible"
>
<el-form
label-position="top"
:model="customLink"
ref="customLink"
:rules="rules"
>
<el-form-item
label="用占位符$url来表示url的位置"
prop="value"
>
<el-input
class="align-center"
v-model="customLink.value"
:autofocus="true"
></el-input>
</el-form-item>
</el-form>
<div>
[]($url)
</div>
<span slot="footer">
<el-button @click="cancelCustomLink">取消</el-button>
<el-button type="primary" @click="confirmCustomLink">确定</el-button>
</span>
</el-dialog>
<el-dialog
class="qrcode-dialog"
top="3vh"
@@ -176,27 +148,18 @@ import { Component, Vue, Watch } from 'vue-property-decorator'
import QrcodeVue from 'qrcode.vue'
import pick from 'lodash/pick'
import pkg from 'root/package.json'
import keyDetect from '@/utils/key-binding'
import {
remote,
ipcRenderer,
IpcRendererEvent,
clipboard
} from 'electron'
import db from '#/datastore'
import mixin from '@/utils/mixin'
import InputBoxDialog from '@/components/InputBoxDialog.vue'
import {
SHOW_PRIVACY_MESSAGE
MINIMIZE_WINDOW,
CLOSE_WINDOW,
SHOW_MAIN_PAGE_MENU
} from '~/universal/events/constants'
const { Menu, dialog, BrowserWindow } = remote
const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) => void) => {
if (!/\$url/.test(value)) {
return callback(new Error('必须含有$url'))
} else {
return callback()
}
}
@Component({
name: 'main-page',
mixins: [mixin],
@@ -208,44 +171,36 @@ const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) =>
export default class extends Vue {
version = process.env.NODE_ENV === 'production' ? pkg.version : 'Dev'
defaultActive = 'upload'
menu: Electron.Menu | null = null
visible = false
keyBindingVisible = false
customLinkVisible = false
customLink = {
value: db.get('customLink') || '$url'
}
rules = {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
}
os = ''
shortKey: IShortKeyMap = {
upload: db.get('shortKey.upload')
}
picBed: IPicBedType[] = []
qrcodeVisible = false
picBedConfigString = ''
choosedPicBedForQRCode: string[] = []
created () {
this.os = process.platform
this.buildMenu()
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
this.handleGetPicPeds()
}
@Watch('choosedPicBedForQRCode')
choosedPicBedForQRCodeChange (val: string[], oldVal: string[]) {
choosedPicBedForQRCodeChange (val: string[]) {
if (val.length > 0) {
this.$nextTick(() => {
const picBedConfig = db.get('picBed')
this.$nextTick(async () => {
const picBedConfig = await this.getConfig('picBed')
const config = pick(picBedConfig, ...this.choosedPicBedForQRCode)
this.picBedConfigString = JSON.stringify(config)
})
}
}
handleGetPicPeds = () => {
ipcRenderer.send('getPicBeds')
}
handleSelect (index: string) {
const type = index.match(/picbeds-/)
if (type === null) {
@@ -268,94 +223,38 @@ export default class extends Vue {
}
}
}
minimizeWindow () {
const window = BrowserWindow.getFocusedWindow()
window!.minimize()
ipcRenderer.send(MINIMIZE_WINDOW)
}
closeWindow () {
const window = BrowserWindow.getFocusedWindow()
if (process.platform === 'linux') {
window!.hide()
} else {
window!.close()
}
}
buildMenu () {
const _this = this
const template = [
{
label: '关于',
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
}
},
{
label: '赞助PicGo',
click () {
_this.visible = true
}
},
{
label: '生成图床配置二维码',
click () {
_this.qrcodeVisible = true
}
},
{
label: '隐私协议',
click () {
ipcRenderer.send(SHOW_PRIVACY_MESSAGE)
}
}
]
this.menu = Menu.buildFromTemplate(template)
ipcRenderer.send(CLOSE_WINDOW)
}
openDialog () {
// this.menu!.popup(remote.getCurrentWindow())
this.menu!.popup()
}
keyDetect (type: string, event: KeyboardEvent) {
this.shortKey[type] = keyDetect(event).join('+')
}
cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = db.get('shortKey')
}
cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = db.get('customLink') || '$url'
}
confirmCustomLink () {
// @ts-ignore
this.$refs.customLink.validate((valid: boolean) => {
if (valid) {
db.set('customLink', this.customLink.value)
this.customLinkVisible = false
ipcRenderer.send('updateCustomLink')
} else {
return false
}
})
ipcRenderer.send(SHOW_MAIN_PAGE_MENU)
}
openMiniWindow () {
ipcRenderer.send('openMiniWindow')
}
handleCopyPicBedConfig () {
clipboard.writeText(this.picBedConfigString)
this.$message.success('图床配置复制成功')
}
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
this.picBed = picBeds
}
beforeRouteEnter (to: any, from: any, next: any) {
beforeRouteEnter (to: any, next: any) {
next((vm: this) => {
vm.defaultActive = to.name
})
}
beforeDestroy () {
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
+109 -78
View File
@@ -71,12 +71,12 @@
<el-col :span="20" :offset="2">
<el-row :gutter="16">
<gallerys
:images="images"
:images="filterList"
:index="idx"
@close="handleClose"
:options="options"
></gallerys>
<el-col :span="6" v-for="(item, index) in images" :key="item.id" class="gallery-list__img">
<el-col :span="6" v-for="(item, index) in filterList" :key="item.id" class="gallery-list__img">
<div
class="gallery-list__item"
@click="zoomImage(index)"
@@ -110,8 +110,9 @@
<script lang="ts">
// @ts-ignore
import gallerys from 'vue-gallery'
import pasteStyle from '#/utils/pasteTemplate'
import { Component, Vue, Watch } from 'vue-property-decorator'
import { IResult } from '@picgo/store/dist/types'
import { PASTE_TEXT } from '#/events/constants'
import {
ipcRenderer,
clipboard,
@@ -131,11 +132,13 @@ export default class extends Vue {
urlProperty: 'imgUrl',
closeOnSlideClick: true
}
dialogVisible = false
imgInfo = {
id: '',
imgUrl: ''
}
choosedList: IObjT<boolean> = {}
choosedPicBed: string[] = []
lastChoosed: number = -1
@@ -150,37 +153,49 @@ export default class extends Vue {
UBB: 'UBB',
Custom: 'Custom'
}
picBed: IPicBedType[] = []
@Watch('$route')
handleRouteUpdate (to: any, from: any) {
console.log(to, from)
if (from.name === 'gallery') {
this.clearChoosedList()
}
if (to.name === 'gallery') {
this.updateGallery()
}
}
created () {
ipcRenderer.on('updateGallery', (event: IpcRendererEvent) => {
this.$nextTick(() => {
this.filterList = this.getGallery()
async created () {
ipcRenderer.on('updateGallery', () => {
this.$nextTick(async () => {
this.updateGallery()
})
})
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
this.updateGallery()
}
mounted () {
document.addEventListener('keydown', this.handleDetectShiftKey)
document.addEventListener('keyup', this.handleDetectShiftKey)
}
handleDetectShiftKey (event: KeyboardEvent) {
if (event.keyCode === 16) {
this.isShiftKeyPress = !this.isShiftKeyPress
if (event.key === 'Shift') {
if (event.type === 'keydown') {
this.isShiftKeyPress = true
} else if (event.type === 'keyup') {
this.isShiftKeyPress = false
}
}
}
get filterList () {
return this.getGallery()
}
set filterList (val) {
this.images = val
}
get isAllSelected () {
const values = Object.values(this.choosedList)
if (values.length === 0) {
@@ -191,50 +206,45 @@ export default class extends Vue {
})
}
}
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
this.picBed = picBeds
}
getGallery () {
if (this.choosedPicBed.length > 0) {
let arr: ImgInfo[] = []
this.choosedPicBed.forEach(item => {
let obj: IObj = {
type: item
}
if (this.searchText) {
obj.fileName = this.searchText
}
// @ts-ignore
arr = arr.concat(this.$db.read().get('uploaded').filter(obj => {
return obj.fileName.indexOf(this.searchText) !== -1 && obj.type === item
}).reverse().value())
})
this.images = arr
getGallery (): ImgInfo[] {
if (this.searchText || this.choosedPicBed.length > 0) {
return this.images
.filter(item => {
let isInChoosedPicBed = true
let isIncludesSearchText = true
if (this.choosedPicBed.length > 0) {
isInChoosedPicBed = this.choosedPicBed.some(type => type === item.type)
}
if (this.searchText) {
isIncludesSearchText = item.fileName?.includes(this.searchText) || false
}
return isIncludesSearchText && isInChoosedPicBed
})
} else {
if (this.searchText) {
let data = this.$db.read().get('uploaded')
// @ts-ignore
.filter(item => {
return item.fileName.indexOf(this.searchText) !== -1
}).reverse().value()
this.images = data
} else {
// @ts-ignore
this.images = this.$db.read().get('uploaded').slice().reverse().value()
}
return this.images
}
return this.images
}
async updateGallery () {
this.images = (await this.$$db.get({ orderBy: 'desc' })).data
}
@Watch('filterList')
handleFilterListChange () {
this.clearChoosedList()
}
handleChooseImage (val: boolean, index: number) {
if (val === true) {
this.handleBarActive = true
if (this.lastChoosed !== -1 && this.isShiftKeyPress) {
let min = Math.min(this.lastChoosed, index)
let max = Math.max(this.lastChoosed, index)
const min = Math.min(this.lastChoosed, index)
const max = Math.max(this.lastChoosed, index)
for (let i = min + 1; i < max; i++) {
const id = this.filterList[i].id!
this.$set(this.choosedList, id, true)
@@ -243,6 +253,7 @@ export default class extends Vue {
this.lastChoosed = index
}
}
clearChoosedList () {
this.isShiftKeyPress = false
Object.keys(this.choosedList).forEach(key => {
@@ -250,10 +261,12 @@ export default class extends Vue {
})
this.lastChoosed = -1
}
zoomImage (index: number) {
this.idx = index
this.changeZIndexForGallery(true)
}
changeZIndexForGallery (isOpen: boolean) {
if (isOpen) {
// @ts-ignore
@@ -263,32 +276,33 @@ export default class extends Vue {
document.querySelector('.main-content.el-row').style.zIndex = 10
}
}
handleClose () {
this.idx = null
this.changeZIndexForGallery(false)
}
copy (item: ImgInfo) {
const style = this.$db.get('settings.pasteStyle') || 'markdown'
const copyLink = pasteStyle(style, item)
async copy (item: ImgInfo) {
const copyLink = await ipcRenderer.invoke(PASTE_TEXT, item)
const obj = {
title: '复制链接成功',
body: copyLink,
icon: item.url || item.imgUrl
}
const myNotification = new Notification(obj.title, obj)
clipboard.writeText(copyLink)
myNotification.onclick = () => {
return true
}
}
remove (id: string) {
this.$confirm('此操作将把该图片移出相册, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const file = this.$db.getById('uploaded', id)
this.$db.removeById('uploaded', id)
}).then(async () => {
const file = await this.$$db.getById(id)
await this.$$db.removeById(id)
ipcRenderer.send('removeFiles', [file])
const obj = {
title: '操作结果',
@@ -298,23 +312,23 @@ export default class extends Vue {
myNotification.onclick = () => {
return true
}
this.getGallery()
this.updateGallery()
}).catch((e) => {
console.log(e)
return true
})
}
openDialog (item: ImgInfo) {
this.imgInfo.id = item.id!
this.imgInfo.imgUrl = item.imgUrl as string
this.dialogVisible = true
}
confirmModify () {
this.$db.read().get('uploaded')
// @ts-ignore
.getById(this.imgInfo.id)
.assign({ imgUrl: this.imgInfo.imgUrl })
.write()
async confirmModify () {
await this.$$db.updateById(this.imgInfo.id, {
imgUrl: this.imgInfo.imgUrl
})
const obj = {
title: '修改图片URL成功',
body: this.imgInfo.imgUrl,
@@ -325,28 +339,33 @@ export default class extends Vue {
return true
}
this.dialogVisible = false
this.getGallery()
this.updateGallery()
}
choosePicBed (type: string) {
let idx = this.choosedPicBed.indexOf(type)
const idx = this.choosedPicBed.indexOf(type)
if (idx !== -1) {
this.choosedPicBed.splice(idx, 1)
} else {
this.choosedPicBed.push(type)
}
}
cleanSearch () {
this.searchText = ''
}
isMultiple (obj: IObj) {
return Object.values(obj).some(item => item)
}
toggleSelectAll () {
const result = !this.isAllSelected
this.filterList.forEach(item => {
this.$set(this.choosedList, item.id!, result)
})
}
multiRemove () {
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
const multiRemoveNumber = Object.values(this.choosedList).filter(item => item).length
@@ -355,18 +374,21 @@ export default class extends Vue {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let files: ImgInfo[] = []
Object.keys(this.choosedList).forEach(key => {
}).then(async () => {
const files: IResult<ImgInfo>[] = []
const imageIDList = Object.keys(this.choosedList)
for (let i = 0; i < imageIDList.length; i++) {
const key = imageIDList[i]
if (this.choosedList[key]) {
const file = this.$db.getById('uploaded', key)
files.push(file)
this.$db.removeById('uploaded', key)
const file = await this.$$db.getById<ImgInfo>(key)
if (file) {
files.push(file)
await this.$$db.removeById(key)
}
}
})
}
this.clearChoosedList()
this.choosedList = {} // 只有删除才能将这个置空
this.getGallery()
const obj = {
title: '操作结果',
body: '删除成功'
@@ -376,23 +398,29 @@ export default class extends Vue {
myNotification.onclick = () => {
return true
}
this.updateGallery()
}).catch(() => {
return true
})
}
}
multiCopy () {
async multiCopy () {
if (Object.values(this.choosedList).some(item => item)) {
const copyString: string[] = []
const style = this.$db.get('settings.pasteStyle') || 'markdown'
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
Object.keys(this.choosedList).forEach(key => {
const imageIDList = Object.keys(this.choosedList)
for (let i = 0; i < imageIDList.length; i++) {
const key = imageIDList[i]
if (this.choosedList[key]) {
const item = this.$db.getById('uploaded', key)
copyString.push(pasteStyle(style, item))
this.choosedList[key] = false
const item = await this.$$db.getById<ImgInfo>(key)
if (item) {
const txt = await ipcRenderer.invoke(PASTE_TEXT, item)
copyString.push(txt)
this.choosedList[key] = false
}
}
})
}
const obj = {
title: '批量复制链接成功',
body: copyString.join('\n')
@@ -404,16 +432,19 @@ export default class extends Vue {
}
}
}
toggleHandleBar () {
this.handleBarActive = !this.handleBarActive
}
getPasteStyle () {
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
}
handlePasteStyleChange (val: string) {
this.$db.set('settings.pasteStyle', val)
// getPasteStyle () {
// this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
// }
async handlePasteStyleChange (val: string) {
this.saveConfig('settings.pasteStyle', val)
this.pasteStyle = val
}
beforeDestroy () {
ipcRenderer.removeAllListeners('updateGallery')
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
+24 -70
View File
@@ -20,10 +20,9 @@ import mixin from '@/utils/mixin'
import { Component, Vue, Watch } from 'vue-property-decorator'
import {
ipcRenderer,
IpcRendererEvent,
remote
IpcRendererEvent
} from 'electron'
import { SHOW_PRIVACY_MESSAGE } from '~/universal/events/constants'
import { SHOW_MINI_PAGE_MENU, SET_MINI_WINDOW_POS } from '~/universal/events/constants'
@Component({
name: 'mini-page',
mixins: [mixin]
@@ -55,6 +54,7 @@ export default class extends Vue {
})
this.getPicBeds()
}
mounted () {
window.addEventListener('mousedown', this.handleMouseDown, false)
window.addEventListener('mousemove', this.handleMouseMove, false)
@@ -73,27 +73,31 @@ export default class extends Vue {
}, 1200)
}
}
getPicBeds () {
this.picBed = ipcRenderer.sendSync('getPicBeds')
this.buildMenu()
}
onDrop (e: DragEvent) {
this.dragover = false
this.ipcSendFiles(e.dataTransfer!.files)
}
openUploadWindow () {
// @ts-ignore
document.getElementById('file-uploader').click()
}
onChange (e: any) {
this.ipcSendFiles(e.target.files)
// @ts-ignore
document.getElementById('file-uploader').value = ''
}
ipcSendFiles (files: FileList) {
let sendFiles: IFileWithPath[] = []
Array.from(files).forEach((item, index) => {
let obj = {
const sendFiles: IFileWithPath[] = []
Array.from(files).forEach((item) => {
const obj = {
name: item.name,
path: item.path
}
@@ -101,6 +105,7 @@ export default class extends Vue {
})
ipcRenderer.send('uploadChoosedFiles', sendFiles)
}
handleMouseDown (e: MouseEvent) {
this.dragging = true
this.wX = e.pageX
@@ -108,20 +113,28 @@ export default class extends Vue {
this.screenX = e.screenX
this.screenY = e.screenY
}
handleMouseMove (e: MouseEvent) {
e.preventDefault()
e.stopPropagation()
if (this.dragging) {
const xLoc = e.screenX - this.wX
const yLoc = e.screenY - this.wY
remote.BrowserWindow.getFocusedWindow()!.setBounds({
ipcRenderer.send(SET_MINI_WINDOW_POS, {
x: xLoc,
y: yLoc,
width: 64,
height: 64
})
// remote.BrowserWindow.getFocusedWindow()!.setBounds({
// x: xLoc,
// y: yLoc,
// width: 64,
// height: 64
// })
}
}
handleMouseUp (e: MouseEvent) {
this.dragging = false
if (this.screenX === e.screenX && this.screenY === e.screenY) {
@@ -133,70 +146,11 @@ export default class extends Vue {
}
}
}
openContextMenu () {
this.menu!.popup()
}
buildMenu () {
const _this = this
const submenu = this.picBed.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: this.$db.get('picBed.current') === item.type,
click () {
_this.letPicGoSaveData({
'picBed.current': item.type,
'picBed.uploader': item.type
})
ipcRenderer.send('syncPicBed')
}
}
})
const template = [
{
label: '打开详细窗口',
click () {
ipcRenderer.send('openSettingWindow')
}
},
{
label: '选择默认图床',
type: 'submenu',
submenu
},
{
label: '剪贴板图片上传',
click () {
ipcRenderer.send('uploadClipboardFilesFromUploadPage')
}
},
{
label: '隐藏窗口',
click () {
remote.BrowserWindow.getFocusedWindow()!.hide()
}
},
{
label: '隐私协议',
click () {
ipcRenderer.send(SHOW_PRIVACY_MESSAGE)
}
},
{
label: '重启应用',
click () {
remote.app.relaunch()
remote.app.exit(0)
}
},
{
role: 'quit',
label: '退出'
}
]
// @ts-ignore
this.menu = remote.Menu.buildFromTemplate(template)
ipcRenderer.send(SHOW_MINI_PAGE_MENU)
}
beforeDestroy () {
ipcRenderer.removeAllListeners('uploadProgress')
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
+126 -57
View File
@@ -338,13 +338,13 @@
<script lang="ts">
import keyDetect from '@/utils/key-binding'
import pkg from 'root/package.json'
import path from 'path'
import { IConfig } from 'picgo/dist/src/types/index'
import { PICGO_OPEN_FILE, OPEN_URL } from '#/events/constants'
import {
ipcRenderer,
remote
ipcRenderer
} from 'electron'
import { Component, Vue } from 'vue-property-decorator'
import db from '#/datastore'
// import db from '#/datastore'
const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases/latest'
const releaseUrlBackup = 'https://cdn.jsdelivr.net/gh/Molunerfinn/PicGo@latest/package.json'
const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
@@ -355,31 +355,24 @@ const customLinkRule = (rule: string, value: string, callback: (arg0?: Error) =>
return callback()
}
}
let logLevel = db.get('settings.logLevel')
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
@Component({
name: 'picgo-setting'
})
export default class extends Vue {
form: ISettingForm = {
updateHelper: db.get('settings.showUpdateTip'),
updateHelper: false,
showPicBedList: [],
autoStart: db.get('settings.autoStart') || false,
rename: db.get('settings.rename') || false,
autoRename: db.get('settings.autoRename') || false,
uploadNotification: db.get('settings.uploadNotification') || false,
miniWindowOntop: db.get('settings.miniWindowOntop') || false,
logLevel,
autoCopyUrl: db.get('settings.autoCopy') === undefined ? true : db.get('settings.autoCopy'),
checkBetaUpdate: db.get('settings.checkBetaUpdate') === undefined ? true : db.get('settings.checkBetaUpdate')
autoStart: false,
rename: false,
autoRename: false,
uploadNotification: false,
miniWindowOntop: false,
logLevel: ['all'],
autoCopyUrl: true,
checkBetaUpdate: true
}
picBed: IPicBedType[] = []
logFileVisible = false
keyBindingVisible = false
@@ -388,19 +381,22 @@ export default class extends Vue {
serverVisible = false
proxyVisible = false
customLink = {
value: db.get('settings.customLink') || '$url'
value: '$url'
}
shortKey: IShortKeyMap = {
upload: db.get('settings.shortKey.upload')
upload: ''
}
proxy = db.get('picBed.proxy') || ''
npmRegistry = db.get('settings.registry') || ''
npmProxy = db.get('settings.proxy') || ''
proxy = ''
npmRegistry = ''
npmProxy = ''
rules = {
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
}
logLevel = {
all: '全部-All',
success: '成功-Success',
@@ -409,11 +405,13 @@ export default class extends Vue {
warn: '提醒-Warn',
none: '不记录日志-None'
}
server = db.get('settings.server') || {
server = {
port: 36677,
host: '127.0.0.1',
enable: true
}
version = pkg.version
latestVersion = ''
os = ''
@@ -425,40 +423,85 @@ export default class extends Vue {
return false
}
}
created () {
this.os = process.platform
ipcRenderer.send('getPicBeds')
ipcRenderer.on('getPicBeds', this.getPicBeds)
this.initData()
}
async initData () {
const config = (await this.getConfig<IConfig>())!
if (config !== undefined) {
const settings = config.settings || {}
const picBed = config.picBed
this.form.updateHelper = settings.showUpdateTip || false
this.form.autoStart = settings.autoStart || false
this.form.rename = settings.rename || false
this.form.autoRename = settings.autoRename || false
this.form.uploadNotification = settings.uploadNotification || false
this.form.miniWindowOntop = settings.miniWindowOntop || false
this.form.logLevel = this.initLogLevel(settings.logLevel || [])
this.form.autoCopyUrl = settings.autoCopy === undefined ? true : settings.autoCopy
this.form.checkBetaUpdate = settings.checkBetaUpdate === undefined ? true : settings.checkBetaUpdate
this.customLink.value = settings.customLink || '$url'
this.shortKey.upload = settings.shortKey.upload
this.proxy = picBed.proxy || ''
this.npmRegistry = settings.registry || ''
this.npmProxy = settings.proxy || ''
this.server = settings.server || {
port: 36677,
host: '127.0.0.1',
enable: true
}
}
}
initLogLevel (logLevel: string | string[]) {
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
return logLevel
}
getPicBeds (event: Event, picBeds: IPicBedType[]) {
this.picBed = picBeds
this.form.showPicBedList = this.picBed.map(item => {
if (item.visible) {
return item.name
}
}) as string[]
return null
}).filter(item => item) as string[]
}
openFile (file: string) {
const { app, shell } = remote
const STORE_PATH = app.getPath('userData')
const FILE = path.join(STORE_PATH, `/${file}`)
shell.openItem(FILE)
ipcRenderer.send(PICGO_OPEN_FILE, file)
}
openLogSetting () {
this.logFileVisible = true
}
keyDetect (type: string, event: KeyboardEvent) {
this.shortKey[type] = keyDetect(event).join('+')
}
cancelCustomLink () {
async cancelCustomLink () {
this.customLinkVisible = false
this.customLink.value = db.get('settings.customLink') || '$url'
this.customLink.value = await this.getConfig<string>('settings.customLink') || '$url'
}
confirmCustomLink () {
// @ts-ignore
this.$refs.customLink.validate((valid: boolean) => {
if (valid) {
db.set('settings.customLink', this.customLink.value)
this.saveConfig('settings.customLink', this.customLink.value)
this.customLinkVisible = false
ipcRenderer.send('updateCustomLink')
} else {
@@ -466,13 +509,15 @@ export default class extends Vue {
}
})
}
cancelProxy () {
async cancelProxy () {
this.proxyVisible = false
this.proxy = db.get('picBed.proxy') || undefined
this.proxy = await this.getConfig<string>('picBed.proxy') || ''
}
confirmProxy () {
this.proxyVisible = false
this.letPicGoSaveData({
this.saveConfig({
'picBed.proxy': this.proxy,
'settings.proxy': this.npmProxy,
'settings.registry': this.npmRegistry
@@ -484,12 +529,15 @@ export default class extends Vue {
return true
}
}
updateHelperChange (val: boolean) {
db.set('settings.showUpdateTip', val)
this.saveConfig('settings.showUpdateTip', val)
}
checkBetaUpdateChange (val: boolean) {
db.set('settings.checkBetaUpdate', val)
this.saveConfig('settings.checkBetaUpdate', val)
}
handleShowPicBedListChange (val: string[]) {
const list = this.picBed.map(item => {
if (!val.includes(item.name)) {
@@ -499,25 +547,29 @@ export default class extends Vue {
}
return item
})
this.letPicGoSaveData({
this.saveConfig({
'picBed.list': list
})
ipcRenderer.send('getPicBeds')
}
handleAutoStartChange (val: boolean) {
db.set('settings.autoStart', val)
this.saveConfig('settings.autoStart', val)
ipcRenderer.send('autoStart', val)
}
handleRename (val: boolean) {
this.letPicGoSaveData({
this.saveConfig({
'settings.rename': val
})
}
handleAutoRename (val: boolean) {
this.letPicGoSaveData({
this.saveConfig({
'settings.autoRename': val
})
}
compareVersion2Update (current: string, latest: string) {
const currentVersion = current.split('.').map(item => parseInt(item))
const latestVersion = latest.split('.').map(item => parseInt(item))
@@ -532,6 +584,7 @@ export default class extends Vue {
}
return false
}
checkUpdate () {
this.checkUpdateVisible = true
this.$http.get(releaseUrl)
@@ -546,24 +599,31 @@ export default class extends Vue {
})
})
}
confirmCheckVersion () {
if (this.needUpdate) {
remote.shell.openExternal(downloadUrl)
ipcRenderer.send(OPEN_URL, downloadUrl)
}
this.checkUpdateVisible = false
}
cancelCheckVersion () {
this.checkUpdateVisible = false
}
handleUploadNotification (val: boolean) {
db.set('settings.uploadNotification', val)
this.saveConfig({
'settings.uploadNotification': val
})
}
handleMiniWindowOntop (val: boolean) {
db.set('settings.miniWindowOntop', val)
this.saveConfig('settings.miniWindowOntop', val)
this.$message.info('需要重启生效')
}
handleAutoCopyUrl (val: boolean) {
db.set('settings.autoCopy', val)
this.saveConfig('settings.autoCopy', val)
const successNotification = new Notification('设置自动复制链接', {
body: '设置成功'
})
@@ -571,11 +631,12 @@ export default class extends Vue {
return true
}
}
confirmLogLevelSetting () {
if (this.form.logLevel.length === 0) {
return this.$message.error('请选择日志记录等级')
}
this.letPicGoSaveData({
this.saveConfig({
'settings.logLevel': this.form.logLevel
})
const successNotification = new Notification('设置日志', {
@@ -586,9 +647,10 @@ export default class extends Vue {
}
this.logFileVisible = false
}
cancelLogLevelSetting () {
async cancelLogLevelSetting () {
this.logFileVisible = false
let logLevel = db.get('settings.logLevel')
let logLevel = await this.getConfig<string | string[]>('settings.logLevel')
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
@@ -598,9 +660,11 @@ export default class extends Vue {
}
this.form.logLevel = logLevel
}
confirmServerSetting () {
// @ts-ignore
this.server.port = parseInt(this.server.port, 10)
this.letPicGoSaveData({
this.saveConfig({
'settings.server': this.server
})
const successNotification = new Notification('设置PicGo-Server', {
@@ -612,18 +676,20 @@ export default class extends Vue {
this.serverVisible = false
ipcRenderer.send('updateServer')
}
cancelServerSetting () {
async cancelServerSetting () {
this.serverVisible = false
this.server = db.get('settings.server') || {
this.server = await this.getConfig('settings.server') || {
port: 36677,
host: '127.0.0.1',
enable: true
}
}
handleLevelDisabled (val: string) {
let currentLevel = val
const currentLevel = val
let flagLevel
let result = this.form.logLevel.some(item => {
const result = this.form.logLevel.some(item => {
if (item === 'all' || item === 'none') {
flagLevel = item
}
@@ -640,12 +706,15 @@ export default class extends Vue {
}
return false
}
goConfigPage () {
remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
ipcRenderer.send(OPEN_URL, 'https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
}
goShortCutPage () {
this.$router.push('shortKey')
}
beforeDestroy () {
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
+70 -116
View File
@@ -109,11 +109,17 @@ import ConfigForm from '@/components/ConfigForm.vue'
import { debounce } from 'lodash'
import {
ipcRenderer,
remote,
IpcRendererEvent
} from 'electron'
import { handleStreamlinePluginName } from '~/universal/utils/common'
const { Menu } = remote
import {
OPEN_URL,
RELOAD_APP,
PICGO_CONFIG_PLUGIN,
PICGO_HANDLE_PLUGIN_ING,
PICGO_TOGGLE_PLUGIN,
SHOW_PLUGIN_PAGE_MENU
} from '#/events/constants'
@Component({
name: 'plugin',
@@ -144,6 +150,7 @@ export default class extends Vue {
? `picgo-plugin-${this.searchText}`
: this.searchText
}
@Watch('npmSearchText')
onNpmSearchTextChange (val: string) {
if (val) {
@@ -154,6 +161,7 @@ export default class extends Vue {
this.getPluginList()
}
}
@Watch('dialogVisible')
onDialogVisible (val: boolean) {
if (val) {
@@ -164,7 +172,8 @@ export default class extends Vue {
document.querySelector('.main-content.el-row').style.zIndex = 10
}
}
created () {
async created () {
this.os = process.platform
ipcRenderer.on('hideLoading', () => {
this.loading = false
@@ -214,103 +223,45 @@ export default class extends Vue {
})
this.pluginNameList = this.pluginNameList.filter(item => item !== plugin)
})
ipcRenderer.on(PICGO_CONFIG_PLUGIN, (evt: IpcRendererEvent, currentType: string, configName: string, config: any) => {
this.currentType = currentType
this.configName = configName
this.dialogVisible = true
this.config = config
})
ipcRenderer.on(PICGO_HANDLE_PLUGIN_ING, (evt: IpcRendererEvent, fullName: string) => {
this.pluginList.forEach(item => {
if (item.fullName === fullName || (item.name === fullName)) {
item.ing = true
}
})
this.loading = true
})
ipcRenderer.on(PICGO_TOGGLE_PLUGIN, (evt: IpcRendererEvent, fullName: string, enabled: boolean) => {
const plugin = this.pluginList.find(item => item.fullName === fullName)
if (plugin) {
plugin.enabled = enabled
this.getPicBeds()
this.needReload = true
}
})
this.getPluginList()
this.getSearchResult = debounce(this.getSearchResult, 50)
this.needReload = this.$db.get('needReload')
this.needReload = await this.getConfig<boolean>('needReload') || false
}
buildContextMenu (plugin: IPicGoPlugin) {
const _this = this
let menu = [{
label: '启用插件',
enabled: !plugin.enabled,
click () {
_this.letPicGoSaveData({
[`picgoPlugins.${plugin.fullName}`]: true
})
plugin.enabled = true
_this.getPicBeds()
}
}, {
label: '禁用插件',
enabled: plugin.enabled,
click () {
_this.letPicGoSaveData({
[`picgoPlugins.${plugin.fullName}`]: false
})
plugin.enabled = false
_this.getPicBeds()
if (plugin.config.transformer.name) {
_this.handleRestoreState('transformer', plugin.config.transformer.name)
}
if (plugin.config.uploader.name) {
_this.handleRestoreState('uploader', plugin.config.uploader.name)
}
_this.needReload = true
}
}, {
label: '卸载插件',
click () {
_this.uninstallPlugin(plugin.fullName)
}
}, {
label: '更新插件',
click () {
_this.updatePlugin(plugin.fullName)
}
}]
for (let i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
label: `配置${i} - ${plugin.config[i].fullName || plugin.config[i].name}`,
click () {
_this.currentType = i
_this.configName = plugin.config[i].fullName || plugin.config[i].name
_this.dialogVisible = true
_this.config = plugin.config[i].config
}
}
menu.push(obj)
}
}
// handle transformer
if (plugin.config.transformer.name) {
let currentTransformer = this.$db.get('picBed.transformer') || 'path'
let pluginTransformer = plugin.config.transformer.name
const obj = {
label: `${currentTransformer === pluginTransformer ? '禁用' : '启用'}transformer - ${plugin.config.transformer.name}`,
click () {
_this.toggleTransformer(plugin.config.transformer.name)
}
}
menu.push(obj)
}
// plugin custom menus
if (plugin.guiMenu) {
menu.push({
// @ts-ignore
type: 'separator'
})
for (let i of plugin.guiMenu) {
menu.push({
label: i.label,
click () {
ipcRenderer.send('pluginActions', plugin.fullName, i.label)
}
})
}
}
this.menu = Menu.buildFromTemplate(menu)
this.menu.popup()
async buildContextMenu (plugin: IPicGoPlugin) {
ipcRenderer.send(SHOW_PLUGIN_PAGE_MENU, plugin)
}
getPluginList () {
ipcRenderer.send('getPluginList')
}
getPicBeds () {
ipcRenderer.send('getPicBeds')
}
installPlugin (item: IPicGoPlugin) {
if (!item.gui) {
this.$confirm('该插件未对可视化界面进行优化, 是否继续安装?', '提示', {
@@ -328,6 +279,7 @@ export default class extends Vue {
ipcRenderer.send('installPlugin', item.fullName)
}
}
uninstallPlugin (val: string) {
this.pluginList.forEach(item => {
if (item.name === val) {
@@ -337,6 +289,7 @@ export default class extends Vue {
this.loading = true
ipcRenderer.send('uninstallPlugin', val)
}
updatePlugin (val: string) {
this.pluginList.forEach(item => {
if (item.fullName === val) {
@@ -346,12 +299,15 @@ export default class extends Vue {
this.loading = true
ipcRenderer.send('updatePlugin', val)
}
reloadApp () {
remote.app.relaunch()
remote.app.exit(0)
ipcRenderer.send(RELOAD_APP)
}
handleReload () {
this.$db.set('needReload', true)
async handleReload () {
this.saveConfig({
needReload: true
})
this.needReload = true
const successNotification = new Notification('更新成功', {
body: '请点击此通知重启应用以生效'
@@ -360,38 +316,28 @@ export default class extends Vue {
this.reloadApp()
}
}
cleanSearch () {
this.searchText = ''
}
toggleTransformer (transformer: string) {
let currentTransformer = this.$db.get('picBed.transformer') || 'path'
if (currentTransformer === transformer) {
this.letPicGoSaveData({
'picBed.transformer': 'path'
})
} else {
this.letPicGoSaveData({
'picBed.transformer': transformer
})
}
}
async handleConfirmConfig () {
// @ts-ignore
const result = await this.$refs.configForm.validate()
if (result !== false) {
switch (this.currentType) {
case 'plugin':
this.letPicGoSaveData({
this.saveConfig({
[`${this.configName}`]: result
})
break
case 'uploader':
this.letPicGoSaveData({
this.saveConfig({
[`picBed.${this.configName}`]: result
})
break
case 'transformer':
this.letPicGoSaveData({
this.saveConfig({
[`transformer.${this.configName}`]: result
})
break
@@ -406,6 +352,7 @@ export default class extends Vue {
this.getPluginList()
}
}
getSearchResult (val: string) {
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
this.$http.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
@@ -424,6 +371,7 @@ export default class extends Vue {
this.loading = false
})
}
handleSearchResult (item: INPMSearchResultObject) {
const name = handleStreamlinePluginName(item.package.name)
let gui = false
@@ -446,41 +394,47 @@ export default class extends Vue {
ing: false // installing or uninstalling
}
}
// restore Uploader & Transformer
handleRestoreState (item: string, name: string) {
async handleRestoreState (item: string, name: string) {
if (item === 'uploader') {
const current = this.$db.get('picBed.current')
const current = await this.getConfig('picBed.current')
if (current === name) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.current': 'smms',
'picBed.uploader': 'smms'
})
}
}
if (item === 'transformer') {
const current = this.$db.get('picBed.transformer')
const current = await this.getConfig('picBed.transformer')
if (current === name) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.transformer': 'path'
})
}
}
}
openHomepage (url: string) {
if (url) {
remote.shell.openExternal(url)
ipcRenderer.send(OPEN_URL, url)
}
}
goAwesomeList () {
remote.shell.openExternal('https://github.com/PicGo/Awesome-PicGo')
ipcRenderer.send(OPEN_URL, 'https://github.com/PicGo/Awesome-PicGo')
}
letPicGoSaveData (data: IObj) {
saveConfig (data: IObj) {
ipcRenderer.send('picgoSaveData', data)
}
handleImportLocalPlugin () {
ipcRenderer.send('importLocalPlugin')
this.loading = true
}
beforeDestroy () {
ipcRenderer.removeAllListeners('pluginList')
ipcRenderer.removeAllListeners('installPlugin')
+3
View File
@@ -41,12 +41,15 @@ export default class extends Vue {
this.id = id
})
}
confirmName () {
ipcRenderer.send(`rename${this.id}`, this.fileName)
}
cancel () {
ipcRenderer.send(`rename${this.id}`, null)
}
beforeDestroy () {
ipcRenderer.removeAllListeners('rename')
}
+17 -11
View File
@@ -109,8 +109,8 @@ export default class extends Vue {
command = ''
shortKey = ''
currentIndex = 0
created () {
const shortKeyConfig = this.$db.get('settings.shortKey') as IShortKeyConfigs
async created () {
const shortKeyConfig = (await this.getConfig<IShortKeyConfigs>('settings.shortKey'))!
this.list = Object.keys(shortKeyConfig).map(item => {
return {
...shortKeyConfig[item],
@@ -118,40 +118,45 @@ export default class extends Vue {
}
})
}
@Watch('keyBindingVisible')
onKeyBindingVisibleChange (val: boolean) {
ipcRenderer.send(TOGGLE_SHORTKEY_MODIFIED_MODE, val)
}
calcOrigin (item: string) {
const [origin] = item.split(':')
return origin
}
calcOriginShowName (item: string) {
return item.replace('picgo-plugin-', '')
}
toggleEnable (item: IShortKeyConfig) {
const status = !item.enable
item.enable = status
// this.$db.set(`settings.shortKey.${item.name}.enable`, status)
ipcRenderer.send('bindOrUnbindShortKey', item, item.from)
}
keyDetect (event: KeyboardEvent) {
this.shortKey = keyDetect(event).join('+')
}
openKeyBindingDialog (config: IShortKeyConfig, index: number) {
async openKeyBindingDialog (config: IShortKeyConfig, index: number) {
this.command = `${config.from}:${config.name}`
this.shortKey = this.$db.get(`settings.shortKey.${this.command}.key`)
this.shortKey = await this.getConfig(`settings.shortKey.${this.command}.key`) || ''
this.currentIndex = index
this.keyBindingVisible = true
}
cancelKeyBinding () {
async cancelKeyBinding () {
this.keyBindingVisible = false
this.shortKey = this.$db.get(`settings.shortKey.${this.command}.key`)
this.shortKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`) || ''
}
confirmKeyBinding () {
const oldKey = this.$db.get(`settings.shortKey.${this.command}.key`)
// this.$db.set(`settings.shortKey.${this.command}.key`, this.shortKey)
// const newKey = this.$db.get(`settings.shortKey.${this.command}`)
async confirmKeyBinding () {
const oldKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`)
const config = Object.assign({}, this.list[this.currentIndex])
config.key = this.shortKey
ipcRenderer.send('updateShortKey', config, oldKey, config.from)
@@ -162,6 +167,7 @@ export default class extends Vue {
}
})
}
beforeDestroy () {
ipcRenderer.send(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
}
+26 -19
View File
@@ -28,41 +28,46 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/mixin'
import pasteTemplate from '#/utils/pasteTemplate'
import { ipcRenderer, clipboard } from 'electron'
import { ipcRenderer } from 'electron'
import { IResult } from '@picgo/store/dist/types'
import { PASTE_TEXT } from '#/events/constants'
@Component({
name: 'tray-page',
mixins: [mixin]
})
export default class extends Vue {
files = []
files: IResult<ImgInfo>[] = []
notification = {
title: '复制链接成功',
body: '',
icon: ''
}
clipboardFiles: ImgInfo[] = []
uploadFlag = false
get reverseList () {
return this.files.slice().reverse()
}
getData () {
// @ts-ignore
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
async getData () {
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
}
copyTheLink (item: ImgInfo) {
async copyTheLink (item: ImgInfo) {
this.notification.body = item.imgUrl!
this.notification.icon = item.imgUrl!
const myNotification = new Notification(this.notification.title, this.notification)
const pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
clipboard.writeText(pasteTemplate(pasteStyle, item))
ipcRenderer.invoke(PASTE_TEXT, item)
myNotification.onclick = () => {
return true
}
}
calcHeight (width: number, height: number): number {
return height * 160 / width
}
disableDragFile () {
window.addEventListener('dragover', (e) => {
e = e || event
@@ -73,6 +78,7 @@ export default class extends Vue {
e.preventDefault()
}, false)
}
uploadClipboardFiles () {
if (this.uploadFlag) {
return
@@ -80,29 +86,30 @@ export default class extends Vue {
this.uploadFlag = true
ipcRenderer.send('uploadClipboardFiles')
}
mounted () {
this.disableDragFile()
this.getData()
ipcRenderer.on('dragFiles', (event: Event, files: string[]) => {
files.forEach(item => {
this.$db.insert('uploaded', item)
})
// @ts-ignore
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
ipcRenderer.on('dragFiles', async (event: Event, files: string[]) => {
for (let i = 0; i < files.length; i++) {
const item = files[i]
await this.$$db.insert(item)
}
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
})
ipcRenderer.on('clipboardFiles', (event: Event, files: ImgInfo[]) => {
this.clipboardFiles = files
})
ipcRenderer.on('uploadFiles', (event: Event) => {
// @ts-ignore
this.files = this.$db.read().get('uploaded').slice().reverse().slice(0, 5).value()
ipcRenderer.on('uploadFiles', async () => {
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
console.log(this.files)
this.uploadFlag = false
})
ipcRenderer.on('updateFiles', (event: Event) => {
ipcRenderer.on('updateFiles', () => {
this.getData()
})
}
beforeDestroy () {
ipcRenderer.removeAllListeners('dragFiles')
ipcRenderer.removeAllListeners('clipboardFiles')
+31 -37
View File
@@ -60,17 +60,16 @@
import { Component, Vue, Watch } from 'vue-property-decorator'
import {
ipcRenderer,
IpcRendererEvent,
remote
IpcRendererEvent
} from 'electron'
import {
SHOW_INPUT_BOX,
SHOW_INPUT_BOX_RESPONSE
SHOW_INPUT_BOX_RESPONSE,
SHOW_UPLOAD_PAGE_MENU
} from '~/universal/events/constants'
import {
isUrl
} from '~/universal/utils/common'
const { Menu } = remote
@Component({
name: 'upload'
})
@@ -82,7 +81,6 @@ export default class extends Vue {
pasteStyle = ''
picBed: IPicBedType[] = []
picBedName = ''
menu: Electron.Menu | null= null
mounted () {
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => {
if (progress !== -1) {
@@ -102,6 +100,7 @@ export default class extends Vue {
ipcRenderer.on('getPicBeds', this.getPicBeds)
this.$bus.$on(SHOW_INPUT_BOX_RESPONSE, this.handleInputBoxValue)
}
@Watch('progress')
onProgressChange (val: number) {
if (val === 100) {
@@ -114,12 +113,14 @@ export default class extends Vue {
}, 1200)
}
}
beforeDestroy () {
this.$bus.$off(SHOW_INPUT_BOX_RESPONSE)
ipcRenderer.removeAllListeners('uploadProgress')
ipcRenderer.removeAllListeners('syncPicBed')
ipcRenderer.removeListener('getPicBeds', this.getPicBeds)
}
onDrop (e: DragEvent) {
this.dragover = false
const items = e.dataTransfer!.items
@@ -136,6 +137,7 @@ export default class extends Vue {
this.ipcSendFiles(e.dataTransfer!.files)
}
}
handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) {
// text/html
// Use this data to get a more precise URL
@@ -151,17 +153,20 @@ export default class extends Vue {
this.$message.error('请拖入合法的图片文件或者图片URL地址')
}
}
openUplodWindow () {
document.getElementById('file-uploader')!.click()
}
onChange (e: any) {
this.ipcSendFiles(e.target.files);
(document.getElementById('file-uploader') as HTMLInputElement).value = ''
}
ipcSendFiles (files: FileList) {
let sendFiles: IFileWithPath[] = []
Array.from(files).forEach((item, index) => {
let obj = {
const sendFiles: IFileWithPath[] = []
Array.from(files).forEach((item) => {
const obj = {
name: item.name,
path: item.path
}
@@ -169,15 +174,21 @@ export default class extends Vue {
})
ipcRenderer.send('uploadChoosedFiles', sendFiles)
}
getPasteStyle () {
this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown'
async getPasteStyle () {
this.pasteStyle = await this.getConfig('settings.pasteStyle') || 'markdown'
}
handlePasteStyleChange (val: string) {
this.$db.set('settings.pasteStyle', val)
this.saveConfig({
'settings.pasteStyle': val
})
}
uploadClipboardFiles () {
ipcRenderer.send('uploadClipboardFilesFromUploadPage')
}
async uploadURLFiles () {
const str = await navigator.clipboard.readText()
this.$bus.$emit(SHOW_INPUT_BOX, {
@@ -186,6 +197,7 @@ export default class extends Vue {
placeholder: 'http://或者https://开头'
})
}
handleInputBoxValue (val: string) {
if (val === '') return false
if (isUrl(val)) {
@@ -196,41 +208,23 @@ export default class extends Vue {
this.$message.error('请输入合法的URL')
}
}
getDefaultPicBed () {
const current: string = this.$db.get('picBed.current')
async getDefaultPicBed () {
const currentPicBed = await this.getConfig<string>('picBed.current')
this.picBed.forEach(item => {
if (item.type === current) {
if (item.type === currentPicBed) {
this.picBedName = item.name
}
})
}
getPicBeds (event: Event, picBeds: IPicBedType[]) {
this.picBed = picBeds
this.getDefaultPicBed()
}
handleChangePicBed () {
this.buildMenu()
// this.menu.popup(remote.getCurrentWindow())
this.menu!.popup()
}
buildMenu () {
const _this = this
const submenu = this.picBed.filter(item => item.visible).map(item => {
return {
label: item.name,
type: 'radio',
checked: this.$db.get('picBed.current') === item.type,
click () {
_this.letPicGoSaveData({
'picBed.current': item.type,
'picBed.uploader': item.type
})
ipcRenderer.send('syncPicBed')
}
}
})
// @ts-ignore
this.menu = Menu.buildFromTemplate(submenu)
async handleChangePicBed () {
ipcRenderer.send(SHOW_UPLOAD_PAGE_MENU)
}
}
</script>
+5 -3
View File
@@ -86,17 +86,19 @@ export default class extends Vue {
customUrl: '',
options: ''
}
created () {
const config = this.$db.get('picBed.aliyun') as IAliYunConfig
async created () {
const config = await this.getConfig<IAliYunConfig>('picBed.aliyun')
if (config) {
this.form = Object.assign({}, config)
}
}
confirm () {
// @ts-ignore
this.$refs.aliyun.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.aliyun': this.form
})
const successNotification = new window.Notification('设置结果', {
+5 -3
View File
@@ -71,17 +71,19 @@ export default class extends Vue {
customUrl: '',
branch: ''
}
created () {
const config = this.$db.get('picBed.github') as IGitHubConfig
async created () {
const config = await this.getConfig<IGitHubConfig>('picBed.github')
if (config) {
this.form = Object.assign({}, config)
}
}
confirm () {
// @ts-ignore
this.$refs.github.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.github': this.form
})
const successNotification = new Notification('设置结果', {
+5 -3
View File
@@ -48,17 +48,19 @@ export default class extends Vue {
clientId: '',
proxy: ''
}
created () {
const config = this.$db.get('picBed.imgur') as IImgurConfig
async created () {
const config = await this.getConfig<IImgurConfig>('picBed.imgur')
if (config) {
this.form = Object.assign({}, config)
}
}
confirm () {
// @ts-ignore
this.$refs.imgur.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.imgur': this.form
})
const successNotification = new Notification('设置结果', {
+6 -2
View File
@@ -52,11 +52,12 @@ export default class extends Vue {
ipcRenderer.send('getPicBedConfig', this.$route.params.type)
ipcRenderer.on('getPicBedConfig', this.getPicBeds)
}
async handleConfirm () {
// @ts-ignore
const result = await this.$refs.configForm.validate()
if (result !== false) {
this.letPicGoSaveData({
this.saveConfig({
[`picBed.${this.type}`]: result
})
const successNotification = new Notification('设置结果', {
@@ -67,8 +68,9 @@ export default class extends Vue {
}
}
}
setDefaultPicBed (type: string) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.current': type,
'picBed.uploader': type
})
@@ -81,10 +83,12 @@ export default class extends Vue {
return true
}
}
getPicBeds (event: IpcRendererEvent, config: any[], name: string) {
this.config = config
this.picBedName = name
}
beforeDestroy () {
ipcRenderer.removeListener('getPicBedConfig', this.getPicBeds)
}
+6 -4
View File
@@ -17,7 +17,7 @@
:rules="{
required: true, message: 'AccessKey不能为空', trigger: 'blur'
}">
<el-input v-model="form.accessKey" placeholder="AccessKey" @keyup.native.enter="confirm('weiboForm')"></el-input>
<el-input v-model="form.accessKey" placeholder="AccessKey" @keyup.native.enter="confirm"></el-input>
</el-form-item>
<el-form-item
label="设定SecretKey"
@@ -88,17 +88,19 @@ export default class extends Vue {
options: '',
path: ''
}
created () {
const config = this.$db.get('picBed.qiniu') as IQiniuConfig
async created () {
const config = await this.getConfig<IQiniuConfig>('picBed.qiniu')
if (config) {
this.form = Object.assign({}, config)
}
}
confirm () {
// @ts-ignore
this.$refs.qiniu.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.qiniu': this.form
})
const successNotification = new Notification('设置结果', {
+6 -4
View File
@@ -41,17 +41,19 @@ export default class extends Vue {
form: ISMMSConfig = {
token: ''
}
created () {
const config = this.$db.get('picBed.smms.token') as (string | boolean)
async created () {
const config = await this.getConfig<string | boolean>('picBed.smms.token')
if (typeof config !== 'boolean') {
this.form.token = config
this.form.token = config || ''
}
}
confirm () {
// @ts-ignore
this.$refs.smms.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.smms': this.form
})
const successNotification = new window.Notification('设置结果', {
+10 -6
View File
@@ -30,7 +30,7 @@
:rules="{
required: true, message: 'SecretId不能为空', trigger: 'blur'
}">
<el-input v-model="form.secretId" placeholder="SecretId" @keyup.native.enter="confirm('weiboForm')"></el-input>
<el-input v-model="form.secretId" placeholder="SecretId" @keyup.native.enter="confirm"></el-input>
</el-form-item>
<el-form-item
label="设定SecretKey"
@@ -86,9 +86,10 @@
</div>
</template>
<script lang="ts">
import { ipcRenderer } from 'electron'
import { Component, Vue } from 'vue-property-decorator'
import mixin from '@/utils/ConfirmButtonMixin'
import { remote } from 'electron'
import { OPEN_URL } from '#/events/constants'
@Component({
name: 'tcyun',
mixins: [mixin]
@@ -104,17 +105,19 @@ export default class extends Vue {
customUrl: '',
version: 'v4'
}
created () {
const config = this.$db.get('picBed.tcyun') as ITcYunConfig
async created () {
const config = await this.getConfig<ITcYunConfig>('picBed.tcyun')
if (config) {
this.form = Object.assign({}, config)
}
}
confirm () {
// @ts-ignore
this.$refs.tcyun.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.tcyun': this.form
})
const successNotification = new window.Notification('设置结果', {
@@ -128,8 +131,9 @@ export default class extends Vue {
}
})
}
openWiki () {
remote.shell.openExternal('https://github.com/Molunerfinn/PicGo/wiki/%E8%AF%A6%E7%BB%86%E7%AA%97%E5%8F%A3%E7%9A%84%E4%BD%BF%E7%94%A8#腾讯云cos')
ipcRenderer.send(OPEN_URL, 'https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E8%85%BE%E8%AE%AF%E4%BA%91cos')
}
}
</script>
+5 -3
View File
@@ -79,17 +79,19 @@ export default class extends Vue {
options: '',
path: ''
}
created () {
const config = this.$db.get('picBed.upyun') as IUpYunConfig
async created () {
const config = await this.getConfig<IUpYunConfig>('picBed.upyun')
if (config) {
this.form = Object.assign({}, config)
}
}
confirm () {
// @ts-ignore
this.$refs.tcyun.validate((valid) => {
if (valid) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.upyun': this.form
})
const successNotification = new Notification('设置结果', {
-153
View File
@@ -1,153 +0,0 @@
<template>
<div id="weibo-view">
<el-row :gutter="16">
<el-col :span="16" :offset="4">
<div class="view-title">
微博图床设置[已停止支持]
</div>
<el-form
ref="weiboForm"
label-position="right"
label-width="120px"
size="small"
:model="form">
<el-form-item
label="设定用户名"
prop="username"
:rules="{
required: !chooseCookie, message: '用户名不能为空', trigger: 'blur'
}">
<el-input v-model="form.username" placeholder="用户名" @keyup.native.enter="confirm('weiboForm')" :disabled="chooseCookie"></el-input>
</el-form-item>
<el-form-item
label="设定密码"
prop="password"
:rules="{required: !chooseCookie,messsage: '密码不能为空',trigger: 'blur'}">
<el-input v-model="form.password" type="password" @keyup.native.enter="confirm('weiboForm')" placeholder="密码" :disabled="chooseCookie"></el-input>
</el-form-item>
<el-form-item
label="使用Cookie上传"
>
<el-switch
v-model="chooseCookie"
active-text="cookie模式"
@change="handleSwitchChange"
></el-switch>
<i class="el-icon-question" @click="openWiki"></i>
</el-form-item>
<el-form-item
label="设定Cookie"
prop="cookie"
:rules="{
required: chooseCookie, message: '密码不能为空', trigger: 'blur'
}">
<el-input v-model="form.cookie" @keyup.native.enter="confirm('weiboForm')" placeholder="Cookie" :disabled="!chooseCookie"></el-input>
</el-form-item>
<el-form-item label="* 图片质量">
<el-radio-group v-model="quality">
<el-radio label="thumbnail">缩略图</el-radio>
<el-radio label="mw690">中等尺寸</el-radio>
<el-radio label="large">原图</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button-group>
<el-button type="primary" @click="confirm('weiboForm')" round>确定</el-button>
<el-button type="success" @click="setDefaultPicBed('weibo')" round :disabled="defaultPicBed === 'weibo'">设为默认图床</el-button>
</el-button-group>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script>
import mixin from '@/utils/ConfirmButtonMixin'
export default {
name: 'weibo',
mixins: [mixin],
data () {
return {
form: {
username: '',
password: '',
cookie: ''
},
chooseCookie: false,
quality: 'large'
}
},
created () {
const config = this.$db.get('picBed.weibo')
if (config) {
this.form.username = config.username
this.form.password = config.password
this.quality = config.quality || 'large'
this.form.cookie = config.cookie
this.chooseCookie = config.chooseCookie
}
},
methods: {
confirm (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.letPicGoSaveData({
'picBed.weibo': {
username: this.form.username,
password: this.form.password,
quality: this.quality,
cookie: this.form.cookie,
chooseCookie: this.chooseCookie
}
})
const successNotification = new window.Notification('设置结果', {
body: '设置成功'
})
successNotification.onclick = () => {
return true
}
} else {
return false
}
})
},
handleSwitchChange () {
this.$refs['weiboForm'].resetFields()
},
openWiki () {
this.$electron.remote.shell.openExternal('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#微博图床')
}
}
}
</script>
<style lang='stylus'>
.el-message
left 60%
#weibo-view
.el-form
label
line-height 22px
padding-bottom 0
color #eee
.el-button-group
width 100%
.el-button
width 50%
.el-input__inner
border-radius 19px
.el-radio-group
margin-left 25px
.el-switch__label
color #eee
&.is-active
color #409EFF
.el-icon-question
font-size 20px
float right
margin-top 9px
color #eee
cursor pointer
transition .2s color ease-in-out
&:hover
color #409EFF
</style>
-5
View File
@@ -31,11 +31,6 @@ export default new Router({
component: () => import(/* webpackChunkName: "Upload" */ '@/pages/Upload.vue'),
name: 'upload'
},
{
path: 'weibo',
component: () => import(/* webpackChunkName: "Weibo" */ '@/pages/picbeds/Weibo.vue'),
name: 'weibo'
},
{
path: 'qiniu',
component: () => import(/* webpackChunkName: "Qiniu" */ '@/pages/picbeds/Qiniu.vue'),
+10 -3
View File
@@ -1,10 +1,17 @@
import { Component, Vue } from 'vue-property-decorator'
import { ipcRenderer } from 'electron'
import { IConfig } from 'picgo/dist/src/types'
@Component
export default class extends Vue {
defaultPicBed = this.$db.get('picBed.uploader') || this.$db.get('picBed.current') || 'smms'
defaultPicBed = 'smms'
async created () {
const config = await this.getConfig<IConfig>()
if (config) {
this.defaultPicBed = config?.picBed?.uploader || config?.picBed?.current || 'smms'
}
}
setDefaultPicBed (type: string) {
this.letPicGoSaveData({
this.saveConfig({
'picBed.current': type,
'picBed.uploader': type
})
+59
View File
@@ -0,0 +1,59 @@
import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
import { ipcRenderer, IpcRendererEvent } from 'electron'
import { uuid } from 'uuidv4'
import {
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
} from '#/events/constants'
import { IGalleryDB } from '#/types/extra-vue'
export class GalleryDB implements IGalleryDB {
async get<T> (filter?: IFilter): Promise<IGetResult<T>> {
const res = await this.msgHandler<IGetResult<T>>(PICGO_GET_DB, filter)
return res
}
async insert<T> (value: T): Promise<IResult<T>> {
const res = await this.msgHandler<IResult<T>>(PICGO_INSERT_DB, value)
return res
}
async insertMany<T> (value: T[]): Promise<IResult<T>[]> {
const res = await this.msgHandler<IResult<T>[]>(PICGO_INSERT_MANY_DB, value)
return res
}
async updateById (id: string, value: IObject): Promise<boolean> {
const res = await this.msgHandler<boolean>(PICGO_UPDATE_BY_ID_DB, id, value)
return res
}
async getById<T> (id: string): Promise<IResult<T> | undefined> {
const res = await this.msgHandler<IResult<T> | undefined>(PICGO_GET_BY_ID_DB, id)
return res
}
async removeById (id: string): Promise<void> {
const res = await this.msgHandler<void>(PICGO_REMOVE_BY_ID_DB, id)
return res
}
private msgHandler<T> (method: string, ...args: any[]): Promise<T> {
return new Promise((resolve) => {
const callbackId = uuid()
const callback = (event: IpcRendererEvent, data: T, returnCallbackId: string) => {
if (returnCallbackId === callbackId) {
resolve(data)
ipcRenderer.removeListener(method, callback)
}
}
ipcRenderer.on(method, callback)
ipcRenderer.send(method, ...args, callbackId)
})
}
}
export default new GalleryDB()
+3 -3
View File
@@ -14,16 +14,16 @@ const isSpecialKey = (keyCode: number) => {
const keyDetect = (event: KeyboardEvent) => {
const meta = process.platform === 'darwin' ? 'Cmd' : 'Super'
let specialKey = {
const specialKey = {
Ctrl: event.ctrlKey,
Shift: event.shiftKey,
Alt: event.altKey,
[meta]: event.metaKey
}
let pressKey = []
const pressKey = []
for (let i in specialKey) {
for (const i in specialKey) {
if (specialKey[i]) {
pressKey.push(i)
}
+25 -3
View File
@@ -1,8 +1,30 @@
import { Component, Vue } from 'vue-property-decorator'
import { ipcRenderer } from 'electron'
import { ipcRenderer, IpcRendererEvent } from 'electron'
import { PICGO_SAVE_CONFIG, PICGO_GET_CONFIG } from '#/events/constants'
import { uuid } from 'uuidv4'
@Component
export default class extends Vue {
letPicGoSaveData (data: IObj) {
ipcRenderer.send('picgoSaveData', data)
// support string key + value or object config
saveConfig (config: IObj | string, value?: any) {
if (typeof config === 'string') {
config = {
[config]: value
}
}
ipcRenderer.send(PICGO_SAVE_CONFIG, config)
}
getConfig<T> (key?: string): Promise<T | undefined> {
return new Promise((resolve) => {
const callbackId = uuid()
const callback = (event: IpcRendererEvent, config: T | undefined, returnCallbackId: string) => {
if (returnCallbackId === callbackId) {
resolve(config)
ipcRenderer.removeListener(PICGO_GET_CONFIG, callback)
}
}
ipcRenderer.on(PICGO_GET_CONFIG, callback)
ipcRenderer.send(PICGO_GET_CONFIG, key, callbackId)
})
}
}
+3
View File
@@ -4,11 +4,13 @@ export default class extends Vue {
mounted () {
this.disableDragEvent()
}
disableDragEvent () {
window.addEventListener('dragenter', this.disableDrag, false)
window.addEventListener('dragover', this.disableDrag)
window.addEventListener('drop', this.disableDrag)
}
disableDrag (e: DragEvent) {
const dropzone = document.getElementById('upload-area')
if (dropzone === null || !dropzone.contains(<Node>e.target)) {
@@ -17,6 +19,7 @@ export default class extends Vue {
e.dataTransfer!.dropEffect = 'none'
}
}
beforeDestroy () {
window.removeEventListener('dragenter', this.disableDrag, false)
window.removeEventListener('dragover', this.disableDrag)
-55
View File
@@ -1,55 +0,0 @@
import fs from 'fs-extra'
import path from 'path'
import { app } from 'electron'
import dayjs from 'dayjs'
const errorMsg = {
broken: 'PicGo 配置文件损坏,已经恢复为默认配置',
brokenButBackup: 'PicGo 配置文件损坏,已经恢复为备份配置'
}
function dbChecker () {
if (process.type !== 'renderer') {
if (!global.notificationList) global.notificationList = []
const STORE_PATH = app.getPath('userData')
const configFilePath = path.join(STORE_PATH, 'data.json')
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
if (!fs.existsSync(configFilePath)) {
return
}
let configFile: string = '{}'
let optionsTpl = {
title: '注意',
body: ''
}
try {
configFile = fs.readFileSync(configFilePath, { encoding: 'utf-8' })
JSON.parse(configFile)
} catch (e) {
fs.unlinkSync(configFilePath)
if (fs.existsSync(configFileBackupPath)) {
try {
configFile = fs.readFileSync(configFileBackupPath, { encoding: 'utf-8' })
JSON.parse(configFile)
fs.writeFileSync(configFilePath, configFile, { encoding: 'utf-8' })
const stats = fs.statSync(configFileBackupPath)
optionsTpl.body = `${errorMsg.brokenButBackup}\n备份文件版本:${dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')}`
global.notificationList.push(optionsTpl)
return
} catch (e) {
optionsTpl.body = errorMsg.broken
global.notificationList.push(optionsTpl)
return
}
}
optionsTpl.body = errorMsg.broken
global.notificationList.push(optionsTpl)
return
}
fs.writeFileSync(configFileBackupPath, configFile, { encoding: 'utf-8' })
}
}
export {
dbChecker
}
+24
View File
@@ -4,3 +4,27 @@ export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE'
export const TALKING_DATA_APPID = '7E6832BCE3F1438696579E541DFEBFDA'
export const TALKING_DATA_EVENT = 'TALKING_DATA_EVENT'
export const SHOW_PRIVACY_MESSAGE = 'SHOW_PRIVACY_MESSAGE'
export const PICGO_SAVE_CONFIG = 'PICGO_SAVE_CONFIG'
export const PICGO_GET_CONFIG = 'PICGO_GET_CONFIG'
export const PICGO_GET_DB = 'PICGO_GET_DB'
export const PICGO_INSERT_DB = 'PICGO_INSERT_DB'
export const PICGO_INSERT_MANY_DB = 'PICGO_INSERT_MANY_DB'
export const PICGO_UPDATE_BY_ID_DB = 'PICGO_UPDATE_BY_ID_DB'
export const PICGO_GET_BY_ID_DB = 'PICGO_GET_BY_ID_DB'
export const PICGO_REMOVE_BY_ID_DB = 'PICGO_REMOVE_BY_ID_DB'
export const PICGO_OPEN_FILE = 'PICGO_OPEN_FILE'
export const OPEN_DEVTOOLS = 'OPEN_DEVTOOLS'
export const SHOW_MINI_PAGE_MENU = 'SHOW_MINI_PAGE_MENU'
export const SHOW_MAIN_PAGE_MENU = 'SHOW_MAIN_PAGE_MENU'
export const SHOW_UPLOAD_PAGE_MENU = 'SHOW_UPLOAD_PAGE_MENU'
export const SHOW_PLUGIN_PAGE_MENU = 'SHOW_PLUGIN_PAGE_MENU'
export const MINIMIZE_WINDOW = 'MINIMIZE_WINDOW'
export const CLOSE_WINDOW = 'CLOSE_WINDOW'
export const OPEN_USER_STORE_FILE = 'OPEN_USER_STORE_FILE'
export const OPEN_URL = 'OPEN_URL'
export const RELOAD_APP = 'RELOAD_APP'
export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN'
export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING'
export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
export const PASTE_TEXT = 'PASTE_TEXT'
export const SET_MINI_WINDOW_POS = 'SET_MINI_WINDOW_POS'
+3 -7
View File
@@ -23,11 +23,7 @@ declare interface IWindowManager {
// https://stackoverflow.com/questions/35074713/extending-typescript-global-object-in-node-js/44387594#44387594
declare global {
namespace NodeJS {
interface Global {
PICGO_GUI_VERSION: string
PICGO_CORE_VERSION: string
notificationList: IAppNotification[]
}
}
var PICGO_GUI_VERSION: string
var PICGO_CORE_VERSION: string
var notificationList: IAppNotification[]
}
+14 -3
View File
@@ -1,14 +1,25 @@
import VueRouter, { Route } from 'vue-router'
import db from '#/datastore'
import axios from 'axios'
import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
interface IGalleryDB {
get<T>(filter?: IFilter): Promise<IGetResult<T>>
insert<T> (value: T): Promise<IResult<T>>
insertMany<T> (value: T[]): Promise<IResult<T>[]>
updateById (id: string, value: IObject): Promise<boolean>
getById<T> (id: string): Promise<IResult<T> | undefined>
removeById (id: string): Promise<void>
}
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter,
$route: Route,
$db: typeof db
$http: typeof axios
$builtInPicBed: string[]
$bus: Vue
letPicGoSaveData(data: IObj): void
$$db: IGalleryDB
saveConfig(data: IObj | string, value?: any): void
getConfig<T>(key?: string): Promise<T | undefined>
}
}
+24 -1
View File
@@ -15,7 +15,7 @@ declare interface ErrnoException extends Error {
stack?: string;
}
declare var __static: string
declare let __static: string
declare type ILogType = 'success' | 'info' | 'warn' | 'error'
@@ -45,6 +45,7 @@ interface ImgInfo {
extname?: string
imgUrl?: string
id?: string
type?: string
[propName: string]: any
}
@@ -93,6 +94,7 @@ interface IBrowserWindowOptions {
webPreferences: {
nodeIntegration: boolean,
nodeIntegrationInWorker: boolean,
contextIsolation: boolean,
backgroundThrottling: boolean
webSecurity?: boolean
},
@@ -144,6 +146,14 @@ interface IPicGoPlugin {
hasInstall?: boolean
}
interface IPicGoPluginConfig {
name: string
type: string
required: boolean
default?: any
[propName: string]: any
}
interface IPluginMenuConfig {
name: string
fullName?: string
@@ -312,3 +322,16 @@ interface IAnalyticsData {
interface IStringKeyMap {
[propName: string]: any
}
type ILogArgvType = string | number
type ILogArgvTypeWithError = ILogArgvType | Error
interface IMiniWindowPos {
x: number,
y: number,
height: number,
width: number
}
type PromiseResType<T> = T extends Promise<infer R> ? R : T
-33
View File
@@ -1,33 +0,0 @@
import db from '#/datastore'
import { IPasteStyle } from '#/types/enum'
import { handleUrlEncode } from './common'
const formatCustomLink = (customLink: string, item: ImgInfo) => {
let fileName = item.fileName!.replace(new RegExp(`\\${item.extname}$`), '')
const url = handleUrlEncode(item.url || item.imgUrl)
const formatObj = {
url,
fileName
}
const keys = Object.keys(formatObj) as ['url', 'fileName']
keys.forEach(item => {
if (customLink.indexOf(`$${item}`) !== -1) {
let reg = new RegExp(`\\$${item}`, 'g')
customLink = customLink.replace(reg, formatObj[item])
}
})
return customLink
}
export default (style: IPasteStyle, item: ImgInfo) => {
const url = handleUrlEncode(item.url || item.imgUrl)
const customLink = db.get('settings.customLink') || '$url'
const tpl = {
'markdown': `![](${url})`,
'HTML': `<img src="${url}"/>`,
'URL': url,
'UBB': `[IMG]${url}[/IMG]`,
'Custom': formatCustomLink(customLink, item)
}
return tpl[style]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es2020",
"target": "es2019", // https://github.com/TypeStrong/ts-loader/issues/1061
"module": "esnext",
"strict": true,
"jsx": "preserve",
+19 -3
View File
@@ -3,6 +3,9 @@ function resolve (dir) {
return path.join(__dirname, dir)
}
const arch = process.argv.includes('--ia32') ? 'ia32' : 'x64'
const macArch = process.argv.includes('--arm64') ? 'arm64' : 'x64'
const config = {
configureWebpack: {
devtool: 'nosources-source-map'
@@ -21,6 +24,7 @@ const config = {
},
pluginOptions: {
electronBuilder: {
nodeIntegration: true, // will remove in the future
customFileProtocol: 'picgo://./',
externals: ['picgo'],
chainWebpackMainProcess: config => {
@@ -62,11 +66,23 @@ const config = {
icon: 'build/icons/icon.icns',
extendInfo: {
LSUIElement: 1
}
},
target: [{
target: 'dmg',
arch: macArch
}],
artifactName: `PicGo-\${version}-${macArch}.dmg`
},
win: {
icon: 'build/icons/icon.ico',
target: 'nsis'
// eslint-disable-next-line no-template-curly-in-string
artifactName: `PicGo Setup \${version}-${arch}.exe`,
target: [{
target: 'nsis',
arch: [
arch
]
}]
},
nsis: {
shortcutName: 'PicGo',
@@ -86,7 +102,7 @@ const config = {
if (process.env.NODE_ENV === 'development') {
config.configureWebpack = {
devtool: 'eval-source-map'
devtool: 'source-map'
}
// for dev main process hot reload
config.pluginOptions.electronBuilder.mainProcessWatch = ['src/main/**/*']
+6423 -4947
View File
File diff suppressed because it is too large Load Diff