🔨 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',
extends: [
'plugin:vue/essential',
'plugin:vue/vue3-recommended',
'@vue/standard',
'@vue/typescript'
],

3
.gitignore vendored
View File

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

View File

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

View File

@ -3,25 +3,87 @@
"version": "2.4.0-beta.0",
"private": true,
"scripts": {
"dev": "vue-cli-service electron:serve",
"build": "vue-cli-service electron:build",
"lint": "vue-cli-service lint",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
"bump": "bump-version",
"cz": "git-cz",
"dev": "vue-cli-service electron:serve",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"gen-i18n": "node ./scripts/gen-i18n-types.js",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"cz": "git-cz",
"bump": "bump-version",
"release": "vue-cli-service electron:build --publish always",
"upload-dist": "node ./scripts/upload-dist-to-cos.js",
"gen-i18n": "node ./scripts/gen-i18n-types.js"
"upload-dist": "node ./scripts/upload-dist-to-cos.js"
},
"main": "background.js",
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.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": {
"commitizen": {
@ -31,72 +93,10 @@
"config": "./node_modules/@picgo/bump-version/.cz-config.js"
}
},
"commitlint": {
"extends": [
"./node_modules/@picgo/bump-version/commitlint-picgo"
]
},
"dependencies": {
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.0.4",
"axios": "^0.19.0",
"compare-versions": "^4.1.3",
"core-js": "^3.3.2",
"element-ui": "^2.13.0",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"picgo": "^1.5.0",
"qrcode.vue": "^1.7.0",
"shell-path": "2.1.0",
"uuidv4": "^6.2.11",
"vue": "^2.6.10",
"vue-gallery": "^2.0.1",
"vue-lazyload": "^1.2.6",
"vue-router": "^3.1.3",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@picgo/bump-version": "^1.1.2",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^6.5.0",
"@types/js-yaml": "^4.0.5",
"@types/lowdb": "^1.0.9",
"@types/node": "^16.10.2",
"@types/request-promise-native": "^1.0.17",
"@types/semver": "^7.3.8",
"@types/write-file-atomic": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-plugin-router": "^4.0.0",
"@vue/cli-plugin-typescript": "^4.5.13",
"@vue/cli-service": "^4.0.0",
"@vue/eslint-config-standard": "^6.1.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/runtime-dom": "^3.2.45",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"dotenv": "^16.0.1",
"electron": "^16.0.6",
"electron-devtools-installer": "^3.2.0",
"eslint": "^7.32.0",
"eslint-config-standard": ">=16.0.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.0.0",
"husky": "^3.1.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"typescript": "^4.4.3",
"vue-cli-plugin-electron-builder": "^2.1.1",
"vue-property-decorator": "^8.3.0",
"vue-template-compiler": "^2.6.10"
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"resolutions": {
"@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 router from './renderer/router'
import ElementUI from 'element-ui'
import ElementUI from 'element-plus'
import 'element-plus/dist/index.css'
import { webFrame } from 'electron'
import 'element-ui/lib/theme-chalk/index.css'
import VueLazyLoad from 'vue-lazyload'
import VueLazyLoad from 'vue3-lazyload'
import axios from 'axios'
import mainMixin from './renderer/utils/mainMixin'
import bus from '@/utils/bus'
import { mainMixin } from './renderer/utils/mainMixin'
import { dragMixin } from '@/utils/mixin'
import { initTalkingData } from './renderer/utils/analytics'
import db from './renderer/utils/db'
// import { T, i18n } from '#/i18n/index'
// import { handleURLParams } from '@/utils/beforeOpen'
import { i18nManager, T } from './renderer/i18n/index'
import { getConfig, saveConfig, sendToMain, triggerRPC } from '@/utils/dataSender'
import { store } from '@/store'
import vue3PhotoPreview from 'vue3-photo-preview'
import 'vue3-photo-preview/dist/index.css'
webFrame.setVisualZoomLevelLimits(1, 1)
// do here before vue init
// handleURLParams()
Vue.config.productionTip = false
Vue.prototype.$builtInPicBed = [
const app = createApp(App)
app.config.globalProperties.$builtInPicBed = [
'smms',
'imgur',
'qiniu',
@ -29,21 +32,27 @@ Vue.prototype.$builtInPicBed = [
'aliyun',
'github'
]
Vue.prototype.$$db = db
Vue.prototype.$http = axios
Vue.prototype.$bus = bus
Vue.prototype.$T = T
Vue.prototype.$i18n = i18nManager
Vue.use(ElementUI)
Vue.use(VueLazyLoad, {
app.config.globalProperties.$$db = db
app.config.globalProperties.$http = axios
app.config.globalProperties.$T = T
app.config.globalProperties.$i18n = i18nManager
app.config.globalProperties.getConfig = getConfig
app.config.globalProperties.triggerRPC = triggerRPC
app.config.globalProperties.saveConfig = saveConfig
app.config.globalProperties.sendToMain = sendToMain
app.mixin(mainMixin)
app.mixin(dragMixin)
app.use(VueLazyLoad, {
error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg`
})
Vue.mixin(mainMixin)
app.use(ElementUI)
app.use(router)
app.use(store)
app.use(vue3PhotoPreview)
new Vue({
router,
render: h => h(App)
}).$mount('#app')
app.mount('#app')
initTalkingData()

View File

@ -23,6 +23,7 @@ const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
export const uploadClipboardFiles = async (): Promise<string> => {
const img = await handleClipboardUploading()
console.log(img)
if (img !== false) {
if (img.length > 0) {
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
class GuiApi implements IGuiApi {
// eslint-disable-next-line no-use-before-define
private static instance: GuiApi
private windowId: number = -1
private settingWindowId: number = -1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,104 +1,104 @@
<template>
<div id="others-view">
<el-row :gutter="20" class="setting-list">
<el-col :span="20" :offset="2">
<div id="picbeds-page">
<el-row
:gutter="20"
class="setting-list"
>
<el-col
:span="20"
:offset="2"
>
<div class="view-title">
{{ picBedName }} {{ $T('SETTINGS') }}
</div>
<config-form
v-if="config.length > 0"
:id="type"
ref="$configForm"
:config="config"
type="uploader"
ref="configForm"
:id="type"
>
<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>
</config-form>
<div v-else class="single">
<div class="notice">{{ $T('SETTINGS_NOT_CONFIG_OPTIONS') }}</div>
<div
v-else
class="single"
>
<div class="notice">
{{ $T('SETTINGS_NOT_CONFIG_OPTIONS') }}
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
<script lang="ts" setup>
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 mixin from '@/utils/ConfirmButtonMixin'
// import mixin from '@/utils/ConfirmButtonMixin'
import {
ipcRenderer,
IpcRendererEvent
} 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({
name: 'OtherPicBed',
mixins: [mixin],
components: {
ConfigForm
}
onBeforeMount(() => {
sendToMain('getPicBedConfig', $route.params.type)
ipcRenderer.on('getPicBedConfig', getPicBeds)
})
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 () {
// @ts-ignore
const result = await this.$refs.configForm.validate()
const handleConfirm = async () => {
const result = (await $configForm.value?.validate()) || false
console.log(result)
if (result !== false) {
await this.triggerRPC<void>(IRPCActionType.UPDATE_UPLOADER_CONFIG, this.type, result?._id, result)
const successNotification = new Notification(this.$T('SETTINGS_RESULT'), {
body: this.$T('TIPS_SET_SUCCEED')
await triggerRPC<void>(IRPCActionType.UPDATE_UPLOADER_CONFIG, type.value, result?._id, result)
const successNotification = new Notification($T('SETTINGS_RESULT'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
this.$router.back()
}
$router.back()
}
}
shouldUpdateDefaultConfig (item: IStringKeyMap) {
const curDefaultConfigId = this.$route.query.defaultConfigId
if (item._id === curDefaultConfigId) {
this.saveConfig(`picBed.${this.type}`, item)
}
}
function getPicBeds (event: IpcRendererEvent, _config: IPicGoPluginConfig[], name: string) {
config.value = _config
picBedName.value = name
}
setDefaultPicBed (type: string) {
this.saveConfig({
'picBed.current': type,
'picBed.uploader': type
})
// @ts-ignore mixin
this.defaultPicBed = type
const successNotification = new Notification(this.$T('SETTINGS_DEFAULT_PICBED'), {
body: this.$T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
}
onBeforeUnmount(() => {
ipcRenderer.removeListener('getPicBedConfig', getPicBeds)
})
getPicBeds (event: IpcRendererEvent, config: any[], name: string) {
this.config = config
this.picBedName = name
}
beforeDestroy () {
ipcRenderer.removeListener('getPicBedConfig', this.getPicBeds)
}
</script>
<script lang="ts">
export default {
name: 'PicbedsPage'
}
</script>
<style lang='stylus'>
#others-view
#picbeds-page
.setting-list
height 425px
overflow-y auto
@ -111,13 +111,11 @@ export default class extends Vue {
padding-bottom 0
color #eee
&-item
margin-bottom 11px
margin-bottom 16px
.el-button-group
width 100%
.el-button
width 50%
.el-input__inner
border-radius 19px
.el-radio-group
margin-left 25px
.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 Router from 'vue-router'
import { createRouter, createWebHashHistory } from 'vue-router'
import * as config from './config'
Vue.use(Router)
export default new Router({
mode: 'hash',
export default createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
name: 'tray-page',
name: config.TRAY_PAGE,
component: () => import(/* webpackChunkName: "tray" */ '@/pages/TrayPage.vue')
},
{
path: '/rename-page',
name: 'rename-page',
name: config.RENAME_PAGE,
component: () => import(/* webpackChunkName: "RenamePage" */ '@/pages/RenamePage.vue')
},
{
path: '/mini-page',
name: 'mini-page',
name: config.MINI_PAGE,
component: () => import(/* webpackChunkName: "MiniPage" */ '@/pages/MiniPage.vue')
},
{
path: '/main-page',
name: 'main-page',
name: config.MAIN_PAGE,
component: () => import(/* webpackChunkName: "SettingPage" */ '@/layouts/Main.vue'),
children: [
{
path: 'upload',
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'),
name: 'picbeds'
name: config.PICBEDS_PAGE
},
{
path: 'gallery',
component: () => import(/* webpackChunkName: "Gallery" */ '@/pages/Gallery.vue'),
name: 'gallery',
component: () => import(/* webpackChunkName: "GalleryView" */ '@/pages/Gallery.vue'),
name: config.GALLERY_PAGE,
meta: {
keepAlive: true
}
@ -47,27 +45,27 @@ export default new Router({
{
path: 'setting',
component: () => import(/* webpackChunkName: "setting" */ '@/pages/PicGoSetting.vue'),
name: 'setting'
name: config.SETTING_PAGE
},
{
path: 'plugin',
component: () => import(/* webpackChunkName: "Plugin" */ '@/pages/Plugin.vue'),
name: 'plugin'
name: config.PLUGIN_PAGE
},
{
path: 'shortKey',
component: () => import(/* webpackChunkName: "ShortkeyPage" */ '@/pages/ShortKey.vue'),
name: 'shortKey'
name: config.SHORTKEY_PAGE
},
{
path: 'uploader-config-page/:type',
component: () => import(/* webpackChunkName: "Other" */ '@/pages/UploaderConfigPage.vue'),
name: 'UploaderConfigPage'
name: config.UPLOADER_CONFIG_PAGE
}
]
},
{
path: '*',
path: '/:pathMatch(.*)*',
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'
export default new Vue()
import mitt from 'mitt'
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'
/* eslint-disable camelcase */
export const handleTalkingDataEvent = (data: ITalkingDataOptions) => {
@ -16,3 +18,36 @@ export const trimValues = (obj: IStringKeyMap) => {
})
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
} from '#/events/constants'
import { IGalleryDB } from '#/types/extra-vue'
import { getRawData } from './common'
export class GalleryDB implements IGalleryDB {
async get<T> (filter?: IFilter): Promise<IGetResult<T>> {
const res = await this.msgHandler<IGetResult<T>>(PICGO_GET_DB, filter)
@ -50,8 +51,9 @@ export class GalleryDB implements IGalleryDB {
ipcRenderer.removeListener(method, callback)
}
}
const data = getRawData(args)
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 { ipcRenderer, IpcRendererEvent } from 'electron'
import { PICGO_SAVE_CONFIG, PICGO_GET_CONFIG, FORCE_UPDATE, RPC_ACTIONS } from '#/events/constants'
import { uuid } from 'uuidv4'
import { IRPCActionType } from '~/universal/types/enum'
@Component
export default class extends Vue {
import { ComponentOptions, getCurrentInstance } from 'vue'
import { FORCE_UPDATE, GET_PICBEDS } from '~/universal/events/constants'
import bus from '~/renderer/utils/bus'
import { ipcRenderer } from 'electron'
export const mainMixin: ComponentOptions = {
created () {
this.$bus.$on(FORCE_UPDATE, () => {
this.$forceUpdate()
bus.on(FORCE_UPDATE, () => {
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 () {
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'
@Component
export default class extends Vue {
import { ComponentOptions } from 'vue'
export const dragMixin: ComponentOptions = {
mounted () {
this.disableDragEvent()
}
},
methods: {
disableDragEvent () {
window.addEventListener('dragenter', this.disableDrag, false)
window.addEventListener('dragover', this.disableDrag)
window.addEventListener('drop', this.disableDrag)
}
},
disableDrag (e: DragEvent) {
const dropzone = document.getElementById('upload-area')
@ -19,8 +19,9 @@ export default class extends Vue {
e.dataTransfer!.dropEffect = 'none'
}
}
},
beforeDestroy () {
beforeUnmount () {
window.removeEventListener('dragenter', this.disableDrag, false)
window.removeEventListener('dragover', 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 PICGO_CONFIG_PLUGIN = 'PICGO_CONFIG_PLUGIN'
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 PASTE_TEXT = 'PASTE_TEXT'
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 { IObject, IResult, IGetResult, IFilter } from '@picgo/store/dist/types'
@ -13,11 +12,13 @@ interface IGalleryDB {
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter,
$route: Route,
}
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$http: typeof axios
$builtInPicBed: string[]
$bus: Vue
$$db: IGalleryDB
$T: typeof import('~/renderer/i18n/index').T
$i18n: import('~/renderer/i18n/index').I18nManager
@ -29,4 +30,9 @@ declare module 'vue/types/vue' {
forceUpdate(): 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' {
import Vue from 'vue'
export default Vue
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// // third-party
// declare module 'fix-path' {

View File

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

View File

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

8512
yarn.lock

File diff suppressed because it is too large Load Diff