🔨 Refactor: upgrade vue2 -> vue3

This commit is contained in:
PiEgg 2023-01-06 17:21:27 +08:00
parent 624e5738d1
commit 66d8d714db
46 changed files with 6584 additions and 7834 deletions

View File

@ -8,7 +8,7 @@ module.exports = {
}, },
parser: 'vue-eslint-parser', parser: 'vue-eslint-parser',
extends: [ extends: [
'plugin:vue/essential', 'plugin:vue/vue3-recommended',
'@vue/standard', '@vue/standard',
'@vue/typescript' '@vue/typescript'
], ],

3
.gitignore vendored
View File

@ -19,3 +19,6 @@ dist_electron/
test.js test.js
.env .env
scripts/*.yml scripts/*.yml
#Electron-builder output
/dist_electron

View File

@ -30,7 +30,7 @@
</template> </template>
<script> <script>
export default { export default {
name: '', name: 'HomePage',
data () { data () {
return { return {
version: '', version: '',

View File

@ -3,25 +3,87 @@
"version": "2.4.0-beta.0", "version": "2.4.0-beta.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vue-cli-service electron:serve",
"build": "vue-cli-service electron:build", "build": "vue-cli-service electron:build",
"lint": "vue-cli-service lint", "lint": "vue-cli-service lint",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/", "bump": "bump-version",
"cz": "git-cz",
"dev": "vue-cli-service electron:serve",
"electron:build": "vue-cli-service electron:build", "electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve", "electron:serve": "vue-cli-service electron:serve",
"gen-i18n": "node ./scripts/gen-i18n-types.js",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps",
"cz": "git-cz",
"bump": "bump-version",
"release": "vue-cli-service electron:build --publish always", "release": "vue-cli-service electron:build --publish always",
"upload-dist": "node ./scripts/upload-dist-to-cos.js", "upload-dist": "node ./scripts/upload-dist-to-cos.js"
"gen-i18n": "node ./scripts/gen-i18n-types.js"
}, },
"main": "background.js", "dependencies": {
"husky": { "@element-plus/icons-vue": "^2.0.10",
"hooks": { "@picgo/i18n": "^1.0.0",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS" "@picgo/store": "^2.0.4",
} "axios": "^0.19.0",
"compare-versions": "^4.1.3",
"core-js": "^3.27.1",
"element-plus": "^2.2.28",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"mitt": "^3.0.0",
"picgo": "^1.5.0",
"qrcode.vue": "^3.3.3",
"shell-path": "2.1.0",
"uuidv4": "^6.2.11",
"vue": "^3.2.45",
"vue-router": "^4.1.6",
"vue3-lazyload": "^0.3.6",
"vue3-photo-preview": "^0.2.9",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@picgo/bump-version": "^1.1.2",
"@types/electron-devtools-installer": "^2.2.0",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^6.5.0",
"@types/js-yaml": "^4.0.5",
"@types/lowdb": "^1.0.9",
"@types/node": "^16.10.2",
"@types/request-promise-native": "^1.0.17",
"@types/semver": "^7.3.8",
"@types/write-file-atomic": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-router": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/eslint-config-standard": "^8.0.1",
"@vue/eslint-config-typescript": "^11.0.2",
"@vue/runtime-dom": "^3.2.45",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"dotenv": "^16.0.1",
"electron": "^16.0.6",
"electron-devtools-installer": "^3.2.0",
"eslint": "^8.31.0",
"eslint-config-standard": ">=16.0.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^9.8.0",
"husky": "^3.1.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"typescript": "^4.4.3",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
},
"commitlint": {
"extends": [
"./node_modules/@picgo/bump-version/commitlint-picgo"
]
}, },
"config": { "config": {
"commitizen": { "commitizen": {
@ -31,72 +93,10 @@
"config": "./node_modules/@picgo/bump-version/.cz-config.js" "config": "./node_modules/@picgo/bump-version/.cz-config.js"
} }
}, },
"commitlint": { "husky": {
"extends": [ "hooks": {
"./node_modules/@picgo/bump-version/commitlint-picgo" "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
] }
},
"dependencies": {
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.0.4",
"axios": "^0.19.0",
"compare-versions": "^4.1.3",
"core-js": "^3.3.2",
"element-ui": "^2.13.0",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"picgo": "^1.5.0",
"qrcode.vue": "^1.7.0",
"shell-path": "2.1.0",
"uuidv4": "^6.2.11",
"vue": "^2.6.10",
"vue-gallery": "^2.0.1",
"vue-lazyload": "^1.2.6",
"vue-router": "^3.1.3",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@picgo/bump-version": "^1.1.2",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^6.5.0",
"@types/js-yaml": "^4.0.5",
"@types/lowdb": "^1.0.9",
"@types/node": "^16.10.2",
"@types/request-promise-native": "^1.0.17",
"@types/semver": "^7.3.8",
"@types/write-file-atomic": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-plugin-router": "^4.0.0",
"@vue/cli-plugin-typescript": "^4.5.13",
"@vue/cli-service": "^4.0.0",
"@vue/eslint-config-standard": "^6.1.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/runtime-dom": "^3.2.45",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"dotenv": "^16.0.1",
"electron": "^16.0.6",
"electron-devtools-installer": "^3.2.0",
"eslint": "^7.32.0",
"eslint-config-standard": ">=16.0.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.0.0",
"husky": "^3.1.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"typescript": "^4.4.3",
"vue-cli-plugin-electron-builder": "^2.1.1",
"vue-property-decorator": "^8.3.0",
"vue-template-compiler": "^2.6.10"
}, },
"resolutions": { "resolutions": {
"@types/node": "^16.10.2", "@types/node": "^16.10.2",

View File

@ -1,26 +1,29 @@
import Vue from 'vue' import { createApp } from 'vue'
import App from './renderer/App.vue' import App from './renderer/App.vue'
import router from './renderer/router' import router from './renderer/router'
import ElementUI from 'element-ui' import ElementUI from 'element-plus'
import 'element-plus/dist/index.css'
import { webFrame } from 'electron' import { webFrame } from 'electron'
import 'element-ui/lib/theme-chalk/index.css' import VueLazyLoad from 'vue3-lazyload'
import VueLazyLoad from 'vue-lazyload'
import axios from 'axios' import axios from 'axios'
import mainMixin from './renderer/utils/mainMixin' import { mainMixin } from './renderer/utils/mainMixin'
import bus from '@/utils/bus' import { dragMixin } from '@/utils/mixin'
import { initTalkingData } from './renderer/utils/analytics' import { initTalkingData } from './renderer/utils/analytics'
import db from './renderer/utils/db' import db from './renderer/utils/db'
// import { T, i18n } from '#/i18n/index'
// import { handleURLParams } from '@/utils/beforeOpen'
import { i18nManager, T } from './renderer/i18n/index' import { i18nManager, T } from './renderer/i18n/index'
import { getConfig, saveConfig, sendToMain, triggerRPC } from '@/utils/dataSender'
import { store } from '@/store'
import vue3PhotoPreview from 'vue3-photo-preview'
import 'vue3-photo-preview/dist/index.css'
webFrame.setVisualZoomLevelLimits(1, 1) webFrame.setVisualZoomLevelLimits(1, 1)
// do here before vue init // do here before vue init
// handleURLParams() // handleURLParams()
Vue.config.productionTip = false const app = createApp(App)
Vue.prototype.$builtInPicBed = [
app.config.globalProperties.$builtInPicBed = [
'smms', 'smms',
'imgur', 'imgur',
'qiniu', 'qiniu',
@ -29,21 +32,27 @@ Vue.prototype.$builtInPicBed = [
'aliyun', 'aliyun',
'github' 'github'
] ]
Vue.prototype.$$db = db
Vue.prototype.$http = axios
Vue.prototype.$bus = bus
Vue.prototype.$T = T
Vue.prototype.$i18n = i18nManager
Vue.use(ElementUI) app.config.globalProperties.$$db = db
Vue.use(VueLazyLoad, { app.config.globalProperties.$http = axios
app.config.globalProperties.$T = T
app.config.globalProperties.$i18n = i18nManager
app.config.globalProperties.getConfig = getConfig
app.config.globalProperties.triggerRPC = triggerRPC
app.config.globalProperties.saveConfig = saveConfig
app.config.globalProperties.sendToMain = sendToMain
app.mixin(mainMixin)
app.mixin(dragMixin)
app.use(VueLazyLoad, {
error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg` error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg`
}) })
Vue.mixin(mainMixin) app.use(ElementUI)
app.use(router)
app.use(store)
app.use(vue3PhotoPreview)
new Vue({ app.mount('#app')
router,
render: h => h(App)
}).$mount('#app')
initTalkingData() initTalkingData()

View File

@ -23,6 +23,7 @@ const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
export const uploadClipboardFiles = async (): Promise<string> => { export const uploadClipboardFiles = async (): Promise<string> => {
const img = await handleClipboardUploading() const img = await handleClipboardUploading()
console.log(img)
if (img !== false) { if (img !== false) {
if (img.length > 0) { if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW) const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)

View File

@ -21,6 +21,7 @@ import { T } from '~/main/i18n'
// Cross-process support may be required in the future // Cross-process support may be required in the future
class GuiApi implements IGuiApi { class GuiApi implements IGuiApi {
// eslint-disable-next-line no-use-before-define
private static instance: GuiApi private static instance: GuiApi
private windowId: number = -1 private windowId: number = -1
private settingWindowId: number = -1 private settingWindowId: number = -1

View File

@ -70,6 +70,7 @@ export default {
}) })
ipcMain.on('uploadClipboardFilesFromUploadPage', () => { ipcMain.on('uploadClipboardFilesFromUploadPage', () => {
console.log('handle')
uploadClipboardFiles() uploadClipboardFiles()
}) })

View File

@ -9,7 +9,7 @@ import {
import { privacyManager } from '~/main/utils/privacyManager' import { privacyManager } from '~/main/utils/privacyManager'
import pkg from 'root/package.json' import pkg from 'root/package.json'
import GuiApi from 'apis/gui' import GuiApi from 'apis/gui'
import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN, SHOW_MAIN_PAGE_DONATION, SHOW_MAIN_PAGE_QRCODE } from '~/universal/events/constants' import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_DONE, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN, SHOW_MAIN_PAGE_DONATION, SHOW_MAIN_PAGE_QRCODE } from '~/universal/events/constants'
import picgoCoreIPC from '~/main/events/picgoCoreIPC' import picgoCoreIPC from '~/main/events/picgoCoreIPC'
import { PicGo as PicGoCore } from 'picgo' import { PicGo as PicGoCore } from 'picgo'
import { T } from '~/main/i18n' import { T } from '~/main/i18n'
@ -210,6 +210,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)! const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName) window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, false) window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, false)
window.webContents.send(PICGO_HANDLE_PLUGIN_DONE, plugin.fullName)
if (plugin.config.transformer.name) { if (plugin.config.transformer.name) {
handleRestoreState('transformer', plugin.config.transformer.name) handleRestoreState('transformer', plugin.config.transformer.name)
} }

View File

@ -105,11 +105,7 @@ class LifeCycle {
await remoteNoticeHandler.init() await remoteNoticeHandler.init()
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START) remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
} }
if (!app.isReady()) { app.whenReady().then(readyFunction)
app.on('ready', readyFunction)
} else {
readyFunction()
}
} }
private onRunning () { private onRunning () {

View File

@ -1,12 +1,28 @@
<template> <template>
<div id="app"> <div id="app">
<router-view></router-view> <router-view />
</div> </div>
</template> </template>
<script> <script lang="ts" setup>
import { useStore } from '@/hooks/useStore'
import { onBeforeMount } from 'vue'
import { getConfig } from './utils/dataSender'
import type { IConfig } from 'picgo'
const store = useStore()
onBeforeMount(async () => {
const config = await getConfig<IConfig>()
if (config) {
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
}
})
</script>
<script lang="ts">
export default { export default {
name: 'picgo' name: 'PicGoApp'
} }
</script> </script>

View File

@ -1,37 +0,0 @@
<template>
<div id="choose-pic-bed">
<span>{{ $T('CHOOSE_YOUR_DEFAULT_PICBED', { d: label }) }}</span>
<el-switch
v-model="value"
@change="choosePicBed"
>
</el-switch>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component({
name: 'choose-pic-bed'
})
export default class extends Vue {
value = false
@Prop() type!: string
@Prop() label!: string
async created () {
const current = await this.getConfig<string>('picBed.current')
if (this.type === current) {
this.value = true
}
}
choosePicBed () {
this.saveConfig({
'picBed.current': this.type,
'picBed.uploader': this.type
})
this.$emit('update:choosed', this.type)
}
}
</script>
<style lang='stylus'>
</style>

View File

@ -1,11 +1,14 @@
<template> <template>
<div id="config-form"> <div
id="config-form"
:class="props.colorMode === 'white' ? 'white' : ''"
>
<el-form <el-form
label-position="right" ref="$form"
label-width="120px" label-position="left"
label-width="50%"
:model="ruleForm" :model="ruleForm"
ref="form" size="small"
size="mini"
> >
<el-form-item <el-form-item
:label="$T('UPLOADER_CONFIG_NAME')" :label="$T('UPLOADER_CONFIG_NAME')"
@ -13,25 +16,25 @@
prop="_configName" prop="_configName"
> >
<el-input <el-input
type="input"
v-model="ruleForm._configName" v-model="ruleForm._configName"
type="input"
:placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')" :placeholder="$T('UPLOADER_CONFIG_PLACEHOLDER')"
></el-input> />
</el-form-item> </el-form-item>
<!-- dynamic config --> <!-- dynamic config -->
<el-form-item <el-form-item
v-for="(item, index) in configList" v-for="(item, index) in configList"
:key="item.name + index"
:label="item.alias || item.name" :label="item.alias || item.name"
:required="item.required" :required="item.required"
:prop="item.name" :prop="item.name"
:key="item.name + index"
> >
<el-input <el-input
v-if="item.type === 'input' || item.type === 'password'" v-if="item.type === 'input' || item.type === 'password'"
:type="item.type === 'password' ? 'password' : 'input'"
v-model="ruleForm[item.name]" v-model="ruleForm[item.name]"
:type="item.type === 'password' ? 'password' : 'input'"
:placeholder="item.message || item.name" :placeholder="item.message || item.name"
></el-input> />
<el-select <el-select
v-else-if="item.type === 'list' && item.choices" v-else-if="item.type === 'list' && item.choices"
v-model="ruleForm[item.name]" v-model="ruleForm[item.name]"
@ -39,10 +42,10 @@
> >
<el-option <el-option
v-for="choice in item.choices" v-for="choice in item.choices"
:label="choice.name || choice.value || choice"
:key="choice.name || choice.value || choice" :key="choice.name || choice.value || choice"
:label="choice.name || choice.value || choice"
:value="choice.value || choice" :value="choice.value || choice"
></el-option> />
</el-select> </el-select>
<el-select <el-select
v-else-if="item.type === 'checkbox' && item.choices" v-else-if="item.type === 'checkbox' && item.choices"
@ -53,85 +56,89 @@
> >
<el-option <el-option
v-for="choice in item.choices" v-for="choice in item.choices"
:label="choice.name || choice.value || choice"
:key="choice.value || choice" :key="choice.value || choice"
:label="choice.name || choice.value || choice"
:value="choice.value || choice" :value="choice.value || choice"
></el-option> />
</el-select> </el-select>
<el-switch <el-switch
v-else-if="item.type === 'confirm'" v-else-if="item.type === 'confirm'"
v-model="ruleForm[item.name]" v-model="ruleForm[item.name]"
active-text="yes" active-text="yes"
inactive-text="no" inactive-text="no"
> />
</el-switch>
</el-form-item> </el-form-item>
<slot></slot> <slot />
</el-form> </el-form>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { import { reactive, ref, watch, defineExpose, toRefs } from 'vue'
Component,
Vue,
Prop,
Watch
} from 'vue-property-decorator'
import { cloneDeep, union } from 'lodash' import { cloneDeep, union } from 'lodash'
import { getConfig } from '@/utils/dataSender'
import { useRoute } from 'vue-router'
import type { FormInstance } from 'element-plus'
@Component({ interface IProps {
name: 'config-form' config: any[]
}) type: 'uploader' | 'transformer' | 'plugin'
export default class extends Vue { id: string
@Prop() private config!: any[] colorMode?: 'white' | 'dark'
@Prop() readonly type!: 'uploader' | 'transformer' | 'plugin' }
@Prop() readonly id!: string
configList: IPicGoPluginConfig[] = [] const props = defineProps<IProps>()
ruleForm: IStringKeyMap = {} const $route = useRoute()
@Watch('config', { const $form = ref<FormInstance>()
const configList = ref<IPicGoPluginConfig[]>([])
const ruleForm = reactive<IStringKeyMap>({})
watch(toRefs(props.config), (val: IPicGoPluginConfig[]) => {
handleConfigChange(val)
}, {
deep: true, deep: true,
immediate: true immediate: true
}) })
handleConfigChange (val: any) {
this.handleConfig(val)
}
async validate () { function handleConfigChange (val: any) {
handleConfig(val)
}
async function validate (): Promise<IStringKeyMap | false> {
return new Promise((resolve) => { return new Promise((resolve) => {
// @ts-ignore $form.value?.validate((valid: boolean) => {
this.$refs.form.validate((valid: boolean) => {
if (valid) { if (valid) {
resolve(this.ruleForm) resolve(ruleForm)
} else { } else {
resolve(false) resolve(false)
return false return false
} }
}) })
}) })
} }
getConfigType () { function getConfigType () {
switch (this.type) { switch (props.type) {
case 'plugin': { case 'plugin': {
return this.id return props.id
} }
case 'uploader': { case 'uploader': {
return `picBed.${this.id}` return `picBed.${props.id}`
} }
case 'transformer': { case 'transformer': {
return `transformer.${this.id}` return `transformer.${props.id}`
} }
default: default:
return 'unknown' return 'unknown'
} }
} }
async handleConfig (val: IPicGoPluginConfig[]) { async function handleConfig (val: IPicGoPluginConfig[]) {
const config = await this.getCurConfigFormData() const config = await getCurConfigFormData()
const configId = this.$route.params.configId const configId = $route.params.configId
this.ruleForm = Object.assign({}, config) Object.assign(ruleForm, config)
if (val.length > 0) { if (val.length > 0) {
this.configList = cloneDeep(val).map((item) => { configList.value = cloneDeep(val).map((item) => {
if (!configId) return item if (!configId) return item
let defaultValue = item.default !== undefined let defaultValue = item.default !== undefined
? item.default ? item.default
@ -147,18 +154,22 @@ export default class extends Vue {
if (config && config[item.name] !== undefined) { if (config && config[item.name] !== undefined) {
defaultValue = config[item.name] defaultValue = config[item.name]
} }
this.$set(this.ruleForm, item.name, defaultValue) ruleForm[item.name] = defaultValue
return item return item
}) })
} }
}
async getCurConfigFormData () {
const configId = this.$route.params.configId
const curTypeConfigList = await this.getConfig<IStringKeyMap[]>(`uploader.${this.id}.configList`) || []
return curTypeConfigList.find(i => i._id === configId) || {}
}
} }
async function getCurConfigFormData () {
const configId = $route.params.configId
const curTypeConfigList = await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`) || []
return curTypeConfigList.find(i => i._id === configId) || {}
}
defineExpose({
validate,
getConfigType
})
</script> </script>
<style lang='stylus'> <style lang='stylus'>
#config-form #config-form
@ -166,15 +177,25 @@ export default class extends Vue {
label label
line-height 22px line-height 22px
padding-bottom 0 padding-bottom 0
&-item
display: flex
justify-content space-between
border-bottom 1px solid darken(#eee, 50%)
padding-bottom 16px
&:last-child
border-bottom none
&__content
justify-content flex-end
.el-button-group .el-button-group
width 100% width 100%
.el-button .el-button
width 50% width 50%
.el-input__inner
border-radius 19px
.el-radio-group .el-radio-group
margin-left 25px margin-left 25px
.el-switch__label .el-switch__label
&.is-active &.is-active
color #409EFF color #409EFF
&.white
.el-form-item
border-bottom 1px solid #ddd
</style> </style>

View File

@ -1,69 +1,84 @@
<template> <template>
<el-dialog <el-dialog
v-model="showInputBoxVisible"
:title="inputBoxOptions.title || $T('INPUT')" :title="inputBoxOptions.title || $T('INPUT')"
:visible.sync="showInputBoxVisible"
:modal-append-to-body="false" :modal-append-to-body="false"
> >
<el-input <el-input
v-model="inputBoxValue" v-model="inputBoxValue"
:placeholder="inputBoxOptions.placeholder"></el-input> :placeholder="inputBoxOptions.placeholder"
<span slot="footer"> />
<el-button @click="handleInputBoxCancel" round>{{ $T('CANCEL') }}</el-button> <template #footer>
<el-button type="primary" @click="handleInputBoxConfirm" round>{{ $T('CONFIRM') }}</el-button> <el-button
</span> round
@click="handleInputBoxCancel"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="handleInputBoxConfirm"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog> </el-dialog>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { Component, Vue } from 'vue-property-decorator' import { ref, reactive, onBeforeUnmount, onBeforeMount } from 'vue'
import { ipcRenderer, IpcRendererEvent } from 'electron' import { ipcRenderer, IpcRendererEvent } from 'electron'
import { import {
SHOW_INPUT_BOX, SHOW_INPUT_BOX,
SHOW_INPUT_BOX_RESPONSE SHOW_INPUT_BOX_RESPONSE
} from '~/universal/events/constants' } from '~/universal/events/constants'
@Component({ import $bus from '@/utils/bus'
name: 'input-box-dialog' import { sendToMain } from '@/utils/dataSender'
}) const inputBoxValue = ref('')
export default class extends Vue { const showInputBoxVisible = ref(false)
inputBoxValue = '' const inputBoxOptions = reactive({
showInputBoxVisible = false
inputBoxOptions = {
title: '', title: '',
placeholder: '' placeholder: ''
} })
created () { onBeforeMount(() => {
ipcRenderer.on(SHOW_INPUT_BOX, this.ipcEventHandler) ipcRenderer.on(SHOW_INPUT_BOX, ipcEventHandler)
this.$bus.$on(SHOW_INPUT_BOX, this.initInputBoxValue) $bus.on(SHOW_INPUT_BOX, initInputBoxValue)
} })
ipcEventHandler (evt: IpcRendererEvent, options: IShowInputBoxOption) { function ipcEventHandler (evt: IpcRendererEvent, options: IShowInputBoxOption) {
this.initInputBoxValue(options) initInputBoxValue(options)
} }
initInputBoxValue (options: IShowInputBoxOption) { function initInputBoxValue (options: IShowInputBoxOption) {
this.inputBoxValue = options.value || '' inputBoxValue.value = options.value || ''
this.inputBoxOptions.title = options.title || '' inputBoxOptions.title = options.title || ''
this.inputBoxOptions.placeholder = options.placeholder || '' inputBoxOptions.placeholder = options.placeholder || ''
this.showInputBoxVisible = true showInputBoxVisible.value = true
} }
handleInputBoxCancel () { function handleInputBoxCancel () {
// TODO: RPCServer // TODO: RPCServer
this.showInputBoxVisible = false showInputBoxVisible.value = false
ipcRenderer.send(SHOW_INPUT_BOX, '') sendToMain(SHOW_INPUT_BOX, '')
this.$bus.$emit(SHOW_INPUT_BOX_RESPONSE, '') $bus.emit(SHOW_INPUT_BOX_RESPONSE, '')
} }
handleInputBoxConfirm () { function handleInputBoxConfirm () {
this.showInputBoxVisible = false showInputBoxVisible.value = false
ipcRenderer.send(SHOW_INPUT_BOX, this.inputBoxValue) sendToMain(SHOW_INPUT_BOX, inputBoxValue.value)
this.$bus.$emit(SHOW_INPUT_BOX_RESPONSE, this.inputBoxValue) $bus.emit(SHOW_INPUT_BOX_RESPONSE, inputBoxValue.value)
} }
beforeDestroy () { onBeforeUnmount(() => {
ipcRenderer.removeListener(SHOW_INPUT_BOX, this.ipcEventHandler) ipcRenderer.removeListener(SHOW_INPUT_BOX, ipcEventHandler)
this.$bus.$off(SHOW_INPUT_BOX) $bus.off(SHOW_INPUT_BOX)
} })
</script>
<script lang="ts">
export default {
name: 'InputBoxDialog'
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>

View File

@ -0,0 +1,6 @@
import { inject } from 'vue'
import { storeKey } from '@/store'
export const useStore = () => {
return inject(storeKey) ?? null
}

View File

@ -19,7 +19,7 @@ export class I18nManager {
ipcRenderer.send(GET_CURRENT_LANGUAGE) ipcRenderer.send(GET_CURRENT_LANGUAGE)
ipcRenderer.once(GET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => { ipcRenderer.once(GET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => {
this.setLocales(lang, locales) this.setLocales(lang, locales)
bus.$emit(FORCE_UPDATE) bus.emit(FORCE_UPDATE)
}) })
} }
@ -38,7 +38,7 @@ export class I18nManager {
this.getLanguageList() this.getLanguageList()
ipcRenderer.on(SET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => { ipcRenderer.on(SET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => {
this.setLocales(lang, locales) this.setLocales(lang, locales)
bus.$emit(FORCE_UPDATE) bus.emit(FORCE_UPDATE)
}) })
} }

View File

@ -1,37 +1,69 @@
<template> <template>
<div id="main-page"> <div id="main-page">
<div class="fake-title-bar" :class="{ 'darwin': os === 'darwin' }"> <div
class="fake-title-bar"
:class="{ 'darwin': os === 'darwin' }"
>
<div class="fake-title-bar__title"> <div class="fake-title-bar__title">
PicGo - {{ version }} PicGo - {{ version }}
</div> </div>
<div class="handle-bar" v-if="os !== 'darwin'"> <div
<i class="el-icon-minus" @click="minimizeWindow"></i> v-if="os !== 'darwin'"
<i class="el-icon-circle-plus-outline" @click="openMiniWindow"></i> class="handle-bar"
<i class="el-icon-close" @click="closeWindow"></i> >
<el-icon
class="minus"
@click="minimizeWindow"
>
<Minus />
</el-icon>
<el-icon
class="plus"
@click="openMiniWindow"
>
<CirclePlus />
</el-icon>
<el-icon
class="close"
@click="closeWindow"
>
<Close />
</el-icon>
</div> </div>
</div> </div>
<el-row style="padding-top: 22px;" class="main-content"> <el-row
<el-col :span="5" class="side-bar-menu"> style="padding-top: 22px;"
class="main-content"
>
<el-col
class="side-bar-menu"
>
<el-menu <el-menu
class="picgo-sidebar" class="picgo-sidebar"
:default-active="defaultActive" :default-active="defaultActive"
@select="handleSelect"
:unique-opened="true" :unique-opened="true"
@select="handleSelect"
@open="handleGetPicPeds" @open="handleGetPicPeds"
> >
<el-menu-item index="upload"> <el-menu-item :index="routerConfig.UPLOAD_PAGE">
<i class="el-icon-upload"></i> <el-icon>
<span slot="title">{{ $T('UPLOAD_AREA') }}</span> <UploadFilled />
</el-icon>
<span>{{ $T('UPLOAD_AREA') }}</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="gallery"> <el-menu-item :index="routerConfig.GALLERY_PAGE">
<i class="el-icon-picture"></i> <el-icon>
<span slot="title">{{ $T('GALLERY') }}</span> <PictureFilled />
</el-icon>
<span>{{ $T('GALLERY') }}</span>
</el-menu-item> </el-menu-item>
<el-submenu <el-sub-menu
index="sub-menu" index="sub-menu"
> >
<template slot="title"> <template #title>
<i class="el-icon-menu"></i> <el-icon>
<Menu />
</el-icon>
<span>{{ $T('PICBEDS_SETTINGS') }}</span> <span>{{ $T('PICBEDS_SETTINGS') }}</span>
</template> </template>
<template <template
@ -39,72 +71,97 @@
> >
<el-menu-item <el-menu-item
v-if="item.visible" v-if="item.visible"
:index="`uploader-config-page-${item.type}`"
:key="item.type" :key="item.type"
:index="`${routerConfig.UPLOADER_CONFIG_PAGE}-${item.type}`"
> >
<!-- <i :class="`el-icon-ui-${item.type}`"></i> --> <span>{{ item.name }}</span>
<span slot="title">{{ item.name }}</span>
</el-menu-item> </el-menu-item>
</template> </template>
</el-submenu> </el-sub-menu>
<el-menu-item index="setting"> <el-menu-item :index="routerConfig.SETTING_PAGE">
<i class="el-icon-setting"></i> <el-icon>
<span slot="title">{{ $T('PICGO_SETTINGS') }}</span> <Setting />
</el-icon>
<span>{{ $T('PICGO_SETTINGS') }}</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="plugin"> <el-menu-item :index="routerConfig.PLUGIN_PAGE">
<i class="el-icon-share"></i> <el-icon>
<span slot="title">{{ $T('PLUGIN_SETTINGS') }}</span> <Share />
</el-icon>
<span>{{ $T('PLUGIN_SETTINGS') }}</span>
</el-menu-item> </el-menu-item>
</el-menu> </el-menu>
<i class="el-icon-info setting-window" @click="openDialog"></i> <el-icon
class="info-window"
@click="openMenu"
>
<InfoFilled />
</el-icon>
</el-col> </el-col>
<el-col <el-col
:span="19" :span="19"
:offset="5" :offset="5"
style="height: 428px" style="height: 428px"
class="main-wrapper" class="main-wrapper"
:class="{ 'darwin': os === 'darwin' }"> :class="{ 'darwin': os === 'darwin' }"
<transition name="picgo-fade" mode="out-in"> >
<keep-alive> <router-view
<router-view v-if="$route && $route.meta && $route.meta.keepAlive"></router-view> v-slot="{ Component }"
>
<transition
name="picgo-fade"
mode="out-in"
>
<keep-alive :include="keepAlivePages">
<component
:is="Component"
/>
</keep-alive> </keep-alive>
</transition> </transition>
<transition name="picgo-fade" mode="out-in"> </router-view>
<router-view :key="$route.path" v-if="!($route && $route.meta && $route.meta.keepAlive)"></router-view>
</transition>
</el-col> </el-col>
</el-row> </el-row>
<el-dialog <el-dialog
v-model="visible"
:title="$T('SPONSOR_PICGO')" :title="$T('SPONSOR_PICGO')"
:visible.sync="visible"
width="70%" width="70%"
top="10vh" top="10vh"
> >
{{ $T('PICGO_SPONSOR_TEXT') }} {{ $T('PICGO_SPONSOR_TEXT') }}
<el-row class="support"> <el-row class="support">
<el-col :span="12"> <el-col :span="12">
<img src="https://user-images.githubusercontent.com/12621342/34188165-e7cdf372-e56f-11e7-8732-1338c88b9bb7.jpg" :alt="$T('ALIPAY')"> <img
<div class="support-title">{{ $T('ALIPAY') }}</div> src="https://user-images.githubusercontent.com/12621342/34188165-e7cdf372-e56f-11e7-8732-1338c88b9bb7.jpg"
:alt="$T('ALIPAY')"
>
<div class="support-title">
{{ $T('ALIPAY') }}
</div>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<img src="https://user-images.githubusercontent.com/12621342/34188201-212cda84-e570-11e7-9b7a-abb298699d85.jpg" :alt="$T('WECHATPAY')"> <img
<div class="support-title">{{ $T('WECHATPAY') }}</div> src="https://user-images.githubusercontent.com/12621342/34188201-212cda84-e570-11e7-9b7a-abb298699d85.jpg"
:alt="$T('WECHATPAY')"
>
<div class="support-title">
{{ $T('WECHATPAY') }}
</div>
</el-col> </el-col>
</el-row> </el-row>
</el-dialog> </el-dialog>
<el-dialog <el-dialog
v-model="qrcodeVisible"
class="qrcode-dialog" class="qrcode-dialog"
top="3vh" top="3vh"
width="60%" width="60%"
:title="$T('PICBED_QRCODE')" :title="$T('PICBED_QRCODE')"
:visible.sync="qrcodeVisible"
:modal-append-to-body="false" :modal-append-to-body="false"
lock-scroll lock-scroll
> >
<el-form <el-form
label-position="left" label-position="left"
label-width="70px" label-width="70px"
size="mini" size="small"
> >
<el-form-item <el-form-item
:label="$T('CHOOSE_PICBED')" :label="$T('CHOOSE_PICBED')"
@ -119,7 +176,7 @@
:key="item.type" :key="item.type"
:label="item.name" :label="item.name"
:value="item.type" :value="item.type"
></el-option> />
</el-select> </el-select>
<el-button <el-button
v-show="choosedPicBedForQRCode.length > 0" v-show="choosedPicBedForQRCode.length > 0"
@ -143,17 +200,32 @@
<input-box-dialog /> <input-box-dialog />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { Component, Vue, Watch } from 'vue-property-decorator' // import { Component, Vue, Watch } from 'vue-property-decorator'
import {
Setting,
UploadFilled,
PictureFilled,
Menu,
Share,
InfoFilled,
Minus,
CirclePlus,
Close
} from '@element-plus/icons-vue'
import { ElMessage as $message } from 'element-plus'
import { T } from '@/i18n/index'
import { ref, onBeforeUnmount, Ref, onBeforeMount, watch, nextTick, reactive } from 'vue'
import { onBeforeRouteUpdate, useRouter } from 'vue-router'
import QrcodeVue from 'qrcode.vue' import QrcodeVue from 'qrcode.vue'
import pick from 'lodash/pick' import pick from 'lodash/pick'
import pkg from 'root/package.json' import pkg from 'root/package.json'
import * as config from '@/router/config'
import { import {
ipcRenderer, ipcRenderer,
IpcRendererEvent, IpcRendererEvent,
clipboard clipboard
} from 'electron' } from 'electron'
import mixin from '@/utils/mixin'
import InputBoxDialog from '@/components/InputBoxDialog.vue' import InputBoxDialog from '@/components/InputBoxDialog.vue'
import { import {
MINIMIZE_WINDOW, MINIMIZE_WINDOW,
@ -163,63 +235,58 @@ import {
SHOW_MAIN_PAGE_DONATION, SHOW_MAIN_PAGE_DONATION,
GET_PICBEDS GET_PICBEDS
} from '~/universal/events/constants' } from '~/universal/events/constants'
@Component({ import { getConfig, sendToMain } from '@/utils/dataSender'
name: 'main-page', const version = ref(process.env.NODE_ENV === 'production' ? pkg.version : 'Dev')
mixins: [mixin], const routerConfig = reactive(config)
components: { const defaultActive = ref(routerConfig.UPLOAD_PAGE)
InputBoxDialog, const visible = ref(false)
QrcodeVue const os = ref('')
} const $router = useRouter()
}) const picBed: Ref<IPicBedType[]> = ref([])
export default class extends Vue { const qrcodeVisible = ref(false)
version = process.env.NODE_ENV === 'production' ? pkg.version : 'Dev' const picBedConfigString = ref('')
defaultActive = 'upload' const choosedPicBedForQRCode: Ref<string[]> = ref([])
visible = false
keyBindingVisible = false const keepAlivePages = $router.getRoutes().filter(item => item.meta.keepAlive).map(item => item.name as string)
customLinkVisible = false
os = '' onBeforeMount(() => {
picBed: IPicBedType[] = [] os.value = process.platform
qrcodeVisible = false sendToMain(GET_PICBEDS)
picBedConfigString = '' ipcRenderer.on(GET_PICBEDS, getPicBeds)
choosedPicBedForQRCode: string[] = [] handleGetPicPeds()
created () {
this.os = process.platform
ipcRenderer.send(GET_PICBEDS)
ipcRenderer.on(GET_PICBEDS, this.getPicBeds)
this.handleGetPicPeds()
ipcRenderer.on(SHOW_MAIN_PAGE_QRCODE, () => { ipcRenderer.on(SHOW_MAIN_PAGE_QRCODE, () => {
this.qrcodeVisible = true qrcodeVisible.value = true
}) })
ipcRenderer.on(SHOW_MAIN_PAGE_DONATION, () => { ipcRenderer.on(SHOW_MAIN_PAGE_DONATION, () => {
this.visible = true visible.value = true
})
})
watch(() => choosedPicBedForQRCode, (val) => {
if (val.value.length > 0) {
nextTick(async () => {
const picBedConfig = await getConfig('picBed')
const config = pick(picBedConfig, ...choosedPicBedForQRCode.value)
picBedConfigString.value = JSON.stringify(config)
}) })
} }
}, { deep: true })
@Watch('choosedPicBedForQRCode') const handleGetPicPeds = () => {
choosedPicBedForQRCodeChange (val: string[]) { sendToMain(GET_PICBEDS)
if (val.length > 0) { }
this.$nextTick(async () => {
const picBedConfig = await this.getConfig('picBed')
const config = pick(picBedConfig, ...this.choosedPicBedForQRCode)
this.picBedConfigString = JSON.stringify(config)
})
}
}
handleGetPicPeds = () => { const handleSelect = (index: string) => {
ipcRenderer.send(GET_PICBEDS) defaultActive.value = index
} const type = index.match(routerConfig.UPLOADER_CONFIG_PAGE)
handleSelect (index: string) {
const type = index.match(/uploader-config-page-/)
if (type === null) { if (type === null) {
this.$router.push({ $router.push({
name: index name: index
}) })
} else { } else {
const type = index.replace(/uploader-config-page-/, '') const type = index.replace(`${routerConfig.UPLOADER_CONFIG_PAGE}-`, '')
this.$router.push({ $router.push({
name: 'UploaderConfigPage', name: routerConfig.UPLOADER_CONFIG_PAGE,
params: { params: {
type type
} }
@ -237,42 +304,49 @@ export default class extends Vue {
// }) // })
// } // }
} }
} }
minimizeWindow () { function minimizeWindow () {
ipcRenderer.send(MINIMIZE_WINDOW) sendToMain(MINIMIZE_WINDOW)
} }
closeWindow () { function closeWindow () {
ipcRenderer.send(CLOSE_WINDOW) sendToMain(CLOSE_WINDOW)
} }
openDialog () { function openMenu () {
ipcRenderer.send(SHOW_MAIN_PAGE_MENU) sendToMain(SHOW_MAIN_PAGE_MENU)
} }
openMiniWindow () { function openMiniWindow () {
ipcRenderer.send('openMiniWindow') sendToMain('openMiniWindow')
} }
handleCopyPicBedConfig () { function handleCopyPicBedConfig () {
clipboard.writeText(this.picBedConfigString) clipboard.writeText(picBedConfigString.value)
this.$message.success(this.$T('COPY_PICBED_CONFIG_SUCCEED')) $message.success(T('COPY_PICBED_CONFIG_SUCCEED'))
} }
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) { function getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
this.picBed = picBeds picBed.value = picBeds
} }
beforeRouteEnter (to: any, next: any) { onBeforeRouteUpdate(async (to) => {
next((vm: this) => { if (to.params.type) {
vm.defaultActive = to.name defaultActive.value = `${routerConfig.UPLOADER_CONFIG_PAGE}-${to.params.type}`
}) } else {
defaultActive.value = to.name as string
} }
})
beforeDestroy () { onBeforeUnmount(() => {
ipcRenderer.removeListener(GET_PICBEDS, this.getPicBeds) ipcRenderer.removeListener(GET_PICBEDS, getPicBeds)
} })
</script>
<script lang="ts">
export default {
name: 'MainPage'
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
@ -289,7 +363,7 @@ $darwinBg = transparentify(#172426, #000, 0.7)
opacity 0 opacity 0
&-enter-active, &-enter-active,
&-leave-active &-leave-active
transition all 100ms linear transition all 150ms linear
.view-title .view-title
color #eee color #eee
font-size 20px font-size 20px
@ -304,8 +378,6 @@ $darwinBg = transparentify(#172426, #000, 0.7)
padding-top 10px padding-top 10px
.copy-picbed-config .copy-picbed-config
margin-left 10px margin-left 10px
.el-input__inner
border-radius 14px
.fake-title-bar .fake-title-bar
-webkit-app-region drag -webkit-app-region drag
height h = 22px height h = 22px
@ -333,17 +405,17 @@ $darwinBg = transparentify(#172426, #000, 0.7)
right 4px right 4px
z-index 10000 z-index 10000
-webkit-app-region no-drag -webkit-app-region no-drag
i .el-icon
cursor pointer cursor pointer
font-size 16px font-size 16px
margin-left 5px margin-left 5px
.el-icon-minus .el-icon.minus
&:hover &:hover
color #409EFF color #409EFF
.el-icon-close .el-icon.close
&:hover &:hover
color #F15140 color #F15140
.el-icon-circle-plus-outline .el-icon.plus
&:hover &:hover
color #69C282 color #69C282
.main-wrapper .main-wrapper
@ -355,7 +427,8 @@ $darwinBg = transparentify(#172426, #000, 0.7)
overflow-x hidden overflow-x hidden
overflow-y auto overflow-y auto
width 170px width 170px
.el-icon-info.setting-window .info-window
cursor pointer
position fixed position fixed
bottom 4px bottom 4px
left 4px left 4px
@ -385,14 +458,13 @@ $darwinBg = transparentify(#172426, #000, 0.7)
right 0 right 0
top 18px top 18px
background active-color background active-color
.el-submenu__title .el-sub-menu__title
span
color #eee color #eee
&:hover &:hover
background transparent background transparent
span span
color #fff color #fff
.el-submenu .el-sub-menu
.el-menu-item .el-menu-item
min-width 166px min-width 166px
&.is-active &.is-active

View File

@ -1,65 +1,105 @@
<template> <template>
<div id="gallery-view"> <div id="gallery-view">
<div class="view-title"> <div class="view-title">
{{ $T('GALLERY') }} - {{ filterList.length }} <i class="el-icon-caret-bottom" @click="toggleHandleBar" :class="{'active': handleBarActive}"></i> {{ $T('GALLERY') }} - {{ filterList.length }}
<el-icon
style="margin-left: 4px"
class="cursor-pointer"
@click="toggleHandleBar"
>
<CaretBottom v-show="!handleBarActive" />
<CaretTop v-show="handleBarActive" />
</el-icon>
</div> </div>
<transition name="el-zoom-in-top"> <transition name="el-zoom-in-top">
<el-row v-show="handleBarActive"> <el-row v-show="handleBarActive">
<el-col :span="20" :offset="2"> <el-col
<el-row class="handle-bar" :gutter="16"> :span="20"
:offset="2"
>
<el-row
class="handle-bar"
:gutter="16"
>
<el-col :span="12"> <el-col :span="12">
<el-select <el-select
v-model="choosedPicBed" v-model="choosedPicBed"
multiple multiple
collapse-tags collapse-tags
size="mini" size="small"
style="width: 100%" style="width: 100%"
:placeholder="$T('CHOOSE_SHOWED_PICBED')"> :placeholder="$T('CHOOSE_SHOWED_PICBED')"
>
<el-option <el-option
v-for="item in picBed" v-for="item in picBed"
:key="item.type" :key="item.type"
:label="item.name" :label="item.name"
:value="item.type"> :value="item.type"
</el-option> />
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-select <el-select
v-model="pasteStyle" v-model="pasteStyle"
size="mini" size="small"
style="width: 100%" style="width: 100%"
:placeholder="$T('CHOOSE_PASTE_FORMAT')"
@change="handlePasteStyleChange" @change="handlePasteStyleChange"
:placeholder="$T('CHOOSE_PASTE_FORMAT')"> >
<el-option <el-option
v-for="(value, key) in pasteStyleMap" v-for="(value, key) in pasteStyleMap"
:key="key" :key="key"
:label="key" :label="key"
:value="value"> :value="value"
</el-option> />
</el-select> </el-select>
</el-col> </el-col>
</el-row> </el-row>
<el-row class="handle-bar" :gutter="16"> <el-row
class="handle-bar"
:gutter="16"
>
<el-col :span="12"> <el-col :span="12">
<el-input <el-input
v-model="searchText"
:placeholder="$T('SEARCH')" :placeholder="$T('SEARCH')"
size="mini" size="small"
v-model="searchText"> >
<i slot="suffix" class="el-input__icon el-icon-close" v-if="searchText" @click="cleanSearch" style="cursor: pointer"></i> <template #suffix>
<el-icon
class="el-input__icon"
style="cursor: pointer;"
@click="cleanSearch"
>
<close />
</el-icon>
</template>
</el-input> </el-input>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<div class="item-base copy round" :class="{ active: isMultiple(choosedList)}" @click="multiCopy"> <div
class="item-base copy round"
:class="{ active: isMultiple(choosedList)}"
@click="multiCopy"
>
{{ $T('COPY') }} {{ $T('COPY') }}
</div> </div>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<div class="item-base delete round" :class="{ active: isMultiple(choosedList)}" @click="multiRemove"> <div
class="item-base delete round"
:class="{ active: isMultiple(choosedList)}"
@click="multiRemove"
>
{{ $T('DELETE') }} {{ $T('DELETE') }}
</div> </div>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<div class="item-base all-pick round" :class="{ active: filterList.length > 0}" @click="toggleSelectAll"> <div
class="item-base all-pick round"
:class="{ active: filterList.length > 0}"
@click="toggleSelectAll"
>
{{ isAllSelected ? $T('CANCEL') : $T('SELECT_ALL') }} {{ isAllSelected ? $T('CANCEL') : $T('SELECT_ALL') }}
</div> </div>
</el-col> </el-col>
@ -67,209 +107,266 @@
</el-col> </el-col>
</el-row> </el-row>
</transition> </transition>
<el-row class="gallery-list" :class="{ small: handleBarActive }"> <el-row
<el-col :span="20" :offset="2"> class="gallery-list"
:class="{ small: handleBarActive }"
>
<el-col
:span="20"
:offset="2"
>
<el-row :gutter="16"> <el-row :gutter="16">
<gallerys <photo-slider
:images="filterList" :items="filterList"
:index="idx" :visible="gallerySliderControl.visible"
@close="handleClose" :index="gallerySliderControl.index"
:options="options" :should-transition="true"
></gallerys> @change-index="zoomImage"
<el-col :span="6" v-for="(item, index) in filterList" :key="item.id" class="gallery-list__img"> @click-mask="handleClose"
@close-modal="handleClose"
/>
<el-col
v-for="(item, index) in filterList"
:key="item.id"
:span="6"
class="gallery-list__img"
>
<div <div
class="gallery-list__item" class="gallery-list__item"
@click="zoomImage(index)" @click="zoomImage(index)"
> >
<img v-lazy="item.imgUrl" class="gallery-list__item-img"> <img
v-lazy="item.imgUrl"
class="gallery-list__item-img"
>
</div> </div>
<div class="gallery-list__file-name" :title="item.fileName"> <div
class="gallery-list__file-name"
:title="item.fileName"
>
{{ item.fileName }} {{ item.fileName }}
</div> </div>
<div class="gallery-list__tool-panel"> <el-row
<i class="el-icon-document" @click="copy(item)"></i> class="gallery-list__tool-panel"
<i class="el-icon-edit-outline" @click="openDialog(item)"></i> justify="space-between"
<i class="el-icon-delete" @click="remove(item.id)"></i> align="middle"
<el-checkbox v-model="choosedList[item.id ? item.id : '']" class="pull-right" @change="(val) => handleChooseImage(val, index)"></el-checkbox> >
</div> <el-row>
<el-icon
class="cursor-pointer document"
@click="copy(item)"
>
<Document />
</el-icon>
<el-icon
class="cursor-pointer edit"
@click="openDialog(item)"
>
<Edit />
</el-icon>
<el-icon
class="cursor-pointer delete"
@click="remove(item.id)"
>
<Delete />
</el-icon>
</el-row>
<el-checkbox
v-model="choosedList[item.id ? item.id : '']"
@change="(val) => handleChooseImage(val, index)"
/>
</el-row>
</el-col> </el-col>
</el-row> </el-row>
</el-col> </el-col>
</el-row> </el-row>
<el-dialog <el-dialog
:visible.sync="dialogVisible" v-model="dialogVisible"
:title="$T('CHANGE_IMAGE_URL')" :title="$T('CHANGE_IMAGE_URL')"
width="500px" width="500px"
:modal-append-to-body="false" :modal-append-to-body="false"
> >
<el-input v-model="imgInfo.imgUrl"></el-input> <el-input v-model="imgInfo.imgUrl" />
<span slot="footer" class="dialog-footer"> <template
<el-button @click="dialogVisible = false">{{ $T('CANCEL') }}</el-button> #footer
<el-button type="primary" @click="confirmModify">{{ $T('CONFIRM') }}</el-button> >
</span> <el-button @click="dialogVisible = false">
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
@click="confirmModify"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
// @ts-ignore import type { IResult } from '@picgo/store/dist/types'
import gallerys from 'vue-gallery'
import { Component, Vue, Watch } from 'vue-property-decorator'
import { IResult } from '@picgo/store/dist/types'
import { PASTE_TEXT, GET_PICBEDS } from '#/events/constants' import { PASTE_TEXT, GET_PICBEDS } from '#/events/constants'
import { CheckboxValueType, ElMessageBox } from 'element-plus'
import { Close, CaretBottom, Document, Edit, Delete, CaretTop } from '@element-plus/icons-vue'
import { import {
ipcRenderer, ipcRenderer,
clipboard, clipboard,
IpcRendererEvent IpcRendererEvent
} from 'electron' } from 'electron'
@Component({ import { computed, nextTick, onActivated, onBeforeUnmount, onBeforeMount, reactive, ref, watch } from 'vue'
name: 'gallery', import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
components: { import { onBeforeRouteUpdate } from 'vue-router'
gallerys import { T as $T } from '@/i18n/index'
} import $$db from '@/utils/db'
}) const images = ref<ImgInfo[]>([])
export default class extends Vue { const dialogVisible = ref(false)
images: ImgInfo[] = [] const imgInfo = reactive({
idx: null | number = null
options = {
titleProperty: 'fileName',
urlProperty: 'imgUrl',
closeOnSlideClick: true
}
dialogVisible = false
imgInfo = {
id: '', id: '',
imgUrl: '' imgUrl: ''
} })
const $confirm = ElMessageBox.confirm
choosedList: IObjT<boolean> = {} const choosedList: IObjT<boolean> = reactive({})
choosedPicBed: string[] = [] const gallerySliderControl = reactive({
lastChoosed: number = -1 visible: false,
isShiftKeyPress: boolean = false index: 0
searchText = '' })
handleBarActive = false const choosedPicBed = ref<string[]>([])
pasteStyle = '' const lastChoosed = ref<number>(-1)
pasteStyleMap = { const isShiftKeyPress = ref<boolean>(false)
const searchText = ref<string>('')
const handleBarActive = ref<boolean>(false)
const pasteStyle = ref<string>('')
const pasteStyleMap = {
Markdown: 'markdown', Markdown: 'markdown',
HTML: 'HTML', HTML: 'HTML',
URL: 'URL', URL: 'URL',
UBB: 'UBB', UBB: 'UBB',
Custom: 'Custom' Custom: 'Custom'
} }
const picBed = ref<IPicBedType[]>([])
picBed: IPicBedType[] = [] onBeforeRouteUpdate((to, from) => {
@Watch('$route')
handleRouteUpdate (to: any, from: any) {
if (from.name === 'gallery') { if (from.name === 'gallery') {
this.clearChoosedList() clearChoosedList()
} }
if (to.name === 'gallery') { if (to.name === 'gallery') {
this.updateGallery() updateGallery()
}
} }
})
async created () { onBeforeMount(async () => {
ipcRenderer.on('updateGallery', () => { ipcRenderer.on('updateGallery', () => {
this.$nextTick(async () => { nextTick(async () => {
this.updateGallery() updateGallery()
}) })
}) })
ipcRenderer.send(GET_PICBEDS) sendToMain(GET_PICBEDS)
ipcRenderer.on(GET_PICBEDS, this.getPicBeds) ipcRenderer.on(GET_PICBEDS, getPicBeds)
this.updateGallery() updateGallery()
}
mounted () { document.addEventListener('keydown', handleDetectShiftKey)
document.addEventListener('keydown', this.handleDetectShiftKey) document.addEventListener('keyup', handleDetectShiftKey)
document.addEventListener('keyup', this.handleDetectShiftKey) })
}
handleDetectShiftKey (event: KeyboardEvent) { function handleDetectShiftKey (event: KeyboardEvent) {
if (event.key === 'Shift') { if (event.key === 'Shift') {
if (event.type === 'keydown') { if (event.type === 'keydown') {
this.isShiftKeyPress = true isShiftKeyPress.value = true
} else if (event.type === 'keyup') { } else if (event.type === 'keyup') {
this.isShiftKeyPress = false isShiftKeyPress.value = false
}
} }
} }
}
get filterList () { const filterList = computed(() => {
return this.getGallery() return getGallery()
} })
get isAllSelected () { const isAllSelected = computed(() => {
const values = Object.values(this.choosedList) const values = Object.values(choosedList)
if (values.length === 0) { if (values.length === 0) {
return false return false
} else { } else {
return this.filterList.every(item => { return filterList.value.every(item => {
return this.choosedList[item.id!] return choosedList[item.id!]
}) })
} }
} })
getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) { function getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
this.picBed = picBeds picBed.value = picBeds
} }
getGallery (): ImgInfo[] { function getGallery (): IGalleryItem[] {
if (this.searchText || this.choosedPicBed.length > 0) { if (searchText.value || choosedPicBed.value.length > 0) {
return this.images return images.value
.filter(item => { .filter(item => {
let isInChoosedPicBed = true let isInChoosedPicBed = true
let isIncludesSearchText = true let isIncludesSearchText = true
if (this.choosedPicBed.length > 0) { if (choosedPicBed.value.length > 0) {
isInChoosedPicBed = this.choosedPicBed.some(type => type === item.type) isInChoosedPicBed = choosedPicBed.value.some(type => type === item.type)
} }
if (this.searchText) { if (searchText.value) {
isIncludesSearchText = item.fileName?.includes(this.searchText) || false isIncludesSearchText = item.fileName?.includes(searchText.value) || false
} }
return isIncludesSearchText && isInChoosedPicBed return isIncludesSearchText && isInChoosedPicBed
}).map(item => {
return {
...item,
src: item.imgUrl || '',
key: (item.id || `${Date.now()}`),
intro: item.fileName || ''
}
}) })
} else { } else {
return this.images return images.value.map(item => {
return {
...item,
src: item.imgUrl || '',
key: (item.id || `${Date.now()}`),
intro: item.fileName || ''
} }
}
async updateGallery () {
this.images = (await this.$$db.get({ orderBy: 'desc' })).data
}
@Watch('filterList')
handleFilterListChange () {
this.clearChoosedList()
}
handleChooseImage (val: boolean, index: number) {
if (val === true) {
this.handleBarActive = true
if (this.lastChoosed !== -1 && this.isShiftKeyPress) {
const min = Math.min(this.lastChoosed, index)
const max = Math.max(this.lastChoosed, index)
for (let i = min + 1; i < max; i++) {
const id = this.filterList[i].id!
this.$set(this.choosedList, id, true)
}
}
this.lastChoosed = index
}
}
clearChoosedList () {
this.isShiftKeyPress = false
Object.keys(this.choosedList).forEach(key => {
this.choosedList[key] = false
}) })
this.lastChoosed = -1
} }
}
zoomImage (index: number) { async function updateGallery () {
this.idx = index images.value = (await $$db.get({ orderBy: 'desc' })).data
this.changeZIndexForGallery(true) }
watch(() => filterList, () => {
clearChoosedList()
})
function handleChooseImage (val: CheckboxValueType, index: number) {
if (val === true) {
handleBarActive.value = true
if (lastChoosed.value !== -1 && isShiftKeyPress.value) {
const min = Math.min(lastChoosed.value, index)
const max = Math.max(lastChoosed.value, index)
for (let i = min + 1; i < max; i++) {
const id = filterList.value[i].id!
choosedList[id] = true
} }
}
lastChoosed.value = index
}
}
changeZIndexForGallery (isOpen: boolean) { function clearChoosedList () {
isShiftKeyPress.value = false
Object.keys(choosedList).forEach(key => {
choosedList[key] = false
})
lastChoosed.value = -1
}
function zoomImage (index: number) {
gallerySliderControl.index = index
gallerySliderControl.visible = true
changeZIndexForGallery(true)
}
function changeZIndexForGallery (isOpen: boolean) {
if (isOpen) { if (isOpen) {
// @ts-ignore // @ts-ignore
document.querySelector('.main-content.el-row').style.zIndex = 101 document.querySelector('.main-content.el-row').style.zIndex = 101
@ -277,17 +374,18 @@ export default class extends Vue {
// @ts-ignore // @ts-ignore
document.querySelector('.main-content.el-row').style.zIndex = 10 document.querySelector('.main-content.el-row').style.zIndex = 10
} }
} }
handleClose () { function handleClose () {
this.idx = null gallerySliderControl.index = 0
this.changeZIndexForGallery(false) gallerySliderControl.visible = false
} changeZIndexForGallery(false)
}
async copy (item: ImgInfo) { async function copy (item: ImgInfo) {
const copyLink = await ipcRenderer.invoke(PASTE_TEXT, item) const copyLink = await ipcRenderer.invoke(PASTE_TEXT, item)
const obj = { const obj = {
title: this.$T('COPY_LINK_SUCCEED'), title: $T('COPY_LINK_SUCCEED'),
body: copyLink body: copyLink
// sometimes will cause lagging // sometimes will cause lagging
// icon: item.url || item.imgUrl // icon: item.url || item.imgUrl
@ -296,140 +394,141 @@ export default class extends Vue {
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
} }
} }
remove (id?: string) { function remove (id?: string) {
if (id) { if (id) {
this.$confirm(this.$T('TIPS_REMOVE_LINK'), this.$T('TIPS_NOTICE'), { $confirm($T('TIPS_REMOVE_LINK'), $T('TIPS_NOTICE'), {
confirmButtonText: this.$T('CONFIRM'), confirmButtonText: $T('CONFIRM'),
cancelButtonText: this.$T('CANCEL'), cancelButtonText: $T('CANCEL'),
type: 'warning' type: 'warning'
}).then(async () => { }).then(async () => {
const file = await this.$$db.getById(id) const file = await $$db.getById(id)
await this.$$db.removeById(id) await $$db.removeById(id)
ipcRenderer.send('removeFiles', [file]) sendToMain('removeFiles', [file])
const obj = { const obj = {
title: this.$T('OPERATION_SUCCEED'), title: $T('OPERATION_SUCCEED'),
body: '' body: ''
} }
const myNotification = new Notification(obj.title, obj) const myNotification = new Notification(obj.title, obj)
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
} }
this.updateGallery() updateGallery()
}).catch((e) => { }).catch((e) => {
console.log(e) console.log(e)
return true return true
}) })
} }
} }
openDialog (item: ImgInfo) { function openDialog (item: ImgInfo) {
this.imgInfo.id = item.id! imgInfo.id = item.id!
this.imgInfo.imgUrl = item.imgUrl as string imgInfo.imgUrl = item.imgUrl as string
this.dialogVisible = true dialogVisible.value = true
} }
async confirmModify () { async function confirmModify () {
await this.$$db.updateById(this.imgInfo.id, { await $$db.updateById(imgInfo.id, {
imgUrl: this.imgInfo.imgUrl imgUrl: imgInfo.imgUrl
}) })
const obj = { const obj = {
title: this.$T('CHANGE_IMAGE_URL_SUCCEED'), title: $T('CHANGE_IMAGE_URL_SUCCEED'),
body: this.imgInfo.imgUrl body: imgInfo.imgUrl
// icon: this.imgInfo.imgUrl // icon: this.imgInfo.imgUrl
} }
const myNotification = new Notification(obj.title, obj) const myNotification = new Notification(obj.title, obj)
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
} }
this.dialogVisible = false dialogVisible.value = false
this.updateGallery() updateGallery()
} }
choosePicBed (type: string) { // function choosePicBed (type: string) {
const idx = this.choosedPicBed.indexOf(type) // const idx = choosedPicBed.value.indexOf(type)
if (idx !== -1) { // if (idx !== -1) {
this.choosedPicBed.splice(idx, 1) // choosedPicBed.value.splice(idx, 1)
} else { // } else {
this.choosedPicBed.push(type) // choosedPicBed.value.push(type)
} // }
} // }
cleanSearch () { function cleanSearch () {
this.searchText = '' searchText.value = ''
} }
isMultiple (obj: IObj) { function isMultiple (obj: IObj) {
return Object.values(obj).some(item => item) return Object.values(obj).some(item => item)
} }
toggleSelectAll () { function toggleSelectAll () {
const result = !this.isAllSelected const result = !isAllSelected.value
this.filterList.forEach(item => { filterList.value.forEach(item => {
this.$set(this.choosedList, item.id!, result) choosedList[item.id!] = result
}) })
} }
multiRemove () { function multiRemove () {
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed. // choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
const multiRemoveNumber = Object.values(this.choosedList).filter(item => item).length const multiRemoveNumber = Object.values(choosedList).filter(item => item).length
if (multiRemoveNumber) { if (multiRemoveNumber) {
this.$confirm(this.$T('TIPS_WILL_REMOVE_CHOOSED_IMAGES', { $confirm($T('TIPS_WILL_REMOVE_CHOOSED_IMAGES', {
m: multiRemoveNumber m: multiRemoveNumber
}), this.$T('TIPS_NOTICE'), { }), $T('TIPS_NOTICE'), {
confirmButtonText: this.$T('CONFIRM'), confirmButtonText: $T('CONFIRM'),
cancelButtonText: this.$T('CANCEL'), cancelButtonText: $T('CANCEL'),
type: 'warning' type: 'warning'
}).then(async () => { }).then(async () => {
const files: IResult<ImgInfo>[] = [] const files: IResult<ImgInfo>[] = []
const imageIDList = Object.keys(this.choosedList) const imageIDList = Object.keys(choosedList)
for (let i = 0; i < imageIDList.length; i++) { for (let i = 0; i < imageIDList.length; i++) {
const key = imageIDList[i] const key = imageIDList[i]
if (this.choosedList[key]) { if (choosedList[key]) {
const file = await this.$$db.getById<ImgInfo>(key) const file = await $$db.getById<ImgInfo>(key)
if (file) { if (file) {
files.push(file) files.push(file)
await this.$$db.removeById(key) await $$db.removeById(key)
} }
} }
} }
this.clearChoosedList() clearChoosedList()
this.choosedList = {} // // TODO: check this
// choosedList = {} //
const obj = { const obj = {
title: this.$T('OPERATION_SUCCEED'), title: $T('OPERATION_SUCCEED'),
body: '' body: ''
} }
ipcRenderer.send('removeFiles', files) sendToMain('removeFiles', files)
const myNotification = new Notification(obj.title, obj) const myNotification = new Notification(obj.title, obj)
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
} }
this.updateGallery() updateGallery()
}).catch(() => { }).catch(() => {
return true return true
}) })
} }
} }
async multiCopy () { async function multiCopy () {
if (Object.values(this.choosedList).some(item => item)) { if (Object.values(choosedList).some(item => item)) {
const copyString: string[] = [] const copyString: string[] = []
// choosedList -> { [id]: true or false }; true means choosed. false means not choosed. // choosedList -> { [id]: true or false }; true means choosed. false means not choosed.
const imageIDList = Object.keys(this.choosedList) const imageIDList = Object.keys(choosedList)
for (let i = 0; i < imageIDList.length; i++) { for (let i = 0; i < imageIDList.length; i++) {
const key = imageIDList[i] const key = imageIDList[i]
if (this.choosedList[key]) { if (choosedList[key]) {
const item = await this.$$db.getById<ImgInfo>(key) const item = await $$db.getById<ImgInfo>(key)
if (item) { if (item) {
const txt = await ipcRenderer.invoke(PASTE_TEXT, item) const txt = await ipcRenderer.invoke(PASTE_TEXT, item)
copyString.push(txt) copyString.push(txt)
this.choosedList[key] = false choosedList[key] = false
} }
} }
} }
const obj = { const obj = {
title: this.$T('BATCH_COPY_LINK_SUCCEED'), title: $T('BATCH_COPY_LINK_SUCCEED'),
body: copyString.join('\n') body: copyString.join('\n')
} }
const myNotification = new Notification(obj.title, obj) const myNotification = new Notification(obj.title, obj)
@ -438,27 +537,40 @@ export default class extends Vue {
return true return true
} }
} }
} }
toggleHandleBar () { function toggleHandleBar () {
this.handleBarActive = !this.handleBarActive handleBarActive.value = !handleBarActive.value
} }
// getPasteStyle () { async function handlePasteStyleChange (val: string) {
// this.pasteStyle = this.$db.get('settings.pasteStyle') || 'markdown' saveConfig('settings.pasteStyle', val)
// } pasteStyle.value = val
async handlePasteStyleChange (val: string) { }
this.saveConfig('settings.pasteStyle', val)
this.pasteStyle = val
}
beforeDestroy () { onBeforeUnmount(() => {
console.log('unmounted')
ipcRenderer.removeAllListeners('updateGallery') ipcRenderer.removeAllListeners('updateGallery')
ipcRenderer.removeListener(GET_PICBEDS, this.getPicBeds) ipcRenderer.removeListener(GET_PICBEDS, getPicBeds)
} })
onActivated(async () => {
pasteStyle.value = (await getConfig('settings.pasteStyle')) || 'markdown'
})
</script>
<script lang="ts">
export default {
name: 'GalleryPage'
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
.PhotoSlider
&__BannerIcon
&:nth-child(1)
display none
&__Counter
margin-top 20px
.view-title .view-title
color #eee color #eee
font-size 20px font-size 20px
@ -473,6 +585,8 @@ export default class extends Vue {
transform: rotate(180deg) transform: rotate(180deg)
#gallery-view #gallery-view
height 100% height 100%
.cursor-pointer
cursor pointer
.item-base .item-base
background #2E2E2E background #2E2E2E
text-align center text-align center
@ -523,9 +637,9 @@ export default class extends Vue {
height: 287px height: 287px
top: 113px top: 113px
&__img &__img
height 150px // height 150px
position relative position relative
margin-bottom 16px margin-bottom 8px
&__item &__item
width 100% width 100%
height 120px height 120px
@ -534,6 +648,7 @@ export default class extends Vue {
margin-bottom 4px margin-bottom 4px
overflow hidden overflow hidden
display flex display flex
margin-bottom 6px
&-fake &-fake
position absolute position absolute
top 0 top 0
@ -549,16 +664,20 @@ export default class extends Vue {
&__tool-panel &__tool-panel
color #ddd color #ddd
margin-bottom 4px margin-bottom 4px
display flex
.el-checkbox
height 16px
i i
cursor pointer cursor pointer
transition all .2s ease-in-out transition all .2s ease-in-out
&.el-icon-document margin-right 4px
&.document
&:hover &:hover
color #49B1F5 color #49B1F5
&.el-icon-edit-outline &.edit
&:hover &:hover
color #69C282 color #69C282
&.el-icon-delete &.delete
&:hover &:hover
color #F15140 color #F15140
&__file-name &__file-name
@ -571,6 +690,4 @@ export default class extends Vue {
.handle-bar .handle-bar
color #ddd color #ddd
margin-bottom 10px margin-bottom 10px
.el-input__inner
border-radius 14px
</style> </style>

View File

@ -1,126 +1,132 @@
<template> <template>
<div id="mini-page" <div
id="mini-page"
:style="{ backgroundImage: 'url(' + logo + ')' }" :style="{ backgroundImage: 'url(' + logo + ')' }"
:class="{ linux: os === 'linux' }" :class="{ linux: os === 'linux' }"
> >
<!-- <i class="el-icon-upload2"></i> --> <!-- <i class="el-icon-upload2"></i> -->
<div <div
id="upload-area" id="upload-area"
:class="{ 'is-dragover': dragover, uploading: showProgress, linux: os === 'linux' }" @drop.prevent="onDrop" @dragover.prevent="dragover = true" @dragleave.prevent="dragover = false" :class="{ 'is-dragover': dragover, uploading: showProgress, linux: os === 'linux' }"
:style="{ backgroundPosition: '0 ' + progress + '%'}" :style="{ backgroundPosition: '0 ' + progress + '%'}"
@drop.prevent="onDrop"
@dragover.prevent="dragover = true"
@dragleave.prevent="dragover = false"
>
<div
id="upload-dragger"
@dblclick="openUploadWindow"
>
<input
id="file-uploader"
type="file"
multiple
@change="onChange"
> >
<div id="upload-dragger" @dblclick="openUploadWindow">
<input type="file" id="file-uploader" @change="onChange" multiple>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import mixin from '@/utils/mixin' // import mixin from '@/utils/mixin'
import { Component, Vue, Watch } from 'vue-property-decorator' // import { Component, Vue, Watch } from 'vue-property-decorator'
import { T as $T } from '@/i18n/index'
import { ElMessage as $message } from 'element-plus'
import { import {
ipcRenderer, ipcRenderer,
IpcRendererEvent IpcRendererEvent
} from 'electron' } from 'electron'
import { onBeforeUnmount, onBeforeMount, ref, watch } from 'vue'
import { SHOW_MINI_PAGE_MENU, SET_MINI_WINDOW_POS } from '~/universal/events/constants' import { SHOW_MINI_PAGE_MENU, SET_MINI_WINDOW_POS } from '~/universal/events/constants'
import { import {
isUrl isUrl
} from '~/universal/utils/common' } from '~/universal/utils/common'
@Component({ import { sendToMain } from '@/utils/dataSender'
name: 'mini-page', const logo = require('../assets/squareLogo.png')
mixins: [mixin] const dragover = ref(false)
}) const progress = ref(0)
export default class extends Vue { const showProgress = ref(false)
logo = require('../assets/squareLogo.png') const showError = ref(false)
dragover = false const dragging = ref(false)
progress = 0 const wX = ref(-1)
showProgress = false const wY = ref(-1)
showError = false const screenX = ref(-1)
dragging = false const screenY = ref(-1)
wX: number = -1 const os = ref('')
wY: number = -1
screenX: number = -1 onBeforeMount(() => {
screenY: number = -1 os.value = process.platform
menu: Electron.Menu | null = null ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, _progress: number) => {
os = '' if (_progress !== -1) {
picBed: IPicBedType[] = [] showProgress.value = true
created () { progress.value = _progress
this.os = process.platform
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => {
if (progress !== -1) {
this.showProgress = true
this.progress = progress
} else { } else {
this.progress = 100 progress.value = 100
this.showError = true showError.value = true
} }
}) })
} window.addEventListener('mousedown', handleMouseDown, false)
window.addEventListener('mousemove', handleMouseMove, false)
window.addEventListener('mouseup', handleMouseUp, false)
})
mounted () { watch(progress, (val) => {
window.addEventListener('mousedown', this.handleMouseDown, false)
window.addEventListener('mousemove', this.handleMouseMove, false)
window.addEventListener('mouseup', this.handleMouseUp, false)
}
@Watch('progress')
onProgressChange (val: number) {
if (val === 100) { if (val === 100) {
setTimeout(() => { setTimeout(() => {
this.showProgress = false showProgress.value = false
this.showError = false showError.value = false
}, 1000) }, 1000)
setTimeout(() => { setTimeout(() => {
this.progress = 0 progress.value = 0
}, 1200) }, 1200)
} }
} })
onDrop (e: DragEvent) { function onDrop (e: DragEvent) {
this.dragover = false dragover.value = false
const items = e.dataTransfer!.items const items = e.dataTransfer!.items
if (items.length === 2 && items[0].type === 'text/uri-list') { if (items.length === 2 && items[0].type === 'text/uri-list') {
this.handleURLDrag(items, e.dataTransfer!) handleURLDrag(items, e.dataTransfer!)
} else if (items[0].type === 'text/plain') { } else if (items[0].type === 'text/plain') {
const str = e.dataTransfer!.getData(items[0].type) const str = e.dataTransfer!.getData(items[0].type)
if (isUrl(str)) { if (isUrl(str)) {
ipcRenderer.send('uploadChoosedFiles', [{ path: str }]) sendToMain('uploadChoosedFiles', [{ path: str }])
} else { } else {
this.$message.error(this.$T('TIPS_DRAG_VALID_PICTURE_OR_URL')) $message.error($T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
} }
} else { } else {
this.ipcSendFiles(e.dataTransfer!.files) ipcSendFiles(e.dataTransfer!.files)
}
} }
}
handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) { function handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) {
// text/html // text/html
// Use this data to get a more precise URL // Use this data to get a more precise URL
const urlString = dataTransfer.getData(items[1].type) const urlString = dataTransfer.getData(items[1].type)
const urlMatch = urlString.match(/<img.*src="(.*?)"/) const urlMatch = urlString.match(/<img.*src="(.*?)"/)
if (urlMatch) { if (urlMatch) {
ipcRenderer.send('uploadChoosedFiles', [ sendToMain('uploadChoosedFiles', [
{ {
path: urlMatch[1] path: urlMatch[1]
} }
]) ])
} else { } else {
this.$message.error(this.$T('TIPS_DRAG_VALID_PICTURE_OR_URL')) $message.error($T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
}
} }
}
openUploadWindow () { function openUploadWindow () {
// @ts-ignore // @ts-ignore
document.getElementById('file-uploader').click() document.getElementById('file-uploader').click()
} }
onChange (e: any) { function onChange (e: any) {
this.ipcSendFiles(e.target.files) ipcSendFiles(e.target.files)
// @ts-ignore // @ts-ignore
document.getElementById('file-uploader').value = '' document.getElementById('file-uploader').value = ''
} }
ipcSendFiles (files: FileList) { function ipcSendFiles (files: FileList) {
const sendFiles: IFileWithPath[] = [] const sendFiles: IFileWithPath[] = []
Array.from(files).forEach((item) => { Array.from(files).forEach((item) => {
const obj = { const obj = {
@ -129,24 +135,24 @@ export default class extends Vue {
} }
sendFiles.push(obj) sendFiles.push(obj)
}) })
ipcRenderer.send('uploadChoosedFiles', sendFiles) sendToMain('uploadChoosedFiles', sendFiles)
} }
handleMouseDown (e: MouseEvent) { function handleMouseDown (e: MouseEvent) {
this.dragging = true dragging.value = true
this.wX = e.pageX wX.value = e.pageX
this.wY = e.pageY wY.value = e.pageY
this.screenX = e.screenX screenX.value = e.screenX
this.screenY = e.screenY screenY.value = e.screenY
} }
handleMouseMove (e: MouseEvent) { function handleMouseMove (e: MouseEvent) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
if (this.dragging) { if (dragging.value) {
const xLoc = e.screenX - this.wX const xLoc = e.screenX - wX.value
const yLoc = e.screenY - this.wY const yLoc = e.screenY - wY.value
ipcRenderer.send(SET_MINI_WINDOW_POS, { sendToMain(SET_MINI_WINDOW_POS, {
x: xLoc, x: xLoc,
y: yLoc, y: yLoc,
width: 64, width: 64,
@ -159,29 +165,34 @@ export default class extends Vue {
// height: 64 // height: 64
// }) // })
} }
} }
handleMouseUp (e: MouseEvent) { function handleMouseUp (e: MouseEvent) {
this.dragging = false dragging.value = false
if (this.screenX === e.screenX && this.screenY === e.screenY) { if (screenX.value === e.screenX && screenY.value === e.screenY) {
if (e.button === 0) { // left mouse if (e.button === 0) { // left mouse
this.openUploadWindow() openUploadWindow()
} else { } else {
this.openContextMenu() openContextMenu()
}
} }
} }
}
openContextMenu () { function openContextMenu () {
ipcRenderer.send(SHOW_MINI_PAGE_MENU) sendToMain(SHOW_MINI_PAGE_MENU)
} }
beforeDestroy () { onBeforeUnmount(() => {
ipcRenderer.removeAllListeners('uploadProgress') ipcRenderer.removeAllListeners('uploadProgress')
window.removeEventListener('mousedown', this.handleMouseDown, false) window.removeEventListener('mousedown', handleMouseDown, false)
window.removeEventListener('mousemove', this.handleMouseMove, false) window.removeEventListener('mousemove', handleMouseMove, false)
window.removeEventListener('mouseup', this.handleMouseUp, false) window.removeEventListener('mouseup', handleMouseUp, false)
} })
</script>
<script lang="ts">
export default {
name: 'MiniPage'
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>

File diff suppressed because it is too large Load Diff

View File

@ -2,72 +2,142 @@
<div id="plugin-view"> <div id="plugin-view">
<div class="view-title"> <div class="view-title">
{{ $T('PLUGIN_SETTINGS') }} - {{ $T('PLUGIN_SETTINGS') }} -
<el-tooltip :content="pluginListToolTip" placement="right"> <el-tooltip
<i class="el-icon-goods" @click="goAwesomeList"></i> :content="pluginListToolTip"
placement="right"
>
<el-icon
class="el-icon-goods"
@click="goAwesomeList"
>
<Goods />
</el-icon>
</el-tooltip> </el-tooltip>
<el-tooltip :content="importLocalPluginToolTip" placement="left"> <el-tooltip
<i class="el-icon-download" @click="handleImportLocalPlugin"/> :content="importLocalPluginToolTip"
placement="left"
>
<el-icon
class="el-icon-download"
@click="handleImportLocalPlugin"
>
<Download />
</el-icon>
</el-tooltip> </el-tooltip>
</div> </div>
<el-row class="handle-bar" :class="{ 'cut-width': pluginList.length > 6 }"> <el-row
class="handle-bar"
:class="{ 'cut-width': pluginList.length > 6 }"
>
<el-input <el-input
v-model="searchText" v-model="searchText"
:placeholder="$T('PLUGIN_SEARCH_PLACEHOLDER')" :placeholder="$T('PLUGIN_SEARCH_PLACEHOLDER')"
size="small" size="small"
> >
<i slot="suffix" class="el-input__icon el-icon-close" v-if="searchText" @click="cleanSearch" style="cursor: pointer"></i> <template #suffix>
<el-icon
class="el-input__icon"
style="cursor: pointer;"
@click="cleanSearch"
>
<close />
</el-icon>
</template>
</el-input> </el-input>
</el-row> </el-row>
<el-row :gutter="10" class="plugin-list" v-loading="loading"> <el-row
<el-col :span="12" v-for="item in pluginList" :key="item.fullName"> v-loading="loading"
<div class="plugin-item" :class="{ 'darwin': os === 'darwin' }"> :gutter="10"
<div class="cli-only-badge" v-if="!item.gui" title="CLI only">CLI</div> class="plugin-list"
<img class="plugin-item__logo" :src="item.logo" >
<el-col
v-for="item in pluginList"
:key="item.fullName"
class="plugin-item__container"
:span="12"
>
<div
class="plugin-item"
:class="{ 'darwin': os === 'darwin' }"
>
<div
v-if="!item.gui"
class="cli-only-badge"
title="CLI only"
>
CLI
</div>
<img
class="plugin-item__logo"
:src="item.logo"
:onerror="defaultLogo" :onerror="defaultLogo"
> >
<div <div
class="plugin-item__content" class="plugin-item__content"
:class="{ disabled: !item.enabled }" :class="{ disabled: !item.enabled }"
> >
<div class="plugin-item__name" @click="openHomepage(item.homepage)"> <div
class="plugin-item__name"
@click="openHomepage(item.homepage)"
>
{{ item.name }} <small>{{ ' ' + item.version }}</small> {{ item.name }} <small>{{ ' ' + item.version }}</small>
</div> </div>
<div class="plugin-item__desc" :title="item.description"> <div
class="plugin-item__desc"
:title="item.description"
>
{{ item.description }} {{ item.description }}
</div> </div>
<div class="plugin-item__info-bar"> <div class="plugin-item__info-bar">
<span class="plugin-item__author"> <span class="plugin-item__author">
{{ item.author }} {{ item.author }}
</span> </span>
<span class="plugin-item__config" > <span class="plugin-item__config">
<template v-if="searchText"> <template v-if="searchText">
<template v-if="!item.hasInstall"> <template v-if="!item.hasInstall">
<span class="config-button install" v-if="!item.ing" @click="installPlugin(item)"> <span
v-if="!item.ing"
class="config-button install"
@click="installPlugin(item)"
>
{{ $T('PLUGIN_INSTALL') }} {{ $T('PLUGIN_INSTALL') }}
</span> </span>
<span v-else-if="item.ing" class="config-button ing"> <span
v-else-if="item.ing"
class="config-button ing"
>
{{ $T('PLUGIN_INSTALLING') }} {{ $T('PLUGIN_INSTALLING') }}
</span> </span>
</template> </template>
<span v-else class="config-button ing"> <span
v-else
class="config-button ing"
>
{{ $T('PLUGIN_INSTALLED') }} {{ $T('PLUGIN_INSTALLED') }}
</span> </span>
</template> </template>
<template v-else> <template v-else>
<span v-if="item.ing" class="config-button ing"> <span
v-if="item.ing"
class="config-button ing"
>
{{ $T('PLUGIN_DOING_SOMETHING') }} {{ $T('PLUGIN_DOING_SOMETHING') }}
</span> </span>
<template v-else> <template v-else>
<i <el-icon
v-if="item.enabled" v-if="item.enabled"
class="el-icon-setting" class="el-icon-setting"
@click="buildContextMenu(item)" @click="buildContextMenu(item)"
></i> >
<i <Setting />
</el-icon>
<el-icon
v-else v-else
class="el-icon-remove-outline" class="el-icon-remove-outline"
@click="buildContextMenu(item)" @click="buildContextMenu(item)"
></i> >
<Remove />
</el-icon>
</template> </template>
</template> </template>
</span> </span>
@ -76,11 +146,23 @@
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-show="needReload" class="reload-mask" :class="{ 'cut-width': pluginList.length > 6 }"> <el-row
<el-button type="primary" @click="reloadApp" size="mini" round>{{ $T('TIPS_NEED_RELOAD') }}</el-button> v-show="needReload"
class="reload-mask"
:class="{ 'cut-width': pluginList.length > 6 }"
justify="center"
>
<el-button
type="primary"
size="small"
round
@click="reloadApp"
>
{{ $T('TIPS_NEED_RELOAD') }}
</el-button>
</el-row> </el-row>
<el-dialog <el-dialog
:visible.sync="dialogVisible" v-model="dialogVisible"
:modal-append-to-body="false" :modal-append-to-body="false"
:title="$T('CONFIG_THING', { :title="$T('CONFIG_THING', {
c: configName c: configName
@ -88,27 +170,35 @@
width="70%" width="70%"
> >
<config-form <config-form
:id="configName"
ref="$configForm"
:config="config" :config="config"
:type="currentType" :type="currentType"
:id="configName" color-mode="white"
ref="configForm" />
<template #footer>
<el-button
round
@click="dialogVisible = false"
> >
</config-form> {{ $T('CANCEL') }}
<span slot="footer"> </el-button>
<el-button @click="dialogVisible = false" round>{{ $T('CANCEL') }}</el-button> <el-button
<el-button type="primary" @click="handleConfirmConfig" round>{{ $T('CONFIRM') }}</el-button> type="primary"
</span> round
@click="handleConfirmConfig"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { import { Close, Download, Goods, Remove, Setting } from '@element-plus/icons-vue'
Component, import { T as $T } from '@/i18n/index'
Vue,
Watch
} from 'vue-property-decorator'
import ConfigForm from '@/components/ConfigForm.vue' import ConfigForm from '@/components/ConfigForm.vue'
import { debounce } from 'lodash' import { debounce, DebouncedFunc } from 'lodash'
import { import {
ipcRenderer, ipcRenderer,
IpcRendererEvent IpcRendererEvent
@ -121,52 +211,49 @@ import {
PICGO_HANDLE_PLUGIN_ING, PICGO_HANDLE_PLUGIN_ING,
PICGO_TOGGLE_PLUGIN, PICGO_TOGGLE_PLUGIN,
SHOW_PLUGIN_PAGE_MENU, SHOW_PLUGIN_PAGE_MENU,
GET_PICBEDS GET_PICBEDS,
PICGO_HANDLE_PLUGIN_DONE
} from '#/events/constants' } from '#/events/constants'
import { computed, ref, onBeforeMount, onBeforeUnmount, watch } from 'vue'
import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
import { ElMessageBox } from 'element-plus'
import axios from 'axios'
const $confirm = ElMessageBox.confirm
const searchText = ref('')
const pluginList = ref<IPicGoPlugin[]>([])
const config = ref<any[]>([])
const currentType = ref<'plugin' | 'uploader' | 'transformer'>('plugin')
const configName = ref('')
const dialogVisible = ref(false)
const pluginNameList = ref<string[]>([])
const loading = ref(true)
const needReload = ref(false)
const pluginListToolTip = $T('PLUGIN_LIST')
const importLocalPluginToolTip = $T('PLUGIN_IMPORT_LOCAL')
// const id = ref('')
const os = ref('')
const defaultLogo = ref(`this.src="file://${__static.replace(/\\/g, '/')}/roundLogo.png"`)
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
const npmSearchText = computed(() => {
return searchText.value.match('picgo-plugin-')
? searchText.value
: searchText.value !== ''
? `picgo-plugin-${searchText.value}`
: searchText.value
})
let getSearchResult: DebouncedFunc<(val: string) => void>
@Component({ watch(npmSearchText, (val: string) => {
name: 'plugin', if (val) {
components: { loading.value = true
ConfigForm pluginList.value = []
getSearchResult(val)
} else {
getPluginList()
} }
}) })
export default class extends Vue {
searchText = ''
pluginList: IPicGoPlugin[] = []
menu: Electron.Menu | null = null
config: any[] = []
currentType = ''
configName = ''
dialogVisible = false
pluginNameList: string[] = []
loading = true
needReload = false
pluginListToolTip = this.$T('PLUGIN_LIST')
importLocalPluginToolTip = this.$T('PLUGIN_IMPORT_LOCAL')
id = ''
os = ''
defaultLogo: string = `this.src="file://${__static.replace(/\\/g, '/')}/roundLogo.png"`
get npmSearchText () {
return this.searchText.match('picgo-plugin-')
? this.searchText
: this.searchText !== ''
? `picgo-plugin-${this.searchText}`
: this.searchText
}
@Watch('npmSearchText') watch(dialogVisible, (val: boolean) => {
onNpmSearchTextChange (val: string) {
if (val) {
this.loading = true
this.pluginList = []
this.getSearchResult(val)
} else {
this.getPluginList()
}
}
@Watch('dialogVisible')
onDialogVisible (val: boolean) {
if (val) { if (val) {
// @ts-ignore // @ts-ignore
document.querySelector('.main-content.el-row').style.zIndex = 101 document.querySelector('.main-content.el-row').style.zIndex = 101
@ -174,24 +261,32 @@ export default class extends Vue {
// @ts-ignore // @ts-ignore
document.querySelector('.main-content.el-row').style.zIndex = 10 document.querySelector('.main-content.el-row').style.zIndex = 10
} }
} })
async created () { onBeforeMount(async () => {
this.os = process.platform os.value = process.platform
ipcRenderer.on('hideLoading', () => { ipcRenderer.on('hideLoading', () => {
this.loading = false loading.value = false
})
ipcRenderer.on(PICGO_HANDLE_PLUGIN_DONE, (evt: IpcRendererEvent, fullName: string) => {
pluginList.value.forEach(item => {
if (item.fullName === fullName || (item.name === fullName)) {
item.ing = false
}
})
loading.value = false
}) })
ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => { ipcRenderer.on('pluginList', (evt: IpcRendererEvent, list: IPicGoPlugin[]) => {
this.pluginList = list pluginList.value = list
this.pluginNameList = list.map(item => item.fullName) pluginNameList.value = list.map(item => item.fullName)
this.loading = false loading.value = false
}) })
ipcRenderer.on('installPlugin', (evt: IpcRendererEvent, { success, body }: { ipcRenderer.on('installPlugin', (evt: IpcRendererEvent, { success, body }: {
success: boolean, success: boolean,
body: string body: string
}) => { }) => {
this.loading = false loading.value = false
this.pluginList.forEach(item => { pluginList.value.forEach(item => {
if (item.fullName === body) { if (item.fullName === body) {
item.ing = false item.ing = false
item.hasInstall = success item.hasInstall = success
@ -199,183 +294,182 @@ export default class extends Vue {
}) })
}) })
ipcRenderer.on('updateSuccess', (evt: IpcRendererEvent, plugin: string) => { ipcRenderer.on('updateSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false loading.value = false
this.pluginList.forEach(item => { pluginList.value.forEach(item => {
if (item.fullName === plugin) { if (item.fullName === plugin) {
item.ing = false item.ing = false
item.hasInstall = true item.hasInstall = true
} }
this.getPicBeds() getPicBeds()
}) })
this.handleReload() handleReload()
this.getPluginList() getPluginList()
}) })
ipcRenderer.on('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => { ipcRenderer.on('uninstallSuccess', (evt: IpcRendererEvent, plugin: string) => {
this.loading = false loading.value = false
this.pluginList = this.pluginList.filter(item => { pluginList.value = pluginList.value.filter(item => {
if (item.fullName === plugin) { // restore Uploader & Transformer after uninstalling if (item.fullName === plugin) { // restore Uploader & Transformer after uninstalling
if (item.config.transformer.name) { if (item.config.transformer.name) {
this.handleRestoreState('transformer', item.config.transformer.name) handleRestoreState('transformer', item.config.transformer.name)
} }
if (item.config.uploader.name) { if (item.config.uploader.name) {
this.handleRestoreState('uploader', item.config.uploader.name) handleRestoreState('uploader', item.config.uploader.name)
} }
this.getPicBeds() getPicBeds()
} }
return item.fullName !== plugin return item.fullName !== plugin
}) })
this.pluginNameList = this.pluginNameList.filter(item => item !== plugin) pluginNameList.value = pluginNameList.value.filter(item => item !== plugin)
}) })
ipcRenderer.on(PICGO_CONFIG_PLUGIN, (evt: IpcRendererEvent, currentType: string, configName: string, config: any) => { ipcRenderer.on(PICGO_CONFIG_PLUGIN, (evt: IpcRendererEvent, _currentType: 'plugin' | 'transformer' | 'uploader', _configName: string, _config: any) => {
this.currentType = currentType currentType.value = _currentType
this.configName = configName configName.value = _configName
this.dialogVisible = true dialogVisible.value = true
this.config = config config.value = _config
}) })
ipcRenderer.on(PICGO_HANDLE_PLUGIN_ING, (evt: IpcRendererEvent, fullName: string) => { ipcRenderer.on(PICGO_HANDLE_PLUGIN_ING, (evt: IpcRendererEvent, fullName: string) => {
this.pluginList.forEach(item => { pluginList.value.forEach(item => {
if (item.fullName === fullName || (item.name === fullName)) { if (item.fullName === fullName || (item.name === fullName)) {
item.ing = true item.ing = true
} }
}) })
this.loading = true loading.value = true
}) })
ipcRenderer.on(PICGO_TOGGLE_PLUGIN, (evt: IpcRendererEvent, fullName: string, enabled: boolean) => { ipcRenderer.on(PICGO_TOGGLE_PLUGIN, (evt: IpcRendererEvent, fullName: string, enabled: boolean) => {
const plugin = this.pluginList.find(item => item.fullName === fullName) const plugin = pluginList.value.find(item => item.fullName === fullName)
if (plugin) { if (plugin) {
plugin.enabled = enabled plugin.enabled = enabled
this.getPicBeds() getPicBeds()
this.needReload = true needReload.value = true
} }
}) })
this.getPluginList() getPluginList()
this.getSearchResult = debounce(this.getSearchResult, 50) getSearchResult = debounce(_getSearchResult, 50)
this.needReload = await this.getConfig<boolean>('needReload') || false needReload.value = await getConfig<boolean>('needReload') || false
} })
async buildContextMenu (plugin: IPicGoPlugin) { async function buildContextMenu (plugin: IPicGoPlugin) {
ipcRenderer.send(SHOW_PLUGIN_PAGE_MENU, plugin) sendToMain(SHOW_PLUGIN_PAGE_MENU, plugin)
} }
getPluginList () { function getPluginList () {
ipcRenderer.send('getPluginList') sendToMain('getPluginList')
} }
getPicBeds () { function getPicBeds () {
ipcRenderer.send(GET_PICBEDS) sendToMain(GET_PICBEDS)
} }
installPlugin (item: IPicGoPlugin) { function installPlugin (item: IPicGoPlugin) {
if (!item.gui) { if (!item.gui) {
this.$confirm(this.$T('TIPS_PLUGIN_NOT_GUI_IMPLEMENT'), this.$T('TIPS_NOTICE'), { $confirm($T('TIPS_PLUGIN_NOT_GUI_IMPLEMENT'), $T('TIPS_NOTICE'), {
confirmButtonText: this.$T('CONFIRM'), confirmButtonText: $T('CONFIRM'),
cancelButtonText: this.$T('CANCEL'), cancelButtonText: $T('CANCEL'),
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
item.ing = true item.ing = true
ipcRenderer.send('installPlugin', item.fullName) sendToMain('installPlugin', item.fullName)
}).catch(() => { }).catch(() => {
console.log('Install canceled') console.log('Install canceled')
}) })
} else { } else {
item.ing = true item.ing = true
ipcRenderer.send('installPlugin', item.fullName) sendToMain('installPlugin', item.fullName)
}
} }
}
uninstallPlugin (val: string) { // function uninstallPlugin (val: string) {
this.pluginList.forEach(item => { // pluginList.value.forEach(item => {
if (item.name === val) { // if (item.name === val) {
item.ing = true // item.ing = true
} // }
}) // })
this.loading = true // loading.value = true
ipcRenderer.send('uninstallPlugin', val) // sendToMain('uninstallPlugin', val)
} // }
updatePlugin (val: string) { // function updatePlugin (val: string) {
this.pluginList.forEach(item => { // pluginList.value.forEach(item => {
if (item.fullName === val) { // if (item.fullName === val) {
item.ing = true // item.ing = true
} // }
}) // })
this.loading = true // loading.value = true
ipcRenderer.send('updatePlugin', val) // sendToMain('updatePlugin', val)
} // }
reloadApp () { function reloadApp () {
ipcRenderer.send(RELOAD_APP) sendToMain(RELOAD_APP)
} }
async handleReload () { async function handleReload () {
this.saveConfig({ saveConfig({
needReload: true needReload: true
}) })
this.needReload = true needReload.value = true
const successNotification = new Notification(this.$T('PLUGIN_UPDATE_SUCCEED'), { const successNotification = new Notification($T('PLUGIN_UPDATE_SUCCEED'), {
body: this.$T('TIPS_NEED_RELOAD') body: $T('TIPS_NEED_RELOAD')
}) })
successNotification.onclick = () => { successNotification.onclick = () => {
this.reloadApp() reloadApp()
}
} }
}
cleanSearch () { function cleanSearch () {
this.searchText = '' searchText.value = ''
} }
async handleConfirmConfig () { async function handleConfirmConfig () {
// @ts-ignore const result = (await $configForm.value?.validate() || false)
const result = await this.$refs.configForm.validate()
if (result !== false) { if (result !== false) {
switch (this.currentType) { switch (currentType.value) {
case 'plugin': case 'plugin':
this.saveConfig({ saveConfig({
[`${this.configName}`]: result [`${configName.value}`]: result
}) })
break break
case 'uploader': case 'uploader':
this.saveConfig({ saveConfig({
[`picBed.${this.configName}`]: result [`picBed.${configName.value}`]: result
}) })
break break
case 'transformer': case 'transformer':
this.saveConfig({ saveConfig({
[`transformer.${this.configName}`]: result [`transformer.${configName.value}`]: result
}) })
break break
} }
const successNotification = new Notification(this.$T('SETTINGS_RESULT'), { const successNotification = new Notification($T('SETTINGS_RESULT'), {
body: this.$T('TIPS_SET_SUCCEED') body: $T('TIPS_SET_SUCCEED')
}) })
successNotification.onclick = () => { successNotification.onclick = () => {
return true return true
} }
this.dialogVisible = false dialogVisible.value = false
this.getPluginList() getPluginList()
}
} }
}
getSearchResult (val: string) { function _getSearchResult (val: string) {
// this.$http.get(`https://api.npms.io/v2/search?q=${val}`) // this.$http.get(`https://api.npms.io/v2/search?q=${val}`)
this.$http.get(`https://registry.npmjs.com/-/v1/search?text=${val}`) axios.get(`https://registry.npmjs.com/-/v1/search?text=${val}`)
.then((res: INPMSearchResult) => { .then((res: INPMSearchResult) => {
this.pluginList = res.data.objects pluginList.value = res.data.objects
.filter((item:INPMSearchResultObject) => { .filter((item:INPMSearchResultObject) => {
return item.package.name.includes('picgo-plugin-') return item.package.name.includes('picgo-plugin-')
}) })
.map((item: INPMSearchResultObject) => { .map((item: INPMSearchResultObject) => {
return this.handleSearchResult(item) return handleSearchResult(item)
}) })
this.loading = false loading.value = false
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err)
this.loading = false loading.value = false
}) })
} }
handleSearchResult (item: INPMSearchResultObject) { function handleSearchResult (item: INPMSearchResultObject) {
const name = handleStreamlinePluginName(item.package.name) const name = handleStreamlinePluginName(item.package.name)
let gui = false let gui = false
if (item.package.keywords && item.package.keywords.length > 0) { if (item.package.keywords && item.package.keywords.length > 0) {
@ -384,63 +478,69 @@ export default class extends Vue {
} }
} }
return { return {
name: name, name,
fullName: item.package.name, fullName: item.package.name,
author: item.package.author.name, author: item.package.author.name,
description: item.package.description, description: item.package.description,
logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`, logo: `https://cdn.jsdelivr.net/npm/${item.package.name}/logo.png`,
config: {}, config: {},
homepage: item.package.links ? item.package.links.homepage : '', homepage: item.package.links ? item.package.links.homepage : '',
hasInstall: this.pluginNameList.some(plugin => plugin === item.package.name), hasInstall: pluginNameList.value.some(plugin => plugin === item.package.name),
version: item.package.version, version: item.package.version,
gui, gui,
ing: false // installing or uninstalling ing: false // installing or uninstalling
} }
} }
// restore Uploader & Transformer // restore Uploader & Transformer
async handleRestoreState (item: string, name: string) { async function handleRestoreState (item: string, name: string) {
if (item === 'uploader') { if (item === 'uploader') {
const current = await this.getConfig('picBed.current') const current = await getConfig('picBed.current')
if (current === name) { if (current === name) {
this.saveConfig({ saveConfig({
'picBed.current': 'smms', 'picBed.current': 'smms',
'picBed.uploader': 'smms' 'picBed.uploader': 'smms'
}) })
} }
} }
if (item === 'transformer') { if (item === 'transformer') {
const current = await this.getConfig('picBed.transformer') const current = await getConfig('picBed.transformer')
if (current === name) { if (current === name) {
this.saveConfig({ saveConfig({
'picBed.transformer': 'path' 'picBed.transformer': 'path'
}) })
} }
} }
} }
openHomepage (url: string) { function openHomepage (url: string) {
if (url) { if (url) {
ipcRenderer.send(OPEN_URL, url) sendToMain(OPEN_URL, url)
}
} }
}
goAwesomeList () { function goAwesomeList () {
ipcRenderer.send(OPEN_URL, 'https://github.com/PicGo/Awesome-PicGo') sendToMain(OPEN_URL, 'https://github.com/PicGo/Awesome-PicGo')
} }
handleImportLocalPlugin () { function handleImportLocalPlugin () {
ipcRenderer.send('importLocalPlugin') sendToMain('importLocalPlugin')
this.loading = true loading.value = true
} }
beforeDestroy () { onBeforeUnmount(() => {
ipcRenderer.removeAllListeners('pluginList') ipcRenderer.removeAllListeners('pluginList')
ipcRenderer.removeAllListeners('installPlugin') ipcRenderer.removeAllListeners('installPlugin')
ipcRenderer.removeAllListeners('uninstallSuccess') ipcRenderer.removeAllListeners('uninstallSuccess')
ipcRenderer.removeAllListeners('updateSuccess') ipcRenderer.removeAllListeners('updateSuccess')
ipcRenderer.removeAllListeners('hideLoading') ipcRenderer.removeAllListeners('hideLoading')
} ipcRenderer.removeAllListeners(PICGO_HANDLE_PLUGIN_DONE)
})
</script>
<script lang="ts">
export default {
name: 'PluginPage'
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
@ -451,6 +551,7 @@ $darwinBg = #172426
.el-loading-mask .el-loading-mask
background-color rgba(0, 0, 0, 0.8) background-color rgba(0, 0, 0, 0.8)
.plugin-list .plugin-list
align-content flex-start
height: 339px; height: 339px;
box-sizing: border-box; box-sizing: border-box;
padding: 8px 15px; padding: 8px 15px;
@ -471,6 +572,7 @@ $darwinBg = #172426
margin 10px auto margin 10px auto
position relative position relative
i.el-icon-goods i.el-icon-goods
margin-left 4px
font-size 20px font-size 20px
vertical-align middle vertical-align middle
cursor pointer cursor pointer
@ -500,8 +602,10 @@ $darwinBg = #172426
padding 8px padding 8px
user-select text user-select text
transition all .2s ease-in-out transition all .2s ease-in-out
margin-bottom 10px
position relative position relative
&__container
height 80px
margin-bottom 10px
.cli-only-badge .cli-only-badge
position absolute position absolute
right 0px right 0px

View File

@ -1,7 +1,7 @@
<template> <template>
<div id="rename-page"> <div id="rename-page">
<el-form <el-form
@submit.native.prevent @submit.prevent
> >
<el-form-item <el-form-item
:label="$T('FILE_RENAME')" :label="$T('FILE_RENAME')"
@ -9,54 +9,67 @@
<el-input <el-input
v-model="fileName" v-model="fileName"
size="small" size="small"
@keyup.enter.native="confirmName" @keyup.enter="confirmName"
></el-input> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-row> <el-row>
<div class="pull-right"> <div class="pull-right">
<el-button @click="cancel" round size="mini">{{ $T('CANCEL') }}</el-button> <el-button
<el-button type="primary" @click="confirmName" round size="mini">{{ $T('CONFIRM') }}</el-button> round
size="small"
@click="cancel"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
size="small"
@click="confirmName"
>
{{ $T('CONFIRM') }}
</el-button>
</div> </div>
</el-row> </el-row>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { RENAME_FILE_NAME } from '#/events/constants' import { RENAME_FILE_NAME } from '#/events/constants'
import { Component, Vue } from 'vue-property-decorator' import { sendToMain } from '@/utils/dataSender'
import mixin from '@/utils/mixin'
import { import {
ipcRenderer, ipcRenderer,
IpcRendererEvent IpcRendererEvent
} from 'electron' } from 'electron'
@Component({ import { onBeforeUnmount, onBeforeMount, ref } from 'vue'
name: 'rename-page', const fileName = ref('')
mixins: [mixin] const originName = ref('')
}) const id = ref<string | null>(null)
export default class extends Vue { onBeforeMount(() => {
fileName: string = '' ipcRenderer.on(RENAME_FILE_NAME, (event: IpcRendererEvent, newName: string, _originName: string, _id: string) => {
originName: string = '' fileName.value = newName
id: string | null = null originName.value = _originName
created () { id.value = _id
ipcRenderer.on(RENAME_FILE_NAME, (event: IpcRendererEvent, newName: string, originName: string, id: string) => {
this.fileName = newName
this.originName = originName
this.id = id
}) })
} })
confirmName () { function confirmName () {
ipcRenderer.send(`${RENAME_FILE_NAME}${this.id}`, this.fileName) sendToMain(`${RENAME_FILE_NAME}${id.value}`, fileName.value)
} }
cancel () { function cancel () {
// if cancel, use origin file name // if cancel, use origin file name
ipcRenderer.send(`${RENAME_FILE_NAME}${this.id}`, this.originName) sendToMain(`${RENAME_FILE_NAME}${id.value}`, originName.value)
} }
beforeDestroy () { onBeforeUnmount(() => {
ipcRenderer.removeAllListeners('rename') ipcRenderer.removeAllListeners('rename')
} })
</script>
<script lang="ts">
export default {
name: 'RenamePage'
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>

View File

@ -4,18 +4,21 @@
{{ $T('SETTINGS_SET_SHORTCUT') }} {{ $T('SETTINGS_SET_SHORTCUT') }}
</div> </div>
<el-row> <el-row>
<el-col :span="20" :offset="2"> <el-col
:span="20"
:offset="2"
>
<el-table <el-table
class="shortcut-page-table-border" class="shortcut-page-table-border"
:data="list" :data="list"
size="mini" size="small"
header-cell-class-name="shortcut-page-table-border" header-cell-class-name="shortcut-page-table-border"
cell-class-name="shortcut-page-table-border" cell-class-name="shortcut-page-table-border"
> >
<el-table-column <el-table-column
:label="$T('SHORTCUT_NAME')" :label="$T('SHORTCUT_NAME')"
> >
<template slot-scope="scope"> <template #default="scope">
{{ scope.row.label ? scope.row.label : scope.row.name }} {{ scope.row.label ? scope.row.label : scope.row.name }}
</template> </template>
</el-table-column> </el-table-column>
@ -23,14 +26,13 @@
width="160px" width="160px"
:label="$T('SHORTCUT_BIND')" :label="$T('SHORTCUT_BIND')"
prop="key" prop="key"
> />
</el-table-column>
<el-table-column <el-table-column
:label="$T('SHORTCUT_STATUS')" :label="$T('SHORTCUT_STATUS')"
> >
<template slot-scope="scope"> <template #default="scope">
<el-tag <el-tag
size="mini" size="small"
:type="scope.row.enable ? 'success' : 'danger'" :type="scope.row.enable ? 'success' : 'danger'"
> >
{{ scope.row.enable ? $T('SHORTCUT_ENABLED') : $T('SHORTCUT_DISABLED') }} {{ scope.row.enable ? $T('SHORTCUT_ENABLED') : $T('SHORTCUT_DISABLED') }}
@ -41,38 +43,43 @@
:label="$T('SHORTCUT_SOURCE')" :label="$T('SHORTCUT_SOURCE')"
width="100px" width="100px"
> >
<template slot-scope="scope"> <template #default="scope">
{{ calcOriginShowName(scope.row.from) }} {{ calcOriginShowName(scope.row.from) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
:label="$T('SHORTCUT_HANDLE')" :label="$T('SHORTCUT_HANDLE')"
width="100px"
> >
<template slot-scope="scope"> <template #default="scope">
<el-row>
<el-button <el-button
@click="toggleEnable(scope.row)" size="small"
size="mini"
:class="{ :class="{
disabled: scope.row.enable disabled: scope.row.enable
}" }"
type="text"> type="text"
@click="toggleEnable(scope.row)"
>
{{ scope.row.enable ? $T('SHORTCUT_DISABLE') : $T('SHORTCUT_ENABLE') }} {{ scope.row.enable ? $T('SHORTCUT_DISABLE') : $T('SHORTCUT_ENABLE') }}
</el-button> </el-button>
<el-button <el-button
class="edit" class="edit"
size="mini" size="small"
type="text"
@click="openKeyBindingDialog(scope.row, scope.$index)" @click="openKeyBindingDialog(scope.row, scope.$index)"
type="text"> >
{{ $T('SHORTCUT_EDIT') }} {{ $T('SHORTCUT_EDIT') }}
</el-button> </el-button>
</el-row>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-col> </el-col>
</el-row> </el-row>
<el-dialog <el-dialog
v-model="keyBindingVisible"
:title="$T('SHORTCUT_CHANGE_UPLOAD')" :title="$T('SHORTCUT_CHANGE_UPLOAD')"
:visible.sync="keyBindingVisible"
:modal-append-to-body="false" :modal-append-to-body="false"
> >
<el-form <el-form
@ -81,101 +88,109 @@
> >
<el-form-item> <el-form-item>
<el-input <el-input
class="align-center"
@keydown.native.prevent="keyDetect($event)"
v-model="shortKey" v-model="shortKey"
class="align-center"
:autofocus="true" :autofocus="true"
></el-input> @keydown.prevent="keyDetect($event as KeyboardEvent)"
/>
</el-form-item> </el-form-item>
</el-form> </el-form>
<span slot="footer"> <template #footer>
<el-button @click="cancelKeyBinding" round> <el-button
round
@click="cancelKeyBinding"
>
{{ $T('CANCEL') }} {{ $T('CANCEL') }}
</el-button> </el-button>
<el-button type="primary" @click="confirmKeyBinding" round> <el-button
type="primary"
round
@click="confirmKeyBinding"
>
{{ $T('CONFIRM') }} {{ $T('CONFIRM') }}
</el-button> </el-button>
</span> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { Component, Vue, Watch } from 'vue-property-decorator' import keyBinding from '@/utils/key-binding'
import keyDetect from '@/utils/key-binding'
import { ipcRenderer, IpcRendererEvent } from 'electron' import { ipcRenderer, IpcRendererEvent } from 'electron'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants' import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { onBeforeUnmount, onBeforeMount, ref, watch } from 'vue'
import { getConfig, sendToMain } from '@/utils/dataSender'
@Component({ const list = ref<IShortKeyConfig[]>([])
name: 'shortkey-page' const keyBindingVisible = ref(false)
}) const command = ref('')
export default class extends Vue { const shortKey = ref('')
list: IShortKeyConfig[] = [] const currentIndex = ref(0)
keyBindingVisible = false
command = '' onBeforeMount(async () => {
shortKey = '' const shortKeyConfig = (await getConfig<IShortKeyConfigs>('settings.shortKey'))!
currentIndex = 0 list.value = Object.keys(shortKeyConfig).map(item => {
async created () {
const shortKeyConfig = (await this.getConfig<IShortKeyConfigs>('settings.shortKey'))!
this.list = Object.keys(shortKeyConfig).map(item => {
return { return {
...shortKeyConfig[item], ...shortKeyConfig[item],
from: this.calcOrigin(item) from: calcOrigin(item)
} }
}) })
} })
@Watch('keyBindingVisible') watch(keyBindingVisible, (val: boolean) => {
onKeyBindingVisibleChange (val: boolean) { sendToMain(TOGGLE_SHORTKEY_MODIFIED_MODE, val)
ipcRenderer.send(TOGGLE_SHORTKEY_MODIFIED_MODE, val) })
}
calcOrigin (item: string) { function calcOrigin (item: string) {
const [origin] = item.split(':') const [origin] = item.split(':')
return origin return origin
} }
calcOriginShowName (item: string) { function calcOriginShowName (item: string) {
return item.replace('picgo-plugin-', '') return item.replace('picgo-plugin-', '')
} }
toggleEnable (item: IShortKeyConfig) { function toggleEnable (item: IShortKeyConfig) {
const status = !item.enable const status = !item.enable
item.enable = status item.enable = status
ipcRenderer.send('bindOrUnbindShortKey', item, item.from) sendToMain('bindOrUnbindShortKey', item, item.from)
} }
keyDetect (event: KeyboardEvent) { function keyDetect (event: KeyboardEvent) {
this.shortKey = keyDetect(event).join('+') shortKey.value = keyBinding(event).join('+')
} }
async openKeyBindingDialog (config: IShortKeyConfig, index: number) { async function openKeyBindingDialog (config: IShortKeyConfig, index: number) {
this.command = `${config.from}:${config.name}` command.value = `${config.from}:${config.name}`
this.shortKey = await this.getConfig(`settings.shortKey.${this.command}.key`) || '' shortKey.value = await getConfig(`settings.shortKey.${command.value}.key`) || ''
this.currentIndex = index currentIndex.value = index
this.keyBindingVisible = true keyBindingVisible.value = true
} }
async cancelKeyBinding () { async function cancelKeyBinding () {
this.keyBindingVisible = false keyBindingVisible.value = false
this.shortKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`) || '' shortKey.value = await getConfig<string>(`settings.shortKey.${command.value}.key`) || ''
} }
async confirmKeyBinding () { async function confirmKeyBinding () {
const oldKey = await this.getConfig<string>(`settings.shortKey.${this.command}.key`) const oldKey = await getConfig<string>(`settings.shortKey.${command.value}.key`)
const config = Object.assign({}, this.list[this.currentIndex]) const config = Object.assign({}, list.value[currentIndex.value])
config.key = this.shortKey config.key = shortKey.value
ipcRenderer.send('updateShortKey', config, oldKey, config.from) sendToMain('updateShortKey', config, oldKey, config.from)
ipcRenderer.once('updateShortKeyResponse', (evt: IpcRendererEvent, result) => { ipcRenderer.once('updateShortKeyResponse', (evt: IpcRendererEvent, result) => {
if (result) { if (result) {
this.keyBindingVisible = false keyBindingVisible.value = false
this.list[this.currentIndex].key = this.shortKey list.value[currentIndex.value].key = shortKey.value
} }
}) })
} }
beforeDestroy () { onBeforeUnmount(() => {
ipcRenderer.send(TOGGLE_SHORTKEY_MODIFIED_MODE, false) sendToMain(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
} })
</script>
<script lang="ts">
export default {
name: 'ShortkeyPage'
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
@ -191,6 +206,9 @@ export default class extends Vue {
color: #F56C6C color: #F56C6C
&.edit &.edit
color: #67C23A color: #67C23A
&--text
padding-left 4px
padding-right 4px
.el-table .el-table
background-color: transparent background-color: transparent
color #ddd color #ddd
@ -209,4 +227,6 @@ export default class extends Vue {
tr:hover tr:hover
&>td &>td
background #333 background #333
.el-button+.el-button
margin-left 4px
</style> </style>

View File

@ -1,24 +1,59 @@
<template> <template>
<div id="tray-page"> <div id="tray-page">
<div class="open-main-window" @click="openSettingWindow">{{ $T('OPEN_MAIN_WINDOW') }}</div> <div
class="open-main-window"
@click="openSettingWindow"
>
{{ $T('OPEN_MAIN_WINDOW') }}
</div>
<div class="content"> <div class="content">
<div class="wait-upload-img" v-if="clipboardFiles.length > 0"> <div
<div class="list-title">{{ $T('WAIT_TO_UPLOAD') }}</div> v-if="clipboardFiles.length > 0"
<div v-for="(item, index) in clipboardFiles" :key="index" class="img-list"> class="wait-upload-img"
>
<div class="list-title">
{{ $T('WAIT_TO_UPLOAD') }}
</div>
<div
v-for="(item, index) in clipboardFiles"
:key="index"
class="img-list"
>
<div <div
class="upload-img__container" class="upload-img__container"
:class="{ upload: uploadFlag }" :class="{ upload: uploadFlag }"
@click="uploadClipboardFiles"> @click="uploadClipboardFiles"
<img :src="item.imgUrl" class="upload-img"> >
<img
:src="item.imgUrl"
class="upload-img"
>
</div> </div>
</div> </div>
</div> </div>
<div class="uploaded-img"> <div class="uploaded-img">
<div class="list-title">{{ $T('ALREADY_UPLOAD') }}</div> <div class="list-title">
<div v-for="item in files" :key="item.imgUrl" class="img-list"> {{ $T('ALREADY_UPLOAD') }}
<div class="upload-img__container" @click="copyTheLink(item)"> </div>
<img v-lazy="item.imgUrl" class="upload-img"> <div
<div class="upload-img__title" :title="item.fileName">{{ item.fileName }}</div> v-for="item in files"
:key="item.imgUrl"
class="img-list"
>
<div
class="upload-img__container"
@click="copyTheLink(item)"
>
<img
v-lazy="item.imgUrl"
class="upload-img"
>
<div
class="upload-img__title"
:title="item.fileName"
>
{{ item.fileName }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -26,53 +61,49 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { Component, Vue } from 'vue-property-decorator' import { reactive, ref, onBeforeUnmount, onBeforeMount } from 'vue'
import mixin from '@/utils/mixin'
import { ipcRenderer } from 'electron' import { ipcRenderer } from 'electron'
import $$db from '@/utils/db'
import { T as $T } from '@/i18n/index'
import { IResult } from '@picgo/store/dist/types' import { IResult } from '@picgo/store/dist/types'
import { PASTE_TEXT, OPEN_WINDOW } from '#/events/constants' import { PASTE_TEXT, OPEN_WINDOW } from '#/events/constants'
import { IWindowList } from '#/types/enum' import { IWindowList } from '#/types/enum'
import { sendToMain } from '@/utils/dataSender'
@Component({ const files = ref<IResult<ImgInfo>[]>([])
name: 'tray-page', const notification = reactive({
mixins: [mixin] title: $T('COPY_LINK_SUCCEED'),
})
export default class extends Vue {
files: IResult<ImgInfo>[] = []
notification = {
title: this.$T('COPY_LINK_SUCCEED'),
body: '' body: ''
} })
clipboardFiles: ImgInfo[] = [] const clipboardFiles = ref<ImgInfo[]>([])
uploadFlag = false const uploadFlag = ref(false)
get reverseList () {
return this.files.slice().reverse()
}
openSettingWindow () { // const reverseList = computed(() => files.value.slice().reverse())
this.sendToMain(OPEN_WINDOW, IWindowList.SETTING_WINDOW)
}
async getData () { function openSettingWindow () {
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data sendToMain(OPEN_WINDOW, IWindowList.SETTING_WINDOW)
} }
async copyTheLink (item: ImgInfo) { async function getData () {
this.notification.body = item.imgUrl! files.value = (await $$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
const myNotification = new Notification(this.notification.title, this.notification) }
async function copyTheLink (item: ImgInfo) {
notification.body = item.imgUrl!
const myNotification = new Notification(notification.title, notification)
ipcRenderer.invoke(PASTE_TEXT, item) ipcRenderer.invoke(PASTE_TEXT, item)
myNotification.onclick = () => { myNotification.onclick = () => {
return true return true
} }
} }
calcHeight (width: number, height: number): number { // function calcHeight (width: number, height: number): number {
return height * 160 / width // return height * 160 / width
} // }
disableDragFile () { function disableDragFile () {
window.addEventListener('dragover', (e) => { window.addEventListener('dragover', (e) => {
e = e || event e = e || event
e.preventDefault() e.preventDefault()
@ -81,44 +112,49 @@ export default class extends Vue {
e = e || event e = e || event
e.preventDefault() e.preventDefault()
}, false) }, false)
} }
uploadClipboardFiles () { function uploadClipboardFiles () {
if (this.uploadFlag) { if (uploadFlag.value) {
return return
} }
this.uploadFlag = true uploadFlag.value = true
ipcRenderer.send('uploadClipboardFiles') sendToMain('uploadClipboardFiles')
} }
mounted () { onBeforeMount(() => {
this.disableDragFile() disableDragFile()
this.getData() getData()
ipcRenderer.on('dragFiles', async (event: Event, files: string[]) => { ipcRenderer.on('dragFiles', async (event: Event, _files: string[]) => {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < _files.length; i++) {
const item = files[i] const item = _files[i]
await this.$$db.insert(item) await $$db.insert(item)
} }
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data files.value = (await $$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
}) })
ipcRenderer.on('clipboardFiles', (event: Event, files: ImgInfo[]) => { ipcRenderer.on('clipboardFiles', (event: Event, files: ImgInfo[]) => {
this.clipboardFiles = files clipboardFiles.value = files
}) })
ipcRenderer.on('uploadFiles', async () => { ipcRenderer.on('uploadFiles', async () => {
this.files = (await this.$$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data files.value = (await $$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 })).data
this.uploadFlag = false uploadFlag.value = false
}) })
ipcRenderer.on('updateFiles', () => { ipcRenderer.on('updateFiles', () => {
this.getData() getData()
}) })
} })
beforeDestroy () { onBeforeUnmount(() => {
ipcRenderer.removeAllListeners('dragFiles') ipcRenderer.removeAllListeners('dragFiles')
ipcRenderer.removeAllListeners('clipboardFiles') ipcRenderer.removeAllListeners('clipboardFiles')
ipcRenderer.removeAllListeners('uploadClipboardFiles') ipcRenderer.removeAllListeners('uploadClipboardFiles')
ipcRenderer.removeAllListeners('updateFiles') ipcRenderer.removeAllListeners('updateFiles')
} })
</script>
<script lang="ts">
export default {
name: 'TrayPage'
} }
</script> </script>
@ -179,11 +215,20 @@ body::-webkit-scrollbar
background #49B1F5 background #49B1F5
.upload-img__index .upload-img__index
color #fff color #fff
.upload-img__container
display flex
flex-direction column
justify-content center
align-items center
.upload-img .upload-img
width 100% max-width 100%
object-fit scale-down object-fit scale-down
margin 0 auto margin 0 auto
&__container &__container
display flex
flex-direction column
justify-content center
align-items center
width 100% width 100%
padding 8px 8px 4px padding 8px 8px 4px
height 100% height 100%

View File

@ -1,9 +1,18 @@
<template> <template>
<div id="upload-view"> <div id="upload-view">
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="20" :offset="2"> <el-col
:span="20"
:offset="2"
>
<div class="view-title"> <div class="view-title">
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }} <i class="el-icon-caret-bottom" @click="handleChangePicBed"></i> {{ $T('PICTURE_UPLOAD') }} - {{ picBedName }}
<el-icon
style="cursor: pointer; margin-left: 4px;"
@click="handleChangePicBed"
>
<CaretBottom />
</el-icon>
</div> </div>
<div <div
id="upload-area" id="upload-area"
@ -12,12 +21,22 @@
@dragover.prevent="dragover = true" @dragover.prevent="dragover = true"
@dragleave.prevent="dragover = false" @dragleave.prevent="dragover = false"
> >
<div id="upload-dragger" @click="openUplodWindow"> <div
<i class="el-icon-upload"></i> id="upload-dragger"
@click="openUplodWindow"
>
<el-icon>
<UploadFilled />
</el-icon>
<div class="upload-dragger__text"> <div class="upload-dragger__text">
{{ $T('DRAG_FILE_TO_HERE') }} <span>{{ $T('CLICK_TO_UPLOAD') }}</span> {{ $T('DRAG_FILE_TO_HERE') }} <span>{{ $T('CLICK_TO_UPLOAD') }}</span>
</div> </div>
<input type="file" id="file-uploader" @change="onChange" multiple> <input
id="file-uploader"
type="file"
multiple
@change="onChange"
>
</div> </div>
</div> </div>
<el-progress <el-progress
@ -26,42 +45,69 @@
class="upload-progress" class="upload-progress"
:class="{ 'show': showProgress }" :class="{ 'show': showProgress }"
:status="showError ? 'exception' : undefined" :status="showError ? 'exception' : undefined"
></el-progress> />
<div class="paste-style"> <div class="paste-style">
<div class="el-col-16"> <div class="el-col-16">
<div class="paste-style__text"> <div class="paste-style__text">
{{ $T('LINK_FORMAT') }} {{ $T('LINK_FORMAT') }}
</div> </div>
<el-radio-group v-model="pasteStyle" size="mini" <el-radio-group
v-model="pasteStyle"
size="small"
@change="handlePasteStyleChange" @change="handlePasteStyleChange"
> >
<el-radio-button label="markdown"> <el-radio-button label="markdown">
Markdown Markdown
</el-radio-button> </el-radio-button>
<el-radio-button label="HTML"></el-radio-button> <el-radio-button label="HTML" />
<el-radio-button label="URL"></el-radio-button> <el-radio-button label="URL" />
<el-radio-button label="UBB"></el-radio-button> <el-radio-button label="UBB" />
<el-radio-button label="Custom" :title="$T('CUSTOM')"></el-radio-button> <el-radio-button
label="Custom"
:title="$T('CUSTOM')"
/>
</el-radio-group> </el-radio-group>
</div> </div>
<div class="el-col-8"> <div class="el-col-8">
<div class="paste-style__text"> <div class="paste-style__text">
{{ $T('QUICK_UPLOAD') }} {{ $T('QUICK_UPLOAD') }}
</div> </div>
<el-button type="primary" round size="mini" @click="uploadClipboardFiles" class="quick-upload" style="width: 50%">{{ $T('CLIPBOARD_PICTURE') }}</el-button> <el-button
<el-button type="primary" round size="mini" @click="uploadURLFiles" class="quick-upload" style="width: 46%; margin-left: 6px">URL</el-button> type="primary"
round
size="small"
class="quick-upload"
style="width: 50%"
@click="uploadClipboardFiles"
>
{{ $T('CLIPBOARD_PICTURE') }}
</el-button>
<el-button
type="primary"
round
size="small"
class="quick-upload"
style="width: 46%; margin-left: 6px"
@click="uploadURLFiles"
>
URL
</el-button>
</div> </div>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { Component, Vue, Watch } from 'vue-property-decorator' // import { Component, Vue, Watch } from 'vue-property-decorator'
import { UploadFilled, CaretBottom } from '@element-plus/icons-vue'
import { import {
ipcRenderer, ipcRenderer,
IpcRendererEvent IpcRendererEvent
} from 'electron' } from 'electron'
import { ref, onBeforeMount, onBeforeUnmount, watch } from 'vue'
import { T as $T } from '@/i18n'
import $bus from '@/utils/bus'
import { import {
SHOW_INPUT_BOX, SHOW_INPUT_BOX,
SHOW_INPUT_BOX_RESPONSE, SHOW_INPUT_BOX_RESPONSE,
@ -71,100 +117,99 @@ import {
import { import {
isUrl isUrl
} from '~/universal/utils/common' } from '~/universal/utils/common'
@Component({ import { ElMessage as $message } from 'element-plus'
name: 'upload' import { getConfig, saveConfig, sendToMain } from '@/utils/dataSender'
}) const dragover = ref(false)
export default class extends Vue { const progress = ref(0)
dragover = false const showProgress = ref(false)
progress = 0 const showError = ref(false)
showProgress = false const pasteStyle = ref('')
showError = false const picBed = ref<IPicBedType[]>([])
pasteStyle = '' const picBedName = ref('')
picBed: IPicBedType[] = [] onBeforeMount(() => {
picBedName = '' ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, _progress: number) => {
mounted () { if (_progress !== -1) {
ipcRenderer.on('uploadProgress', (event: IpcRendererEvent, progress: number) => { showProgress.value = true
if (progress !== -1) { progress.value = _progress
this.showProgress = true
this.progress = progress
} else { } else {
this.progress = 100 progress.value = 100
this.showError = true showError.value = true
} }
}) })
this.getPasteStyle() getPasteStyle()
this.getDefaultPicBed() getDefaultPicBed()
ipcRenderer.on('syncPicBed', () => { ipcRenderer.on('syncPicBed', () => {
this.getDefaultPicBed() getDefaultPicBed()
}) })
ipcRenderer.send(GET_PICBEDS) sendToMain(GET_PICBEDS)
ipcRenderer.on(GET_PICBEDS, this.getPicBeds) ipcRenderer.on(GET_PICBEDS, getPicBeds)
this.$bus.$on(SHOW_INPUT_BOX_RESPONSE, this.handleInputBoxValue) $bus.on(SHOW_INPUT_BOX_RESPONSE, handleInputBoxValue)
} })
@Watch('progress') watch(progress, onProgressChange)
onProgressChange (val: number) {
function onProgressChange (val: number) {
if (val === 100) { if (val === 100) {
setTimeout(() => { setTimeout(() => {
this.showProgress = false showProgress.value = false
this.showError = false showError.value = false
}, 1000) }, 1000)
setTimeout(() => { setTimeout(() => {
this.progress = 0 progress.value = 0
}, 1200) }, 1200)
} }
} }
beforeDestroy () { onBeforeUnmount(() => {
this.$bus.$off(SHOW_INPUT_BOX_RESPONSE) $bus.off(SHOW_INPUT_BOX_RESPONSE)
ipcRenderer.removeAllListeners('uploadProgress') ipcRenderer.removeAllListeners('uploadProgress')
ipcRenderer.removeAllListeners('syncPicBed') ipcRenderer.removeAllListeners('syncPicBed')
ipcRenderer.removeListener(GET_PICBEDS, this.getPicBeds) ipcRenderer.removeListener(GET_PICBEDS, getPicBeds)
} })
onDrop (e: DragEvent) { function onDrop (e: DragEvent) {
this.dragover = false dragover.value = false
const items = e.dataTransfer!.items const items = e.dataTransfer!.items
if (items.length === 2 && items[0].type === 'text/uri-list') { if (items.length === 2 && items[0].type === 'text/uri-list') {
this.handleURLDrag(items, e.dataTransfer!) handleURLDrag(items, e.dataTransfer!)
} else if (items[0].type === 'text/plain') { } else if (items[0].type === 'text/plain') {
const str = e.dataTransfer!.getData(items[0].type) const str = e.dataTransfer!.getData(items[0].type)
if (isUrl(str)) { if (isUrl(str)) {
ipcRenderer.send('uploadChoosedFiles', [{ path: str }]) sendToMain('uploadChoosedFiles', [{ path: str }])
} else { } else {
this.$message.error(this.$T('TIPS_DRAG_VALID_PICTURE_OR_URL')) $message.error($T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
} }
} else { } else {
this.ipcSendFiles(e.dataTransfer!.files) ipcSendFiles(e.dataTransfer!.files)
}
} }
}
handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) { function handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer) {
// text/html // text/html
// Use this data to get a more precise URL // Use this data to get a more precise URL
const urlString = dataTransfer.getData(items[1].type) const urlString = dataTransfer.getData(items[1].type)
const urlMatch = urlString.match(/<img.*src="(.*?)"/) const urlMatch = urlString.match(/<img.*src="(.*?)"/)
if (urlMatch) { if (urlMatch) {
ipcRenderer.send('uploadChoosedFiles', [ sendToMain('uploadChoosedFiles', [
{ {
path: urlMatch[1] path: urlMatch[1]
} }
]) ])
} else { } else {
this.$message.error(this.$T('TIPS_DRAG_VALID_PICTURE_OR_URL')) $message.error($T('TIPS_DRAG_VALID_PICTURE_OR_URL'))
}
} }
}
openUplodWindow () { function openUplodWindow () {
document.getElementById('file-uploader')!.click() document.getElementById('file-uploader')!.click()
} }
onChange (e: any) { function onChange (e: any) {
this.ipcSendFiles(e.target.files); ipcSendFiles(e.target.files);
(document.getElementById('file-uploader') as HTMLInputElement).value = '' (document.getElementById('file-uploader') as HTMLInputElement).value = ''
} }
ipcSendFiles (files: FileList) { function ipcSendFiles (files: FileList) {
const sendFiles: IFileWithPath[] = [] const sendFiles: IFileWithPath[] = []
Array.from(files).forEach((item) => { Array.from(files).forEach((item) => {
const obj = { const obj = {
@ -173,68 +218,75 @@ export default class extends Vue {
} }
sendFiles.push(obj) sendFiles.push(obj)
}) })
ipcRenderer.send('uploadChoosedFiles', sendFiles) sendToMain('uploadChoosedFiles', sendFiles)
} }
async getPasteStyle () { async function getPasteStyle () {
this.pasteStyle = await this.getConfig('settings.pasteStyle') || 'markdown' pasteStyle.value = await getConfig('settings.pasteStyle') || 'markdown'
} }
handlePasteStyleChange (val: string) { function handlePasteStyleChange (val: string | number | boolean) {
this.saveConfig({ saveConfig({
'settings.pasteStyle': val 'settings.pasteStyle': val
}) })
} }
uploadClipboardFiles () { function uploadClipboardFiles () {
ipcRenderer.send('uploadClipboardFilesFromUploadPage') sendToMain('uploadClipboardFilesFromUploadPage')
} }
async uploadURLFiles () { async function uploadURLFiles () {
const str = await navigator.clipboard.readText() const str = await navigator.clipboard.readText()
this.$bus.$emit(SHOW_INPUT_BOX, { $bus.emit(SHOW_INPUT_BOX, {
value: isUrl(str) ? str : '', value: isUrl(str) ? str : '',
title: this.$T('TIPS_INPUT_URL'), title: $T('TIPS_INPUT_URL'),
placeholder: this.$T('TIPS_HTTP_PREFIX') placeholder: $T('TIPS_HTTP_PREFIX')
}) })
} }
handleInputBoxValue (val: string) { function handleInputBoxValue (val: string) {
if (val === '') return false if (val === '') return
if (isUrl(val)) { if (isUrl(val)) {
ipcRenderer.send('uploadChoosedFiles', [{ sendToMain('uploadChoosedFiles', [{
path: val path: val
}]) }])
} else { } else {
this.$message.error(this.$T('TIPS_INPUT_VALID_URL')) $message.error($T('TIPS_INPUT_VALID_URL'))
}
} }
}
async getDefaultPicBed () { async function getDefaultPicBed () {
const currentPicBed = await this.getConfig<string>('picBed.current') const currentPicBed = await getConfig<string>('picBed.current')
this.picBed.forEach(item => { picBed.value.forEach(item => {
if (item.type === currentPicBed) { if (item.type === currentPicBed) {
this.picBedName = item.name picBedName.value = item.name
} }
}) })
} }
getPicBeds (event: Event, picBeds: IPicBedType[]) { function getPicBeds (event: Event, picBeds: IPicBedType[]) {
this.picBed = picBeds picBed.value = picBeds
this.getDefaultPicBed() getDefaultPicBed()
} }
async handleChangePicBed () { async function handleChangePicBed () {
ipcRenderer.send(SHOW_UPLOAD_PAGE_MENU) sendToMain(SHOW_UPLOAD_PAGE_MENU)
} }
</script>
<script lang="ts">
export default {
name: 'UploadPage'
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
.view-title .view-title
display flex
color #eee color #eee
font-size 20px font-size 20px
text-align center text-align center
margin 10px auto margin 10px auto
align-items center
justify-content center
#upload-view #upload-view
.view-title .view-title
margin 20px auto margin 20px auto
@ -275,6 +327,8 @@ export default class extends Vue {
.paste-style .paste-style
text-align center text-align center
margin-top 16px margin-top 16px
display flex
align-items flex-end
&__text &__text
font-size 12px font-size 12px
color #eeeeee color #eeeeee

View File

@ -3,11 +3,17 @@
<div class="view-title"> <div class="view-title">
{{ $T('SETTINGS') }} {{ $T('SETTINGS') }}
</div> </div>
<el-row :gutter="15" justify="space-between" align="center" type="flex" class="config-list"> <el-row
:gutter="15"
justify="space-between"
align="middle"
type="flex"
class="config-list"
>
<el-col <el-col
class="config-item-col"
v-for="item in curConfigList" v-for="item in curConfigList"
:key="item._id" :key="item._id"
class="config-item-col"
:span="11" :span="11"
:offset="1" :offset="1"
> >
@ -15,12 +21,32 @@
:class="`config-item ${defaultConfigId === item._id ? 'selected' : ''}`" :class="`config-item ${defaultConfigId === item._id ? 'selected' : ''}`"
@click="() => selectItem(item._id)" @click="() => selectItem(item._id)"
> >
<div class="config-name">{{item._configName}}</div> <div class="config-name">
<div class="config-update-time">{{formatTime(item._updatedAt)}}</div> {{ item._configName }}
<div v-if="defaultConfigId === item._id" class="default-text">{{$T('SELECTED_SETTING_HINT')}}</div> </div>
<div class="config-update-time">
{{ formatTime(item._updatedAt) }}
</div>
<div
v-if="defaultConfigId === item._id"
class="default-text"
>
{{ $T('SELECTED_SETTING_HINT') }}
</div>
<div class="operation-container"> <div class="operation-container">
<i class="el-icon-edit" @click="openEditPage(item._id)"></i> <el-icon
<i :class="`el-icon-delete ${curConfigList.length <= 1 ? 'disabled' : ''}`" @click.stop="() => deleteConfig(item._id)"></i> class="el-icon-edit"
@click="openEditPage(item._id)"
>
<Edit />
</el-icon>
<el-icon
class="el-icon-delete"
:class="curConfigList.length <= 1 ? 'disabled' : ''"
@click.stop="() => deleteConfig(item._id)"
>
<Delete />
</el-icon>
</div> </div>
</div> </div>
</el-col> </el-col>
@ -33,94 +59,130 @@
class="config-item config-item-add" class="config-item config-item-add"
@click="addNewConfig" @click="addNewConfig"
> >
<i class="el-icon-plus"></i> <el-icon
class="el-icon-plus"
>
<Plus />
</el-icon>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
<el-row type="flex" justify="center" :span="24" class="set-default-container"> <el-row
<el-button class="set-default-btn" type="success" @click="setDefaultPicBed(type)" round :disabled="defaultPicBed === type">{{ $T('SETTINGS_SET_DEFAULT_PICBED') }}</el-button> type="flex"
justify="center"
:span="24"
class="set-default-container"
>
<el-button
class="set-default-btn"
type="success"
round
:disabled="store?.state.defaultPicBed === type"
@click="setDefaultPicBed(type)"
>
{{ $T('SETTINGS_SET_DEFAULT_PICBED') }}
</el-button>
</el-row> </el-row>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { Component, Vue } from 'vue-property-decorator' import { Edit, Delete, Plus } from '@element-plus/icons-vue'
import { saveConfig, triggerRPC } from '@/utils/dataSender'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import mixin from '@/utils/ConfirmButtonMixin'
import { IRPCActionType } from '~/universal/types/enum' import { IRPCActionType } from '~/universal/types/enum'
import { T as $T } from '@/i18n/index'
import { useRouter, useRoute, onBeforeRouteUpdate } from 'vue-router'
import { onBeforeMount, ref } from 'vue'
import { PICBEDS_PAGE, UPLOADER_CONFIG_PAGE } from '@/router/config'
import { useStore } from '@/hooks/useStore'
const $router = useRouter()
const $route = useRoute()
@Component({ const type = ref('')
name: 'UploaderConfigPage', const curConfigList = ref<IStringKeyMap[]>([])
mixins: [mixin] const defaultConfigId = ref('')
const store = useStore()
async function selectItem (id: string) {
await triggerRPC<void>(IRPCActionType.SELECT_UPLOADER, type.value, id)
defaultConfigId.value = id
}
onBeforeRouteUpdate((to, from, next) => {
if (to.params.type && (to.name === UPLOADER_CONFIG_PAGE)) {
type.value = to.params.type as string
getCurrentConfigList()
console.log(type.value)
}
next()
}) })
export default class extends Vue {
type!: string;
curConfigList: IStringKeyMap[] = [];
defaultConfigId = '';
async selectItem (id: string) { onBeforeMount(() => {
await this.triggerRPC<void>(IRPCActionType.SELECT_UPLOADER, this.type, id) type.value = $route.params.type as string
this.defaultConfigId = id console.log(type.value)
} getCurrentConfigList()
})
created () { async function getCurrentConfigList () {
this.type = this.$route.params.type const configList = await triggerRPC<IUploaderConfigItem>(IRPCActionType.GET_PICBED_CONFIG_LIST, type.value)
this.getCurrentConfigList() console.log(configList)
} curConfigList.value = configList?.configList ?? []
defaultConfigId.value = configList?.defaultId ?? ''
}
async getCurrentConfigList () { function openEditPage (configId: string) {
const configList = await this.triggerRPC<IUploaderConfigItem>(IRPCActionType.GET_PICBED_CONFIG_LIST, this.type) console.log(configId, type.value, defaultConfigId.value)
this.curConfigList = configList?.configList ?? [] $router.push({
this.defaultConfigId = configList?.defaultId ?? '' name: PICBEDS_PAGE,
}
openEditPage (configId: string) {
this.$router.push({
name: 'picbeds',
params: { params: {
type: this.type, type: type.value,
configId configId
}, },
query: { query: {
defaultConfigId: this.defaultConfigId defaultConfigId: defaultConfigId.value
} }
}) })
} }
formatTime (time: number):string { function formatTime (time: number): string {
return dayjs(time).format('YYYY-MM-DD HH:mm:ss') return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
} }
async deleteConfig (id: string) { async function deleteConfig (id: string) {
const res = await this.triggerRPC<IUploaderConfigItem | undefined>(IRPCActionType.DELETE_PICBED_CONFIG, this.type, id) const res = await triggerRPC<IUploaderConfigItem | undefined>(IRPCActionType.DELETE_PICBED_CONFIG, type.value, id)
if (!res) return if (!res) return
this.curConfigList = res.configList curConfigList.value = res.configList
this.defaultConfigId = res.defaultId defaultConfigId.value = res.defaultId
} }
addNewConfig () { function addNewConfig () {
this.$router.push({ $router.push({
name: 'picbeds', name: PICBEDS_PAGE,
params: { params: {
type: this.type, type: type.value,
configId: '' configId: ''
} }
}) })
} }
setDefaultPicBed (type: string) { function setDefaultPicBed (type: string) {
this.saveConfig({ saveConfig({
'picBed.current': type, 'picBed.current': type,
'picBed.uploader': type 'picBed.uploader': type
}) })
// @ts-ignore mixin
this.defaultPicBed = type store?.setDefaultPicBed(type)
const successNotification = new Notification(this.$T('SETTINGS_DEFAULT_PICBED'), { const successNotification = new Notification($T('SETTINGS_DEFAULT_PICBED'), {
body: this.$T('TIPS_SET_SUCCEED') body: $T('TIPS_SET_SUCCEED')
}) })
successNotification.onclick = () => { successNotification.onclick = () => {
return true return true
} }
} }
</script>
<script lang="ts">
export default {
name: 'UploaderConfigPage'
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>

View File

@ -1,104 +1,104 @@
<template> <template>
<div id="others-view"> <div id="picbeds-page">
<el-row :gutter="20" class="setting-list"> <el-row
<el-col :span="20" :offset="2"> :gutter="20"
class="setting-list"
>
<el-col
:span="20"
:offset="2"
>
<div class="view-title"> <div class="view-title">
{{ picBedName }} {{ $T('SETTINGS') }} {{ picBedName }} {{ $T('SETTINGS') }}
</div> </div>
<config-form <config-form
v-if="config.length > 0" v-if="config.length > 0"
:id="type"
ref="$configForm"
:config="config" :config="config"
type="uploader" type="uploader"
ref="configForm"
:id="type"
> >
<el-form-item> <el-form-item>
<el-button class="confirm-btn" type="primary" @click="handleConfirm" round>{{ $T('CONFIRM') }}</el-button> <el-button
class="confirm-btn"
type="primary"
round
@click="handleConfirm"
>
{{ $T('CONFIRM') }}
</el-button>
</el-form-item> </el-form-item>
</config-form> </config-form>
<div v-else class="single"> <div
<div class="notice">{{ $T('SETTINGS_NOT_CONFIG_OPTIONS') }}</div> v-else
class="single"
>
<div class="notice">
{{ $T('SETTINGS_NOT_CONFIG_OPTIONS') }}
</div>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { Component, Vue } from 'vue-property-decorator' import { IRPCActionType } from '~/universal/types/enum'
import { ref, onBeforeUnmount, onBeforeMount } from 'vue'
import { T as $T } from '@/i18n/index'
import { sendToMain, triggerRPC } from '@/utils/dataSender'
import { useRoute, useRouter } from 'vue-router'
import ConfigForm from '@/components/ConfigForm.vue' import ConfigForm from '@/components/ConfigForm.vue'
import mixin from '@/utils/ConfirmButtonMixin' // import mixin from '@/utils/ConfirmButtonMixin'
import { import {
ipcRenderer, ipcRenderer,
IpcRendererEvent IpcRendererEvent
} from 'electron' } from 'electron'
import { IRPCActionType } from '~/universal/types/enum' const type = ref('')
const config = ref<IPicGoPluginConfig[]>([])
const picBedName = ref('')
const $route = useRoute()
const $router = useRouter()
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
type.value = $route.params.type as string
console.log('picbed type', $route.params.type)
@Component({ onBeforeMount(() => {
name: 'OtherPicBed', sendToMain('getPicBedConfig', $route.params.type)
mixins: [mixin], ipcRenderer.on('getPicBedConfig', getPicBeds)
components: {
ConfigForm
}
}) })
export default class extends Vue {
type: string = ''
config: any[] = []
picBedName: string = ''
created () {
this.type = this.$route.params.type
ipcRenderer.send('getPicBedConfig', this.$route.params.type)
ipcRenderer.on('getPicBedConfig', this.getPicBeds)
}
async handleConfirm () { const handleConfirm = async () => {
// @ts-ignore const result = (await $configForm.value?.validate()) || false
const result = await this.$refs.configForm.validate() console.log(result)
if (result !== false) { if (result !== false) {
await this.triggerRPC<void>(IRPCActionType.UPDATE_UPLOADER_CONFIG, this.type, result?._id, result) await triggerRPC<void>(IRPCActionType.UPDATE_UPLOADER_CONFIG, type.value, result?._id, result)
const successNotification = new Notification(this.$T('SETTINGS_RESULT'), { const successNotification = new Notification($T('SETTINGS_RESULT'), {
body: this.$T('TIPS_SET_SUCCEED') body: $T('TIPS_SET_SUCCEED')
}) })
successNotification.onclick = () => { successNotification.onclick = () => {
return true return true
} }
this.$router.back() $router.back()
}
} }
}
shouldUpdateDefaultConfig (item: IStringKeyMap) { function getPicBeds (event: IpcRendererEvent, _config: IPicGoPluginConfig[], name: string) {
const curDefaultConfigId = this.$route.query.defaultConfigId config.value = _config
if (item._id === curDefaultConfigId) { picBedName.value = name
this.saveConfig(`picBed.${this.type}`, item) }
}
}
setDefaultPicBed (type: string) { onBeforeUnmount(() => {
this.saveConfig({ ipcRenderer.removeListener('getPicBedConfig', getPicBeds)
'picBed.current': type, })
'picBed.uploader': type
})
// @ts-ignore mixin
this.defaultPicBed = type
const successNotification = new Notification(this.$T('SETTINGS_DEFAULT_PICBED'), {
body: this.$T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
}
getPicBeds (event: IpcRendererEvent, config: any[], name: string) { </script>
this.config = config <script lang="ts">
this.picBedName = name export default {
} name: 'PicbedsPage'
beforeDestroy () {
ipcRenderer.removeListener('getPicBedConfig', this.getPicBeds)
}
} }
</script> </script>
<style lang='stylus'> <style lang='stylus'>
#others-view #picbeds-page
.setting-list .setting-list
height 425px height 425px
overflow-y auto overflow-y auto
@ -111,13 +111,11 @@ export default class extends Vue {
padding-bottom 0 padding-bottom 0
color #eee color #eee
&-item &-item
margin-bottom 11px margin-bottom 16px
.el-button-group .el-button-group
width 100% width 100%
.el-button .el-button
width 50% width 50%
.el-input__inner
border-radius 19px
.el-radio-group .el-radio-group
margin-left 25px margin-left 25px
.el-switch__label .el-switch__label

View File

@ -0,0 +1,11 @@
export const GALLERY_PAGE = 'GalleryPage'
export const TRAY_PAGE = 'TrayPage'
export const RENAME_PAGE = 'RenamePage'
export const MINI_PAGE = 'MiniPage'
export const MAIN_PAGE = 'MainPage'
export const UPLOAD_PAGE = 'UploadPage'
export const PICBEDS_PAGE = 'PicbedsPage'
export const SETTING_PAGE = 'SettingPage'
export const PLUGIN_PAGE = 'PluginPage'
export const SHORTKEY_PAGE = 'ShortkeyPage'
export const UPLOADER_CONFIG_PAGE = 'UploaderConfigPage'

View File

@ -1,45 +1,43 @@
import Vue from 'vue' import { createRouter, createWebHashHistory } from 'vue-router'
import Router from 'vue-router' import * as config from './config'
Vue.use(Router) export default createRouter({
history: createWebHashHistory(),
export default new Router({
mode: 'hash',
routes: [ routes: [
{ {
path: '/', path: '/',
name: 'tray-page', name: config.TRAY_PAGE,
component: () => import(/* webpackChunkName: "tray" */ '@/pages/TrayPage.vue') component: () => import(/* webpackChunkName: "tray" */ '@/pages/TrayPage.vue')
}, },
{ {
path: '/rename-page', path: '/rename-page',
name: 'rename-page', name: config.RENAME_PAGE,
component: () => import(/* webpackChunkName: "RenamePage" */ '@/pages/RenamePage.vue') component: () => import(/* webpackChunkName: "RenamePage" */ '@/pages/RenamePage.vue')
}, },
{ {
path: '/mini-page', path: '/mini-page',
name: 'mini-page', name: config.MINI_PAGE,
component: () => import(/* webpackChunkName: "MiniPage" */ '@/pages/MiniPage.vue') component: () => import(/* webpackChunkName: "MiniPage" */ '@/pages/MiniPage.vue')
}, },
{ {
path: '/main-page', path: '/main-page',
name: 'main-page', name: config.MAIN_PAGE,
component: () => import(/* webpackChunkName: "SettingPage" */ '@/layouts/Main.vue'), component: () => import(/* webpackChunkName: "SettingPage" */ '@/layouts/Main.vue'),
children: [ children: [
{ {
path: 'upload', path: 'upload',
component: () => import(/* webpackChunkName: "Upload" */ '@/pages/Upload.vue'), component: () => import(/* webpackChunkName: "Upload" */ '@/pages/Upload.vue'),
name: 'upload' name: config.UPLOAD_PAGE
}, },
{ {
path: 'picbeds/:type/:configId', path: 'picbeds/:type/:configId?',
component: () => import(/* webpackChunkName: "Other" */ '@/pages/picbeds/index.vue'), component: () => import(/* webpackChunkName: "Other" */ '@/pages/picbeds/index.vue'),
name: 'picbeds' name: config.PICBEDS_PAGE
}, },
{ {
path: 'gallery', path: 'gallery',
component: () => import(/* webpackChunkName: "Gallery" */ '@/pages/Gallery.vue'), component: () => import(/* webpackChunkName: "GalleryView" */ '@/pages/Gallery.vue'),
name: 'gallery', name: config.GALLERY_PAGE,
meta: { meta: {
keepAlive: true keepAlive: true
} }
@ -47,27 +45,27 @@ export default new Router({
{ {
path: 'setting', path: 'setting',
component: () => import(/* webpackChunkName: "setting" */ '@/pages/PicGoSetting.vue'), component: () => import(/* webpackChunkName: "setting" */ '@/pages/PicGoSetting.vue'),
name: 'setting' name: config.SETTING_PAGE
}, },
{ {
path: 'plugin', path: 'plugin',
component: () => import(/* webpackChunkName: "Plugin" */ '@/pages/Plugin.vue'), component: () => import(/* webpackChunkName: "Plugin" */ '@/pages/Plugin.vue'),
name: 'plugin' name: config.PLUGIN_PAGE
}, },
{ {
path: 'shortKey', path: 'shortKey',
component: () => import(/* webpackChunkName: "ShortkeyPage" */ '@/pages/ShortKey.vue'), component: () => import(/* webpackChunkName: "ShortkeyPage" */ '@/pages/ShortKey.vue'),
name: 'shortKey' name: config.SHORTKEY_PAGE
}, },
{ {
path: 'uploader-config-page/:type', path: 'uploader-config-page/:type',
component: () => import(/* webpackChunkName: "Other" */ '@/pages/UploaderConfigPage.vue'), component: () => import(/* webpackChunkName: "Other" */ '@/pages/UploaderConfigPage.vue'),
name: 'UploaderConfigPage' name: config.UPLOADER_CONFIG_PAGE
} }
] ]
}, },
{ {
path: '*', path: '/:pathMatch(.*)*',
redirect: '/' redirect: '/'
} }
] ]

View File

@ -1,11 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
modules,
strict: process.env.NODE_ENV !== 'production'
})

View File

@ -0,0 +1,37 @@
import { reactive, InjectionKey, readonly, App, UnwrapRef } from 'vue'
import { saveConfig } from '@/utils/dataSender'
export interface IState {
defaultPicBed: string;
}
export interface IStore {
state: UnwrapRef<IState>
setDefaultPicBed: (type: string) => void;
}
export const storeKey: InjectionKey<IStore> = Symbol('store')
// state
const state: IState = reactive({
defaultPicBed: 'smms'
})
// methods
const setDefaultPicBed = (type: string) => {
saveConfig({
'picBed.current': type,
'picBed.uploader': type
})
state.defaultPicBed = type
console.log(state)
}
export const store = {
install (app: App) {
app.provide(storeKey, {
state: readonly(state),
setDefaultPicBed
})
}
}

View File

@ -1,25 +0,0 @@
const state = {
main: 0
}
const mutations = {
DECREMENT_MAIN_COUNTER (state) {
state.main--
},
INCREMENT_MAIN_COUNTER (state) {
state.main++
}
}
const actions = {
someAsyncTask ({ commit }) {
// do something async
commit('INCREMENT_MAIN_COUNTER')
}
}
export default {
state,
mutations,
actions
}

View File

@ -1,14 +0,0 @@
/**
* The file enables `@/store/index.js` to import all vuex modules
* in a one-shot manner. There should not be any reason to edit this file.
*/
const files = require.context('.', false, /\.js$/)
const modules = {}
files.keys().forEach(key => {
if (key === './index.js') return
modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
})
export default modules

View File

@ -1,26 +0,0 @@
import { Component, Vue } from 'vue-property-decorator'
import { IConfig } from 'picgo'
@Component
export default class extends Vue {
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.saveConfig({
'picBed.current': type,
'picBed.uploader': type
})
this.defaultPicBed = type
const successNotification = new Notification(this.$T('SETTINGS_DEFAULT_PICBED'), {
body: this.$T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
}
}

View File

@ -1,2 +1,20 @@
import Vue from 'vue' import mitt from 'mitt'
export default new Vue() import {
SHOW_INPUT_BOX,
SHOW_INPUT_BOX_RESPONSE,
FORCE_UPDATE
} from '~/universal/events/constants'
type IEvent ={
[SHOW_INPUT_BOX_RESPONSE]: string
[SHOW_INPUT_BOX]: {
value: string
title: string
placeholder: string
},
[FORCE_UPDATE]: void
}
const emitter = mitt<IEvent>()
export default emitter

View File

@ -1,3 +1,5 @@
import { isReactive, isRef, toRaw, unref } from 'vue'
const isDevelopment = process.env.NODE_ENV !== 'production' const isDevelopment = process.env.NODE_ENV !== 'production'
/* eslint-disable camelcase */ /* eslint-disable camelcase */
export const handleTalkingDataEvent = (data: ITalkingDataOptions) => { export const handleTalkingDataEvent = (data: ITalkingDataOptions) => {
@ -16,3 +18,36 @@ export const trimValues = (obj: IStringKeyMap) => {
}) })
return newObj return newObj
} }
/**
* get raw data from reactive or ref
*/
export const getRawData = (args: any) => {
if (Array.isArray(args)) {
const data = args.map((item: any) => {
if (isRef(item)) {
return unref(item)
}
if (isReactive(item)) {
return toRaw(item)
}
return item
})
return data
}
if (typeof args === 'object') {
const data = {} as IStringKeyMap
Object.keys(args).forEach(key => {
const item = args[key]
if (isRef(item)) {
data[key] = unref(item)
} else if (isReactive(item)) {
data[key] = toRaw(item)
} else {
data[key] = getRawData(item)
}
})
return data
}
return args
}

View File

@ -0,0 +1,55 @@
import { ipcRenderer, IpcRendererEvent } from 'electron'
import { PICGO_SAVE_CONFIG, PICGO_GET_CONFIG, RPC_ACTIONS } from '#/events/constants'
import { uuid } from 'uuidv4'
import { IRPCActionType } from '~/universal/types/enum'
import { getRawData } from './common'
export function saveConfig (_config: IObj | string, value?: any) {
let config
if (typeof _config === 'string') {
config = {
[_config]: value
}
} else {
config = getRawData(_config)
}
ipcRenderer.send(PICGO_SAVE_CONFIG, config)
}
export function 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)
})
}
/**
* trigger RPC action
* TODO: create an isolate rpc handler
*/
export function triggerRPC<T> (action: IRPCActionType, ...args: any[]): Promise<T | null> {
return new Promise((resolve) => {
const callbackId = uuid()
const callback = (event: IpcRendererEvent, data: T | null, returnActionType: IRPCActionType, returnCallbackId: string) => {
if (returnCallbackId === callbackId && returnActionType === action) {
resolve(data)
ipcRenderer.removeListener(RPC_ACTIONS, callback)
}
}
const data = getRawData(args)
ipcRenderer.on(RPC_ACTIONS, callback)
ipcRenderer.send(RPC_ACTIONS, action, data, callbackId)
})
}
export function sendToMain (channel: string, ...args: any[]) {
const data = getRawData(args)
ipcRenderer.send(channel, ...data)
}

View File

@ -10,6 +10,7 @@ import {
PICGO_REMOVE_BY_ID_DB PICGO_REMOVE_BY_ID_DB
} from '#/events/constants' } from '#/events/constants'
import { IGalleryDB } from '#/types/extra-vue' import { IGalleryDB } from '#/types/extra-vue'
import { getRawData } from './common'
export class GalleryDB implements IGalleryDB { export class GalleryDB implements IGalleryDB {
async get<T> (filter?: IFilter): Promise<IGetResult<T>> { async get<T> (filter?: IFilter): Promise<IGetResult<T>> {
const res = await this.msgHandler<IGetResult<T>>(PICGO_GET_DB, filter) const res = await this.msgHandler<IGetResult<T>>(PICGO_GET_DB, filter)
@ -50,8 +51,9 @@ export class GalleryDB implements IGalleryDB {
ipcRenderer.removeListener(method, callback) ipcRenderer.removeListener(method, callback)
} }
} }
const data = getRawData(args)
ipcRenderer.on(method, callback) ipcRenderer.on(method, callback)
ipcRenderer.send(method, ...args, callbackId) ipcRenderer.send(method, ...data, callbackId)
}) })
} }
} }

View File

@ -1,63 +1,20 @@
import { Component, Vue } from 'vue-property-decorator' import { ComponentOptions, getCurrentInstance } from 'vue'
import { ipcRenderer, IpcRendererEvent } from 'electron' import { FORCE_UPDATE, GET_PICBEDS } from '~/universal/events/constants'
import { PICGO_SAVE_CONFIG, PICGO_GET_CONFIG, FORCE_UPDATE, RPC_ACTIONS } from '#/events/constants' import bus from '~/renderer/utils/bus'
import { uuid } from 'uuidv4' import { ipcRenderer } from 'electron'
import { IRPCActionType } from '~/universal/types/enum' export const mainMixin: ComponentOptions = {
@Component
export default class extends Vue {
created () { created () {
this.$bus.$on(FORCE_UPDATE, () => { bus.on(FORCE_UPDATE, () => {
this.$forceUpdate() getCurrentInstance()?.proxy?.$forceUpdate()
}) })
} },
// 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)
})
}
/**
* trigger RPC action
* TODO: create an isolate rpc handler
*/
triggerRPC<T> (action: IRPCActionType, ...args: any[]): Promise<T | null> {
return new Promise((resolve) => {
const callbackId = uuid()
const callback = (event: IpcRendererEvent, data: T | null, returnActionType: IRPCActionType, returnCallbackId: string) => {
if (returnCallbackId === callbackId && returnActionType === action) {
resolve(data)
ipcRenderer.removeListener(RPC_ACTIONS, callback)
}
}
ipcRenderer.on(RPC_ACTIONS, callback)
ipcRenderer.send(RPC_ACTIONS, action, args, callbackId)
})
}
methods: {
forceUpdate () { forceUpdate () {
this.$bus.$emit(FORCE_UPDATE) bus.emit(FORCE_UPDATE)
},
getPicBeds () {
ipcRenderer.send(GET_PICBEDS)
} }
sendToMain (channel: string, ...args: any[]) {
ipcRenderer.send(channel, ...args)
} }
} }

View File

@ -1,15 +1,15 @@
import { Component, Vue } from 'vue-property-decorator' import { ComponentOptions } from 'vue'
@Component export const dragMixin: ComponentOptions = {
export default class extends Vue {
mounted () { mounted () {
this.disableDragEvent() this.disableDragEvent()
} },
methods: {
disableDragEvent () { disableDragEvent () {
window.addEventListener('dragenter', this.disableDrag, false) window.addEventListener('dragenter', this.disableDrag, false)
window.addEventListener('dragover', this.disableDrag) window.addEventListener('dragover', this.disableDrag)
window.addEventListener('drop', this.disableDrag) window.addEventListener('drop', this.disableDrag)
} },
disableDrag (e: DragEvent) { disableDrag (e: DragEvent) {
const dropzone = document.getElementById('upload-area') const dropzone = document.getElementById('upload-area')
@ -19,8 +19,9 @@ export default class extends Vue {
e.dataTransfer!.dropEffect = 'none' e.dataTransfer!.dropEffect = 'none'
} }
} }
},
beforeDestroy () { beforeUnmount () {
window.removeEventListener('dragenter', this.disableDrag, false) window.removeEventListener('dragenter', this.disableDrag, false)
window.removeEventListener('dragover', this.disableDrag) window.removeEventListener('dragover', this.disableDrag)
window.removeEventListener('drop', this.disableDrag) window.removeEventListener('drop', this.disableDrag)

View File

@ -25,6 +25,7 @@ export const OPEN_URL = 'OPEN_URL'
export const RELOAD_APP = 'RELOAD_APP' export const RELOAD_APP = 'RELOAD_APP'
export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN' export const PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN'
export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING' export const PICGO_HANDLE_PLUGIN_ING = 'PICGO_HANDLE_PLUGIN_ING'
export const PICGO_HANDLE_PLUGIN_DONE = 'PICGO_HANDLE_PLUGIN_DONE'
export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN' export const PICGO_TOGGLE_PLUGIN = 'PICGO_TOGGLE_PLUGIN'
export const PASTE_TEXT = 'PASTE_TEXT' export const PASTE_TEXT = 'PASTE_TEXT'
export const SET_MINI_WINDOW_POS = 'SET_MINI_WINDOW_POS' export const SET_MINI_WINDOW_POS = 'SET_MINI_WINDOW_POS'

View File

@ -1,4 +1,3 @@
import VueRouter, { Route } from 'vue-router'
import axios from 'axios' import axios from 'axios'
import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types' import { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
@ -13,11 +12,13 @@ interface IGalleryDB {
declare module 'vue/types/vue' { declare module 'vue/types/vue' {
interface Vue { interface Vue {
$router: VueRouter, }
$route: Route, }
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$http: typeof axios $http: typeof axios
$builtInPicBed: string[] $builtInPicBed: string[]
$bus: Vue
$$db: IGalleryDB $$db: IGalleryDB
$T: typeof import('~/renderer/i18n/index').T $T: typeof import('~/renderer/i18n/index').T
$i18n: import('~/renderer/i18n/index').I18nManager $i18n: import('~/renderer/i18n/index').I18nManager
@ -29,4 +30,9 @@ declare module 'vue/types/vue' {
forceUpdate(): void forceUpdate(): void
sendToMain(channel: string, ...args: any[]): void sendToMain(channel: string, ...args: any[]): void
} }
interface GlobalComponents {
PhotoProvider: typeof import('vue3-photo-preview').PhotoProvider
PhotoConsumer: typeof import('vue3-photo-preview').PhotoConsumer
PhotoSlider: typeof import('vue3-photo-preview').PhotoSlider
}
} }

View File

@ -1,6 +1,7 @@
declare module '*.vue' { declare module '*.vue' {
import Vue from 'vue' import { DefineComponent } from 'vue'
export default Vue const component: DefineComponent<{}, {}, any>
export default component
} }
// // third-party // // third-party
// declare module 'fix-path' { // declare module 'fix-path' {

View File

@ -49,6 +49,12 @@ interface ImgInfo {
[propName: string]: any [propName: string]: any
} }
interface IGalleryItem extends ImgInfo {
src: string
key: string
intro: string
}
interface IPicBedType { interface IPicBedType {
type: string type: string
name: string name: string
@ -423,3 +429,5 @@ interface IUploaderConfigItem {
} }
type IUploaderConfigListItem = IStringKeyMap & IUploaderListItemMetaInfo type IUploaderConfigListItem = IStringKeyMap & IUploaderListItemMetaInfo
type ICheckBoxValueType = boolean | string | number

View File

@ -13,7 +13,9 @@
"sourceMap": true, "sourceMap": true,
"baseUrl": ".", "baseUrl": ".",
"types": [ "types": [
"webpack-env" "webpack-env",
"element-plus/global",
"vue3-photo-preview"
], ],
"typeRoots": [ "typeRoots": [
"./src/universal/types/*", "./src/universal/types/*",
@ -56,6 +58,6 @@
"node_modules" "node_modules"
], ],
"vueCompilerOptions": { "vueCompilerOptions": {
"target": 2, "target": 3,
} }
} }

8512
yarn.lock

File diff suppressed because it is too large Load Diff