🔨 Refactor: setting page

This commit is contained in:
PiEgg
2023-07-09 10:12:49 +08:00
parent 8d9a4000f3
commit 10e0584c96
39 changed files with 1736 additions and 1018 deletions
+12 -7
View File
@@ -15,7 +15,8 @@
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"release": "vue-cli-service electron:build --publish always",
"upload-dist": "node ./scripts/upload-dist.js"
"upload-dist": "node ./scripts/upload-dist.js",
"lint:dpdm": "dpdm -T --no-tree --no-warning --exit-code circular:1 src/background.ts;"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
@@ -24,7 +25,7 @@
"axios": "^0.19.0",
"compare-versions": "^4.1.3",
"core-js": "^3.27.1",
"element-plus": "^2.2.28",
"element-plus": "^2.3.7",
"epipebomb": "^1.0.0",
"fs-extra": "^10.0.0",
"js-yaml": "^4.1.0",
@@ -32,14 +33,14 @@
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"mitt": "^3.0.0",
"picgo": "^1.5.3",
"picgo": "^1.5.4",
"qrcode.vue": "^3.3.3",
"shell-path": "2.1.0",
"uuidv4": "^6.2.11",
"vue": "^3.2.45",
"vue-router": "^4.1.6",
"vue": "^3.3.4",
"vue-router": "^4.2.2",
"vue3-lazyload": "^0.3.6",
"vue3-photo-preview": "^0.2.9",
"vue3-photo-preview": "^0.3.0",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
@@ -67,9 +68,11 @@
"@vue/eslint-config-standard": "^8.0.1",
"@vue/eslint-config-typescript": "^11.0.2",
"@vue/runtime-dom": "^3.2.45",
"autoprefixer": "^10.4.14",
"conventional-changelog": "^3.1.18",
"cz-customizable": "^6.2.0",
"dotenv": "^16.0.1",
"dpdm": "^3.13.1",
"electron": "^16.0.6",
"electron-devtools-installer": "^3.2.0",
"eslint": "^8.31.0",
@@ -79,8 +82,10 @@
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^9.8.0",
"husky": "^3.1.0",
"postcss": "^8.4.23",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"tailwindcss": "^3.3.2",
"typescript": "^4.4.3",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
},
@@ -99,7 +104,7 @@
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
"commit-msg": "npm run lint:dpdm && commitlint -E HUSKY_GIT_PARAMS"
}
},
"resolutions": {
+2 -1
View File
@@ -1,5 +1,6 @@
module.exports = {
plugins: {
autoprefixer: {}
autoprefixer: {},
tailwindcss: {}
}
}
+1
View File
@@ -1,3 +1,4 @@
import './renderer/assets/css/tailwind.css'
import { createApp } from 'vue'
import App from './renderer/App.vue'
import router from './renderer/router'
+6
View File
@@ -0,0 +1,6 @@
import { SHORTKEY_COMMAND_UPLOAD } from '../../core/bus/constants'
import { uploadClipboardFiles } from '../uploader/apis'
export const BuiltinShortKeyMap: Record<string, FN> = {
[SHORTKEY_COMMAND_UPLOAD]: uploadClipboardFiles
}
+18 -4
View File
@@ -8,6 +8,8 @@ import db from '~/main/apis/core/datastore'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import shortKeyService from './shortKeyService'
import picgo from '@core/picgo'
import { BuiltinShortKeyMap } from './builtin'
import { SHORTKEY_BUILTIN_PREFIX } from '../../core/bus/constants'
class ShortKeyHandler {
private isInModifiedMode: boolean = false
@@ -29,10 +31,14 @@ class ShortKeyHandler {
.forEach(command => {
const config = commands[command]
// if disabled, don't register #534
logger.info(`register builtin shortKey command: [${command}] - [${config.key}]`)
if (config.enable) {
logger.info(`register builtin shortKey command: [${command}] - [${config.key}] successfully`)
globalShortcut.register(config.key, () => {
this.handler(command)
})
} else {
logger.warn(`builtin shortKey command: [${command}] - [${config.key}] register failed, it's disabled`)
}
})
}
@@ -76,7 +82,7 @@ class ShortKeyHandler {
} else {
logger.warn(`${command} do not provide a key to bind`)
}
// if the configfile already had this command
// if the config file already had this command
// then writeFlag -> false
if (writeFlag) {
picgo.saveConfig({
@@ -130,17 +136,25 @@ class ShortKeyHandler {
private async handler (command: string) {
if (this.isInModifiedMode) {
logger.warn(`in modified mode, ignore shortKey command: [${command}]`)
return
}
if (command.includes('picgo:')) {
bus.emit(command)
if (command.includes(SHORTKEY_BUILTIN_PREFIX)) {
const handler = BuiltinShortKeyMap[command]
if (handler) {
logger.info(`get builtin shortKey handler for command: [${command}]`)
return handler()
} else {
logger.warn(`can't find builtin shortKey handler for command: [${command}]`)
}
} else if (command.includes('picgo-plugin-')) {
const handler = shortKeyService.getShortKeyHandler(command)
if (handler) {
logger.info(`get plugin shortKey handler for command: [${command}]`)
return handler(picgo, GuiApi.getInstance())
}
} else {
logger.warn(`can not find command: ${command}`)
logger.warn(`can not find command: [${command}]`)
}
}
+2 -1
View File
@@ -9,6 +9,7 @@ import pasteTemplate from '~/main/utils/pasteTemplate'
import db, { GalleryDB } from '~/main/apis/core/datastore'
import { handleCopyUrl, handleUrlEncodeWithSetting } from '~/main/utils/common'
import { T } from '~/main/i18n/index'
import logger from '@core/picgo/logger'
// import dayjs from 'dayjs'
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
@@ -21,8 +22,8 @@ const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
}
export const uploadClipboardFiles = async (): Promise<string> => {
logger.info('upload clipboard file')
const img = await handleClipboardUploading()
console.log(img)
if (img !== false) {
if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
+2 -6
View File
@@ -5,13 +5,12 @@ import {
RENAME_WINDOW_URL,
TOOLBOX_WINDOW_URL
} from './constants'
import { IRemoteNoticeTriggerHook, IWindowList } from '#/types/enum'
import { IWindowList } from '#/types/enum'
import bus from '@core/bus'
import { CREATE_APP_MENU } from '@core/bus/constants'
import db from '~/main/apis/core/datastore'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { app } from 'electron'
import { remoteNoticeHandler } from '../remoteNotice'
import { T } from '~/main/i18n'
// import { URLSearchParams } from 'url'
@@ -93,9 +92,6 @@ windowList.set(IWindowList.SETTING_WINDOW, {
return options
},
callback (window, windowManager) {
window.once('show', () => {
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
})
window.loadURL(handleWindowParams(SETTING_WINDOW_URL))
window.on('closed', () => {
bus.emit(TOGGLE_SHORTKEY_MODIFIED_MODE, false)
@@ -132,7 +128,7 @@ windowList.set(IWindowList.MINI_WINDOW, {
}
}
if (db.get('settings.miniWindowOntop')) {
if (db.get('settings.miniWindowOnTop')) {
obj.alwaysOnTop = true
}
return obj
+4
View File
@@ -7,3 +7,7 @@ export const UPLOAD_WITH_FILES_RESPONSE = 'UPLOAD_WITH_FILES_RESPONSE'
export const UPLOAD_WITH_CLIPBOARD_FILES = 'UPLOAD_WITH_CLIPBOARD_FILES'
export const UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE = 'UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE'
export const CREATE_APP_MENU = 'CREATE_APP_MENU'
// shortkey command
export const SHORTKEY_BUILTIN_PREFIX = 'picgo:'
export const SHORTKEY_COMMAND_UPLOAD = `${SHORTKEY_BUILTIN_PREFIX}upload`
-1
View File
@@ -21,7 +21,6 @@ import {
} from '@core/bus/constants'
function initEventCenter () {
const eventList: any = {
'picgo:upload': uploadClipboardFiles,
[UPLOAD_WITH_CLIPBOARD_FILES]: busCallUploadClipboardFiles,
[UPLOAD_WITH_FILES]: busCallUploadFiles,
[GET_WINDOW_ID]: busCallGetWindowId,
+1 -1
View File
@@ -137,7 +137,7 @@ export default {
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
if (db.get('settings.miniWindowOntop')) {
if (db.get('settings.miniWindowOnTop')) {
miniWindow.setAlwaysOnTop(true)
}
+4 -4
View File
@@ -7,14 +7,14 @@ import { builtinI18nList } from '#/i18n'
class I18nManager {
private i18n: I18n | null = null
private builtinI18nFolder = path.join(__static, 'i18n')
private outterI18nFolder = ''
private outerI18nFolder = ''
private localesMap: Map<string, ILocales> = new Map()
private currentLanguage: string = 'zh-CN'
readonly defaultLanguage: string = 'zh-CN'
private i18nFileList: II18nItem[] = builtinI18nList
setOutterI18nFolder (folder: string) {
this.outterI18nFolder = folder
setOuterI18nFolder (folder: string) {
this.outerI18nFolder = folder
}
addI18nFile (file: string, label: string) {
@@ -30,7 +30,7 @@ class I18nManager {
}
let localesPath = path.join(this.builtinI18nFolder, `${lang}.yml`)
if (!fs.existsSync(localesPath)) {
localesPath = path.join(this.outterI18nFolder, `${lang}.yml`)
localesPath = path.join(this.outerI18nFolder, `${lang}.yml`)
if (!fs.existsSync(localesPath)) {
localesPath = path.join(this.builtinI18nFolder, `${this.defaultLanguage}.yml`)
}
+4 -1
View File
@@ -83,7 +83,10 @@ class LifeCycle {
}
}
windowManager.create(IWindowList.TRAY_WINDOW)
windowManager.create(IWindowList.SETTING_WINDOW)
const settingWindow = windowManager.create(IWindowList.SETTING_WINDOW)
settingWindow?.once('show', () => {
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.SETTING_WINDOW_OPEN)
})
createTray()
handleDockIcon()
db.set('needReload', false)
+2 -1
View File
@@ -4,6 +4,7 @@ import path from 'path'
import fse from 'fs-extra'
import { PicGo as PicGoCore } from 'picgo'
import { T } from '~/main/i18n'
import { SHORTKEY_COMMAND_UPLOAD } from 'apis/core/bus/constants'
// from v2.1.2
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
// #557 极端情况可能会出现配置不存在,需要重新写入
@@ -19,7 +20,7 @@ const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IS
}
if (shortKeyConfig.upload) {
// @ts-ignore
shortKeyConfig['picgo:upload'] = {
shortKeyConfig[SHORTKEY_COMMAND_UPLOAD] = {
enable: true,
key: shortKeyConfig.upload,
name: 'upload',
+1 -1
View File
@@ -88,7 +88,7 @@ function resolveOtherI18nFiles () {
if (!fs.pathExistsSync(i18nFolder)) {
fs.mkdirSync(i18nFolder)
}
i18nManager.setOutterI18nFolder(i18nFolder)
i18nManager.setOuterI18nFolder(i18nFolder)
const i18nFiles = fs.readdirSync(path.join(CONFIG_DIR, 'i18n'), {
withFileTypes: true
})
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -0,0 +1,37 @@
<template>
<el-form-item
:label="label"
>
<el-button
type="primary"
round
size="small"
@click="handleClick"
>
{{ buttonLabel }}
</el-button>
</el-form-item>
</template>
<script lang="ts" setup>
interface IProps {
label: string
buttonLabel: string
}
defineProps<IProps>()
const emit = defineEmits(['click'])
const handleClick = () => {
emit('click')
}
</script>
<script lang="ts">
export default {
name: 'ButtonFormItem'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,56 @@
<template>
<el-form
label-position="left"
label-width="50%"
size="small"
>
<el-form-item
:label="label"
>
<el-select
v-model="currentValue"
size="small"
style="width: 100%"
:placeholder="placeholder"
@change="handleChange"
>
<el-option
v-for="item in list"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup generic="T extends string | number">
import { useVModel } from '@/hooks/useVModel'
interface IProps {
modelValue: T
label: string
placeholder?: string
list: {
label: string
value: T
}[]
}
const props = defineProps<IProps>()
const emit = defineEmits(['change'])
const currentValue = useVModel(props, 'modelValue')
const handleChange = (val: T) => {
emit('change', val)
}
</script>
<script lang="ts">
export default {
name: 'SelectFormItem'
}
</script>
<style lang='stylus'>
</style>
@@ -2,7 +2,7 @@
<el-form-item>
<template #label>
<el-row align="middle">
{{ props.settingText }}
{{ props.label }}
<template v-if="props.tooltips">
<el-tooltip
class="item"
@@ -10,7 +10,7 @@
:content="props.tooltips"
placement="right"
>
<el-icon style="margin-left: 4px">
<el-icon class="ml-[4px] cursor-pointer hover:text-blue">
<QuestionFilled />
</el-icon>
</el-tooltip>
@@ -28,37 +28,35 @@
<script lang="ts" setup>
import { T as $T } from '@/i18n'
import { saveConfig } from '@/utils/dataSender'
import { ref, watch } from 'vue'
import { QuestionFilled } from '@element-plus/icons-vue'
import { showNotification } from '@/utils/common'
import { useVModel } from '@/hooks/useVModel'
interface IProps {
tooltips?: string
settingProps: string
settingText: string
defaultValue: boolean
settingProps: keyof ISettingForm
label: string
modelValue: boolean
}
const props = defineProps<IProps>()
// to fix v-model blank bug
watch(() => props.defaultValue, (v) => {
value.value = v
})
const emit = defineEmits(['update:modelValue', 'change'])
const value = ref<boolean>(props.defaultValue)
const emit = defineEmits(['change'])
const value = useVModel(props, 'modelValue')
const handleChange = (val: ICheckBoxValueType) => {
saveConfig(props.settingProps, val)
emit('change', val)
const handleChange = (value: ISwitchValueType) => {
saveConfig(`settings.${props.settingProps}`, value)
emit('update:modelValue', value)
emit('change', value)
showNotification(props.label, $T('TIPS_SET_SUCCEED'))
}
</script>
<script lang="ts">
export default {
name: 'CheckboxFormItem'
name: 'SwitchFormItem'
}
</script>
<style lang='stylus'>
</style>
+16
View File
@@ -0,0 +1,16 @@
import { computed, getCurrentInstance } from 'vue'
export type VModelObject = object
/**
* v-model for single prop
*/
export function useVModel<T extends VModelObject, K extends keyof T> (props: T, key: K) {
const vm = getCurrentInstance()
return computed({
get: () => props[key],
set: (value) => {
vm?.emit(`update:${key as string}`, value)
}
})
}
+29
View File
@@ -0,0 +1,29 @@
import { getCurrentInstance, reactive, UnwrapNestedRefs, watch } from 'vue'
/**
* v-model for multiple props
*/
export function useVModelValues<T extends object> (props: T, keys: Array<keyof T>) {
const vm = getCurrentInstance()
const obj = {} as T
keys.forEach(key => {
obj[key] = props[key]
})
const mutableValue = reactive(obj) as T
watch(() => props, (val) => {
keys.forEach(key => {
mutableValue[key] = val[key]
})
}, {
deep: true
})
function updateProps () {
for (const key of keys) {
vm?.emit(`update:${key as string}`, mutableValue[key])
}
}
return [mutableValue as UnwrapNestedRefs<T>, updateProps] as const
}
+41 -859
View File
@@ -24,553 +24,47 @@
label-width="50%"
size="small"
>
<el-form-item
:label="$T('SETTINGS_CHOOSE_LANGUAGE')"
>
<!-- <el-button type="primary" round size="small" @click="openFile('data.json')">{{ $T('SETTINGS_CLICK_TO_OPEN') }}</el-button> -->
<el-select
v-model="currentLanguage"
size="small"
style="width: 100%"
:placeholder="$T('SETTINGS_CHOOSE_LANGUAGE')"
@change="handleLanguageChange"
>
<el-option
v-for="item in languageList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_OPEN_CONFIG_FILE')"
>
<el-button
type="primary"
round
size="small"
@click="openFile('data.json')"
>
{{ $T('SETTINGS_CLICK_TO_OPEN') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_SET_LOG_FILE')"
>
<el-button
type="primary"
round
size="small"
@click="openLogSetting"
>
{{ $T('SETTINGS_CLICK_TO_SET') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_SET_SHORTCUT')"
>
<el-button
type="primary"
round
size="small"
@click="goShortCutPage"
>
{{ $T('SETTINGS_CLICK_TO_SET') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
>
<el-button
type="primary"
round
size="small"
@click="customLinkVisible = true"
>
{{ $T('SETTINGS_CLICK_TO_SET') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
>
<el-button
type="primary"
round
size="small"
@click="proxyVisible = true"
>
{{ $T('SETTINGS_CLICK_TO_SET') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_SET_SERVER')"
>
<el-button
type="primary"
round
size="small"
@click="serverVisible = true"
>
{{ $T('SETTINGS_CLICK_TO_SET') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_CHECK_UPDATE')"
>
<el-button
type="primary"
round
size="small"
@click="checkUpdate"
>
{{ $T('SETTINGS_CLICK_TO_CHECK') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_OPEN_UPDATE_HELPER')"
>
<el-switch
v-model="form.updateHelper"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="updateHelperChange"
/>
</el-form-item>
<el-form-item
v-show="form.updateHelper"
:label="$T('SETTINGS_ACCEPT_BETA_UPDATE')"
>
<el-switch
v-model="form.checkBetaUpdate"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="checkBetaUpdateChange"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_LAUNCH_ON_BOOT')"
>
<el-switch
v-model="form.autoStart"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="handleAutoStartChange"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_RENAME_BEFORE_UPLOAD')"
>
<el-switch
v-model="form.rename"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="handleRename"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_TIMESTAMP_RENAME')"
>
<el-switch
v-model="form.autoRename"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="handleAutoRename"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_OPEN_UPLOAD_TIPS')"
>
<el-switch
v-model="form.uploadNotification"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="handleUploadNotification"
/>
</el-form-item>
<el-form-item
v-if="os !== 'darwin'"
:label="$T('SETTINGS_MINI_WINDOW_ON_TOP')"
>
<el-switch
v-model="form.miniWindowOntop"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="handleMiniWindowOntop"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD')"
>
<el-switch
v-model="form.autoCopyUrl"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="handleAutoCopyUrl"
/>
</el-form-item>
<el-form-item>
<template #label>
<el-row align="middle">
{{ $T('SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD') }}
<el-tooltip
class="item"
effect="dark"
:content="$T('BUILTIN_CLIPBOARD_TIPS')"
placement="right"
>
<el-icon style="margin-left: 4px">
<QuestionFilled />
</el-icon>
</el-tooltip>
</el-row>
</template>
<el-switch
v-model="form.useBuiltinClipboard"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="useBuiltinClipboardChange"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_ENCODE_OUTPUT_URL')"
>
<el-switch
v-model="form.encodeOutputURL"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
@change="handleEncodeOutputURL"
/>
</el-form-item>
<checkbox-form-item
v-if="os === 'darwin'"
:default-value="form.showDockIcon"
setting-props="settings.showDockIcon"
:setting-text="$T('SETTINGS_SHOW_DOCK_ICON')"
@change="handleShowDockIcon"
<SelectAreaSettings
:settings="form"
/>
<ButtonAreaSettings
v-model:proxy="proxy"
:settings="form"
/>
<SwitchAreaSettings
:settings="form"
/>
<CustomAreaSettings
:settings="form"
/>
<el-form-item
:style="{ marginRight: '-64px' }"
:label="$T('CHOOSE_SHOWED_PICBED')"
>
<el-checkbox-group
v-model="form.showPicBedList"
@change="handleShowPicBedListChange"
>
<el-checkbox
v-for="item in picBed"
:key="item.name"
:label="item.name"
/>
</el-checkbox-group>
</el-form-item>
</el-form>
</el-row>
</el-col>
</el-row>
<el-dialog
v-model="customLinkVisible"
:title="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
:modal-append-to-body="false"
>
<el-form
ref="$customLink"
label-position="top"
:model="customLink"
:rules="rules"
size="small"
>
<el-form-item
prop="value"
>
<div class="custom-title">
{{ $T('SETTINGS_TIPS_PLACEHOLDER_URL') }}
</div>
<div class="custom-title">
{{ $T('SETTINGS_TIPS_PLACEHOLDER_FILENAME') }}
</div>
<div class="custom-title">
{{ $T('SETTINGS_TIPS_PLACEHOLDER_EXTNAME') }}
</div>
<el-input
v-model="customLink.value"
class="align-center"
:autofocus="true"
/>
</el-form-item>
</el-form>
<div>
{{ $T('SETTINGS_TIPS_SUCH_AS') }}[$fileName]($url)
</div>
<template #footer>
<el-button
round
@click="cancelCustomLink"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmCustomLink"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="proxyVisible"
:title="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
:modal-append-to-body="false"
width="70%"
>
<el-form
label-position="right"
:model="customLink"
label-width="120px"
>
<el-form-item
:label="$T('SETTINGS_UPLOAD_PROXY')"
>
<el-input
v-model="proxy"
:autofocus="true"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}http://127.0.0.1:1080`"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_PLUGIN_INSTALL_PROXY')"
>
<el-input
v-model="npmProxy"
:autofocus="true"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}http://127.0.0.1:1080`"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_PLUGIN_INSTALL_MIRROR')"
>
<el-input
v-model="npmRegistry"
:autofocus="true"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}https://registry.npmmirror.com`"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button
round
@click="cancelProxy"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmProxy"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="checkUpdateVisible"
:title="$T('SETTINGS_CHECK_UPDATE')"
:modal-append-to-body="false"
>
<div>
{{ $T('SETTINGS_CURRENT_VERSION') }}: {{ version }}
</div>
<div>
{{ $T('SETTINGS_NEWEST_VERSION') }}: {{ latestVersion ? latestVersion : `${$T('SETTINGS_GETING')}...` }}
</div>
<div v-if="needUpdate">
{{ $T('SETTINGS_TIPS_HAS_NEW_VERSION') }}
</div>
<template #footer>
<el-button
round
@click="cancelCheckVersion"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmCheckVersion"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="logFileVisible"
:title="$T('SETTINGS_SET_LOG_FILE')"
:modal-append-to-body="false"
width="500px"
>
<el-form
label-position="right"
label-width="150px"
>
<el-form-item
:label="$T('SETTINGS_LOG_FILE')"
>
<el-button
type="primary"
round
size="small"
@click="openFile('picgo.log')"
>
{{ $T('SETTINGS_CLICK_TO_OPEN') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_LOG_LEVEL')"
>
<el-select
v-model="form.logLevel"
multiple
collapse-tags
style="width: 100%;"
>
<el-option
v-for="(value, key) of logLevel"
:key="key"
:label="value"
:value="key"
:disabled="handleLevelDisabled(key)"
/>
</el-select>
</el-form-item>
<el-form-item
:label="`${$T('SETTINGS_LOG_FILE_SIZE')} (MB)`"
>
<el-input-number
v-model="form.logFileSizeLimit"
style="width: 100%;"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}10`"
:controls="false"
:min="1"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button
round
@click="cancelLogLevelSetting"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmLogLevelSetting"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="serverVisible"
class="server-dialog"
width="60%"
:title="$T('SETTINGS_SET_PICGO_SERVER')"
:modal-append-to-body="false"
>
<div class="notice-text">
{{ $T('SETTINGS_TIPS_SERVER_NOTICE') }}
</div>
<el-form
label-position="right"
label-width="120px"
>
<el-form-item
:label="$T('SETTINGS_ENABLE_SERVER')"
>
<el-switch
v-model="server.enable"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
/>
</el-form-item>
<template v-if="server.enable">
<el-form-item
:label="$T('SETTINGS_SET_SERVER_HOST')"
>
<el-input
v-model="server.host"
type="input"
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_HOST')"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_SET_SERVER_PORT')"
>
<el-input
v-model="server.port"
type="number"
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_PORT')"
/>
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button
round
@click="cancelServerSetting"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
type="primary"
round
@click="confirmServerSetting"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ElForm, ElMessage as $message, FormRules } from 'element-plus'
import { Reading, QuestionFilled } from '@element-plus/icons-vue'
import pkg from 'root/package.json'
import { ElForm } from 'element-plus'
import { Reading } from '@element-plus/icons-vue'
import { IConfig } from 'picgo'
import { PICGO_OPEN_FILE, OPEN_URL, GET_PICBEDS } from '#/events/constants'
import {
ipcRenderer
} from 'electron'
import { i18nManager, T as $T } from '@/i18n/index'
import { T as $T } from '@/i18n/index'
import { enforceNumber } from '~/universal/utils/common'
import { compare } from 'compare-versions'
import { STABLE_RELEASE_URL, BETA_RELEASE_URL } from '#/utils/static'
import { computed, onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue'
import { getConfig, saveConfig, sendRPC, sendToMain, triggerRPC } from '@/utils/dataSender'
import { useRouter } from 'vue-router'
import { SHORTKEY_PAGE } from '@/router/config'
import { IRPCActionType } from '~/universal/types/enum'
import CheckboxFormItem from '@/components/settings/CheckboxFormItem.vue'
import { onBeforeMount, reactive, ref } from 'vue'
import { getConfig } from '@/utils/dataSender'
import ButtonAreaSettings from './components/settings/buttonArea/ButtonAreaSettings.vue'
import SwitchAreaSettings from './components/settings/switchArea/SwitchAreaSettings.vue'
import CustomAreaSettings from './components/settings/customArea/CustomAreaSettings.vue'
import SelectAreaSettings from './components/settings/selectArea/SelectAreaSettings.vue'
import { openURL } from '@/utils/common'
const $customLink = ref<InstanceType<typeof ElForm> | null>(null)
const customLinkRule = (rule: any, value: string, callback: (arg0?: Error) => void) => {
if (!/\$url/.test(value) && !/\$fileName/.test(value) && !/\$extName/.test(value)) {
return callback(new Error($T('TIPS_MUST_CONTAINS_URL')))
} else {
return callback()
}
}
const $router = useRouter()
const form = reactive<ISettingForm>({
updateHelper: false,
showUpdateTip: false,
showPicBedList: [],
autoStart: false,
rename: false,
autoRename: false,
uploadNotification: false,
miniWindowOntop: false,
miniWindowOnTop: false,
logLevel: ['all'],
autoCopyUrl: true,
checkBetaUpdate: true,
@@ -578,72 +72,20 @@ const form = reactive<ISettingForm>({
language: 'zh-CN',
logFileSizeLimit: 10,
encodeOutputURL: true,
showDockIcon: true
})
const languageList = i18nManager.languageList.map(item => ({
label: item.label,
value: item.value
}))
const currentLanguage = ref('zh-CN')
const picBed = ref<IPicBedType[]>([])
const logFileVisible = ref(false)
const customLinkVisible = ref(false)
const checkUpdateVisible = ref(false)
const serverVisible = ref(false)
const proxyVisible = ref(false)
const customLink = reactive({
value: '$url'
})
const shortKey = reactive<IShortKeyMap>({
upload: ''
})
const proxy = ref('')
const npmRegistry = ref('')
const npmProxy = ref('')
const rules = reactive<FormRules>({
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
})
const logLevel = {
all: $T('SETTINGS_LOG_LEVEL_ALL'),
success: $T('SETTINGS_LOG_LEVEL_SUCCESS'),
error: $T('SETTINGS_LOG_LEVEL_ERROR'),
info: $T('SETTINGS_LOG_LEVEL_INFO'),
warn: $T('SETTINGS_LOG_LEVEL_WARN'),
none: $T('SETTINGS_LOG_LEVEL_NONE')
}
const server = ref({
port: 36677,
host: '127.0.0.1',
enable: true
})
const version = pkg.version
const latestVersion = ref('')
const os = ref('')
const needUpdate = computed(() => {
if (latestVersion.value) {
return compareVersion2Update(version, latestVersion.value)
} else {
return false
showDockIcon: true,
customLink: '$url',
npmProxy: '',
npmRegistry: '',
server: {
port: 36677,
host: '127.0.0.1',
enable: true
}
})
const proxy = ref('')
onBeforeMount(() => {
os.value = process.platform
sendToMain(GET_PICBEDS)
ipcRenderer.on(GET_PICBEDS, getPicBeds)
initData()
})
@@ -652,29 +94,23 @@ async function initData () {
if (config !== undefined) {
const settings = config.settings || {}
const picBed = config.picBed
form.updateHelper = settings.showUpdateTip || false
form.showUpdateTip = settings.showUpdateTip || false
form.autoStart = settings.autoStart || false
form.rename = settings.rename || false
form.autoRename = settings.autoRename || false
form.uploadNotification = settings.uploadNotification || false
form.miniWindowOntop = settings.miniWindowOntop || false
form.miniWindowOnTop = settings.miniWindowOnTop || false
form.logLevel = initLogLevel(settings.logLevel || [])
form.autoCopyUrl = settings.autoCopy === undefined ? true : settings.autoCopy
form.checkBetaUpdate = settings.checkBetaUpdate === undefined ? true : settings.checkBetaUpdate
form.useBuiltinClipboard = settings.useBuiltinClipboard === undefined ? false : settings.useBuiltinClipboard
form.language = settings.language ?? 'zh-CN'
form.encodeOutputURL = settings.encodeOutputURL === undefined ? false : settings.encodeOutputURL
currentLanguage.value = settings.language ?? 'zh-CN'
customLink.value = settings.customLink || '$url'
shortKey.upload = settings.shortKey.upload
form.customLink = settings.customLink || '$url'
form.npmProxy = settings.npmProxy || ''
form.npmRegistry = settings.npmRegistry || ''
proxy.value = picBed.proxy || ''
npmRegistry.value = settings.registry || ''
npmProxy.value = settings.proxy || ''
server.value = settings.server || {
port: 36677,
host: '127.0.0.1',
enable: true
}
form.server = settings.server
form.logFileSizeLimit = enforceNumber(settings.logFileSizeLimit) || 10
form.showDockIcon = settings.showDockIcon === undefined ? true : settings.showDockIcon
}
@@ -691,264 +127,10 @@ function initLogLevel (logLevel: string | string[]) {
return logLevel
}
function getPicBeds (event: Event, picBeds: IPicBedType[]) {
picBed.value = picBeds
form.showPicBedList = picBed.value.map(item => {
if (item.visible) {
return item.name
}
return null
}).filter(item => item) as string[]
}
function openFile (file: string) {
sendToMain(PICGO_OPEN_FILE, file)
}
function openLogSetting () {
logFileVisible.value = true
}
async function cancelCustomLink () {
customLinkVisible.value = false
customLink.value = await getConfig<string>('settings.customLink') || '$url'
}
function confirmCustomLink () {
$customLink.value?.validate((valid: boolean) => {
if (valid) {
saveConfig('settings.customLink', customLink.value)
customLinkVisible.value = false
sendToMain('updateCustomLink')
} else {
return false
}
})
}
async function cancelProxy () {
proxyVisible.value = false
proxy.value = await getConfig<string>('picBed.proxy') || ''
}
function confirmProxy () {
proxyVisible.value = false
saveConfig({
'picBed.proxy': proxy.value,
'settings.proxy': npmProxy.value,
'settings.registry': npmRegistry.value
})
const successNotification = new Notification($T('SETTINGS_SET_PROXY_AND_MIRROR'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
}
function updateHelperChange (val: ICheckBoxValueType) {
saveConfig('settings.showUpdateTip', val)
}
function checkBetaUpdateChange (val: ICheckBoxValueType) {
saveConfig('settings.checkBetaUpdate', val)
}
function useBuiltinClipboardChange (val: ICheckBoxValueType) {
saveConfig('settings.useBuiltinClipboard', val)
}
function handleShowPicBedListChange (val: ICheckBoxValueType[]) {
const list = picBed.value.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else {
item.visible = true
}
return item
})
saveConfig({
'picBed.list': list
})
sendToMain(GET_PICBEDS)
}
function handleAutoStartChange (val: ICheckBoxValueType) {
saveConfig('settings.autoStart', val)
sendToMain('autoStart', val)
}
function handleRename (val: ICheckBoxValueType) {
saveConfig({
'settings.rename': val
})
}
function handleAutoRename (val: ICheckBoxValueType) {
saveConfig({
'settings.autoRename': val
})
}
function compareVersion2Update (current: string, latest: string): boolean {
return compare(current, latest, '<')
}
async function checkUpdate () {
checkUpdateVisible.value = true
const version = await triggerRPC<string>(IRPCActionType.GET_LATEST_VERSION, form.checkBetaUpdate)
if (version) {
latestVersion.value = version
} else {
latestVersion.value = $T('TIPS_NETWORK_ERROR')
}
}
function confirmCheckVersion () {
if (needUpdate.value) {
sendToMain(OPEN_URL, form.checkBetaUpdate ? BETA_RELEASE_URL : STABLE_RELEASE_URL)
}
checkUpdateVisible.value = false
}
function cancelCheckVersion () {
checkUpdateVisible.value = false
}
function handleUploadNotification (val: ICheckBoxValueType) {
saveConfig({
'settings.uploadNotification': val
})
}
function handleMiniWindowOntop (val: ICheckBoxValueType) {
saveConfig('settings.miniWindowOntop', val)
$message.info($T('TIPS_NEED_RELOAD'))
}
function handleAutoCopyUrl (val: ICheckBoxValueType) {
saveConfig('settings.autoCopy', val)
const successNotification = new Notification($T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
}
function handleEncodeOutputURL (val: ICheckBoxValueType) {
saveConfig('settings.encodeOutputURL', val)
const successNotification = new Notification($T('SETTINGS_ENCODE_OUTPUT_URL'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
}
function confirmLogLevelSetting () {
if (form.logLevel.length === 0) {
return $message.error($T('TIPS_PLEASE_CHOOSE_LOG_LEVEL'))
}
saveConfig({
'settings.logLevel': form.logLevel,
'settings.logFileSizeLimit': form.logFileSizeLimit
})
const successNotification = new Notification($T('SETTINGS_SET_LOG_FILE'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
logFileVisible.value = false
}
async function cancelLogLevelSetting () {
logFileVisible.value = false
let logLevel = await getConfig<string | string[]>('settings.logLevel')
const logFileSizeLimit = await getConfig<number>('settings.logFileSizeLimit') || 10
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
form.logLevel = logLevel
form.logFileSizeLimit = logFileSizeLimit
}
function confirmServerSetting () {
server.value.port = parseInt(server.value.port as unknown as string, 10)
saveConfig({
'settings.server': server.value
})
const successNotification = new Notification($T('SETTINGS_SET_PICGO_SERVER'), {
body: $T('TIPS_SET_SUCCEED')
})
successNotification.onclick = () => {
return true
}
serverVisible.value = false
sendToMain('updateServer')
}
async function cancelServerSetting () {
serverVisible.value = false
server.value = await getConfig('settings.server') || {
port: 36677,
host: '127.0.0.1',
enable: true
}
}
function handleLevelDisabled (val: string) {
const currentLevel = val
let flagLevel
const result = form.logLevel.some(item => {
if (item === 'all' || item === 'none') {
flagLevel = item
}
return (item === 'all' || item === 'none')
})
if (result) {
if (currentLevel !== flagLevel) {
return true
}
} else if (form.logLevel.length > 0) {
if (val === 'all' || val === 'none') {
return true
}
}
return false
}
function handleLanguageChange (val: string) {
i18nManager.setCurrentLanguage(val)
saveConfig({
'settings.language': val
})
sendToMain(GET_PICBEDS)
}
function handleShowDockIcon (val: ICheckBoxValueType) {
sendRPC(IRPCActionType.SHOW_DOCK_ICON, val)
}
function goConfigPage () {
sendToMain(OPEN_URL, 'https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
openURL('https://picgo.github.io/PicGo-Doc/zh/guide/config.html#picgo设置')
}
function goShortCutPage () {
$router.push({
name: SHORTKEY_PAGE
})
}
onBeforeUnmount(() => {
ipcRenderer.removeListener(GET_PICBEDS, getPicBeds)
})
</script>
<script lang="ts">
export default {
+1
View File
@@ -119,6 +119,7 @@ import { ipcRenderer, IpcRendererEvent } from 'electron'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { onBeforeUnmount, onBeforeMount, ref, watch } from 'vue'
import { getConfig, sendToMain } from '@/utils/dataSender'
import { T as $T } from '@/i18n/index'
const list = ref<IShortKeyConfig[]>([])
const keyBindingVisible = ref(false)
+1 -1
View File
@@ -5,7 +5,7 @@
:span="20"
:offset="2"
>
<div class="view-title">
<div class="view-title text-[22px]">
{{ $T('PICTURE_UPLOAD') }} - {{ picBedName }}
<el-icon
style="cursor: pointer; margin-left: 4px;"
+2 -2
View File
@@ -193,7 +193,7 @@ export default {
flex-wrap wrap
width: 98%
.config-item
height 85px
height 100px
margin-bottom 20px
border-radius 4px
cursor pointer
@@ -240,7 +240,7 @@ export default {
border 1px solid #409EFF
.set-default-container
position absolute
bottom 10px
bottom 20px
width 100%
.set-default-btn
width 250px
@@ -0,0 +1,113 @@
<template>
<ButtonFormItem
:label="$T('SETTINGS_OPEN_CONFIG_FILE')"
:button-label="$T('SETTINGS_CLICK_TO_OPEN')"
@click="openFile('picgo.log')"
/>
<ButtonFormItem
:label="$T('SETTINGS_SET_LOG_FILE')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="logFileDialogVisible = true"
/>
<ButtonFormItem
:label="$T('SETTINGS_SET_SHORTCUT')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="goShortCutPage"
/>
<ButtonFormItem
:label="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="customLinkDialogVisible = true"
/>
<ButtonFormItem
:label="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="proxySettingDialogVisible = true"
/>
<ButtonFormItem
:label="$T('SETTINGS_SET_SERVER')"
:button-label="$T('SETTINGS_CLICK_TO_SET')"
@click="serverSettingDialogVisible = true"
/>
<ButtonFormItem
:label="$T('SETTINGS_CHECK_UPDATE')"
:button-label="$T('SETTINGS_CLICK_TO_CHECK')"
@click="checkUpdateDialogVisible = true"
/>
<LogSettingDialog
v-model="logFileDialogVisible"
v-model:log-level="form.logLevel"
v-model:log-file-size-limit="form.logFileSizeLimit"
/>
<CustomLinkDialog
v-model="customLinkDialogVisible"
v-model:custom-link="form.customLink"
/>
<ProxySettingDialog
v-model="proxySettingDialogVisible"
v-model:proxy="proxyString"
v-model:npm-proxy="form.npmProxy"
v-model:npm-registry="form.npmRegistry"
/>
<ServerSettingsDialog
v-model="serverSettingDialogVisible"
v-model:host="form.server.host"
v-model:port="form.server.port"
v-model:enable="form.server.enable"
/>
<CheckUpdateDialog
v-model="checkUpdateDialogVisible"
v-model:checkBetaUpdate="form.checkBetaUpdate"
/>
</template>
<script lang="ts" setup>
import ButtonFormItem from '@/components/settings/ButtonFormItem.vue'
import CustomLinkDialog from './CustomLinkDialog.vue'
import ProxySettingDialog from './ProxySettingDialog.vue'
import ServerSettingsDialog from './ServerSettingsDialog.vue'
import CheckUpdateDialog from './CheckUpdateDialog.vue'
import { T as $T } from '@/i18n'
import { sendToMain } from '@/utils/dataSender'
import { reactive, ref } from 'vue'
import { PICGO_OPEN_FILE } from '~/universal/events/constants'
import LogSettingDialog from './LogSettingDialog.vue'
import { useRouter } from 'vue-router'
import { SHORTKEY_PAGE } from '@/router/config'
import { useVModel } from '@/hooks/useVModel'
const $router = useRouter()
interface IProps {
settings: ISettingForm
proxy: string
}
const props = defineProps<IProps>()
const form = reactive(props.settings)
const proxyString = useVModel(props, 'proxy')
const logFileDialogVisible = ref(false)
const customLinkDialogVisible = ref(false)
const proxySettingDialogVisible = ref(false)
const serverSettingDialogVisible = ref(false)
const checkUpdateDialogVisible = ref(false)
function openFile (file: string) {
sendToMain(PICGO_OPEN_FILE, file)
}
function goShortCutPage () {
$router.push({
name: SHORTKEY_PAGE
})
}
</script>
<script lang="ts">
export default {
name: 'ButtonAreaSettings'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,105 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="$T('SETTINGS_CHECK_UPDATE')"
:append-to-body="true"
>
<div>
{{ $T('SETTINGS_CURRENT_VERSION') }}: {{ version }}
</div>
<div>
{{ $T('SETTINGS_NEWEST_VERSION') }}: {{ latestVersion ? latestVersion : `${$T('SETTINGS_GETING')}...` }}
</div>
<div v-if="needUpdate">
{{ $T('SETTINGS_TIPS_HAS_NEW_VERSION') }}
</div>
<template #footer>
<el-button
size="default"
round
@click="cancelCheckVersion"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmCheckVersion"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import { T as $T } from '@/i18n'
import { STABLE_RELEASE_URL, BETA_RELEASE_URL } from '#/utils/static'
import pkg from 'root/package.json'
import { compare } from 'compare-versions'
import { IRPCActionType } from '~/universal/types/enum'
import { useVModel } from '@/hooks/useVModel'
import { triggerRPC } from '@/utils/dataSender'
import { openURL } from '@/utils/common'
interface IProps {
modelValue: boolean
checkBetaUpdate: boolean
}
const props = defineProps<IProps>()
const dialogVisible = useVModel(props, 'modelValue')
const checkBetaUpdate = useVModel(props, 'checkBetaUpdate')
const version = pkg.version
const latestVersion = ref('')
function compareVersion2Update (current: string, latest: string): boolean {
return compare(current, latest, '<')
}
const needUpdate = computed(() => {
if (latestVersion.value) {
return compareVersion2Update(version, latestVersion.value)
} else {
return false
}
})
watch(() => dialogVisible.value, (value) => {
if (value) {
checkUpdate()
}
})
async function checkUpdate () {
const version = await triggerRPC<string>(IRPCActionType.GET_LATEST_VERSION, checkBetaUpdate.value)
if (version) {
latestVersion.value = version
} else {
latestVersion.value = $T('TIPS_NETWORK_ERROR')
}
}
function confirmCheckVersion () {
if (needUpdate.value) {
openURL(checkBetaUpdate.value ? BETA_RELEASE_URL : STABLE_RELEASE_URL)
}
dialogVisible.value = false
}
function cancelCheckVersion () {
latestVersion.value = ''
dialogVisible.value = false
}
</script>
<script lang="ts">
export default {
name: 'CheckUpdateDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,118 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="$T('SETTINGS_CUSTOM_LINK_FORMAT')"
:append-to-body="true"
>
<el-form
ref="$customLink"
label-position="top"
:model="form"
:rules="rules"
size="small"
>
<el-form-item
prop="customLink"
>
<div class="flex flex-col mb-[8px]">
<div>
{{ $T('SETTINGS_TIPS_PLACEHOLDER_URL') }}
</div>
<div>
{{ $T('SETTINGS_TIPS_PLACEHOLDER_FILENAME') }}
</div>
<div>
{{ $T('SETTINGS_TIPS_PLACEHOLDER_EXTNAME') }}
</div>
</div>
<el-input
v-model="form.customLink"
class="align-center"
:autofocus="true"
size="default"
/>
</el-form-item>
</el-form>
<div>
{{ $T('SETTINGS_TIPS_SUCH_AS') }}[$fileName]($url)
</div>
<template #footer>
<el-button
size="default"
round
@click="cancelCustomLink"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmCustomLink"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { T as $T } from '@/i18n'
import { useVModel } from '@/hooks/useVModel'
import { saveConfig, sendToMain } from '@/utils/dataSender'
import { ElForm, FormRules } from 'element-plus'
import { useVModelValues } from '@/hooks/useVModelValues'
interface IProps {
modelValue: boolean
customLink: string
}
const props = defineProps<IProps>()
const [form, updateProps] = useVModelValues<IProps>(props, ['customLink'])
const $customLink = ref<InstanceType<typeof ElForm> | null>(null)
const dialogVisible = useVModel(props, 'modelValue')
const customLinkRule = (rule: any, value: string, callback: (arg0?: Error) => void) => {
if (!/\$url/.test(value) && !/\$fileName/.test(value) && !/\$extName/.test(value)) {
return callback(new Error($T('TIPS_MUST_CONTAINS_URL')))
} else {
return callback()
}
}
const rules = reactive<FormRules>({
value: [
{ validator: customLinkRule, trigger: 'blur' }
]
})
function confirmCustomLink () {
$customLink.value?.validate((valid: boolean) => {
if (valid) {
saveConfig('settings.customLink', form.customLink)
dialogVisible.value = false
sendToMain('updateCustomLink')
updateProps()
} else {
return false
}
})
}
async function cancelCustomLink () {
dialogVisible.value = false
form.customLink = props.customLink
}
</script>
<script lang="ts">
export default {
name: 'CustomLinkDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,156 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="$T('SETTINGS_SET_LOG_FILE')"
:append-to-body="true"
width="500px"
>
<el-form
label-position="right"
label-width="150px"
>
<el-form-item
:label="$T('SETTINGS_LOG_FILE')"
>
<el-button
type="primary"
round
size="small"
@click="openFile('picgo.log')"
>
{{ $T('SETTINGS_CLICK_TO_OPEN') }}
</el-button>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_LOG_LEVEL')"
>
<el-select
v-model="form.logLevel"
multiple
collapse-tags
style="width: 100%;"
>
<el-option
v-for="(value, key) of logLevelMap"
:key="key"
:label="value"
:value="key"
:disabled="handleLevelDisabled(key)"
/>
</el-select>
</el-form-item>
<el-form-item
:label="`${$T('SETTINGS_LOG_FILE_SIZE')} (MB)`"
>
<el-input-number
v-model="form.logFileSizeLimit"
style="width: 100%;"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}10`"
:controls="false"
:min="1"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button
size="default"
round
@click="cancelLogLevelSetting"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmLogLevelSetting"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { T as $T } from '@/i18n'
import { saveConfig } from '@/utils/dataSender'
import { useVModel } from '@/hooks/useVModel'
import { openFile, showNotification } from '@/utils/common'
import { ElMessage as $message } from 'element-plus'
import { useVModelValues } from '@/hooks/useVModelValues'
const logLevelMap = {
all: $T('SETTINGS_LOG_LEVEL_ALL'),
success: $T('SETTINGS_LOG_LEVEL_SUCCESS'),
error: $T('SETTINGS_LOG_LEVEL_ERROR'),
info: $T('SETTINGS_LOG_LEVEL_INFO'),
warn: $T('SETTINGS_LOG_LEVEL_WARN'),
none: $T('SETTINGS_LOG_LEVEL_NONE')
}
interface IProps {
modelValue: boolean
logLevel: string[]
logFileSizeLimit: number
}
const props = defineProps<IProps>()
const dialogVisible = useVModel(props, 'modelValue')
const [form, updateProps] = useVModelValues<IProps>(props, ['logFileSizeLimit', 'logLevel'])
async function cancelLogLevelSetting () {
dialogVisible.value = false
let logLevel = props.logLevel as string | string[]
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
form.logLevel = logLevel
form.logFileSizeLimit = props.logFileSizeLimit
}
function confirmLogLevelSetting () {
if (form.logLevel.length === 0) {
return $message.error($T('TIPS_PLEASE_CHOOSE_LOG_LEVEL'))
}
saveConfig({
'settings.logLevel': form.logLevel,
'settings.logFileSizeLimit': form.logFileSizeLimit
})
showNotification($T('SETTINGS_SET_LOG_FILE'), $T('TIPS_SET_SUCCEED'))
updateProps()
dialogVisible.value = false
}
function handleLevelDisabled (val: string) {
const currentLevel = val
let flagLevel
const result = form.logLevel.some((item: any) => {
if (item === 'all' || item === 'none') {
flagLevel = item
}
return (item === 'all' || item === 'none')
})
if (result) {
if (currentLevel !== flagLevel) {
return true
}
} else if (form.logLevel.length > 0) {
if (val === 'all' || val === 'none') {
return true
}
}
return false
}
</script>
<script lang="ts">
export default {
name: 'LogSettingDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,103 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="$T('SETTINGS_SET_PROXY_AND_MIRROR')"
:append-to-body="true"
width="70%"
>
<el-form
label-position="right"
label-width="120px"
>
<el-form-item
:label="$T('SETTINGS_UPLOAD_PROXY')"
>
<el-input
v-model="form.proxy"
:autofocus="true"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}http://127.0.0.1:1080`"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_PLUGIN_INSTALL_PROXY')"
>
<el-input
v-model="form.npmProxy"
:autofocus="true"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}http://127.0.0.1:1080`"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_PLUGIN_INSTALL_MIRROR')"
>
<el-input
v-model="form.npmRegistry"
:autofocus="true"
:placeholder="`${$T('SETTINGS_TIPS_SUCH_AS')}https://registry.npmmirror.com`"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button
size="default"
round
@click="cancelProxy"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmProxy"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { T as $T } from '@/i18n'
import { useVModel } from '@/hooks/useVModel'
import { saveConfig } from '@/utils/dataSender'
import { useVModelValues } from '@/hooks/useVModelValues'
import { showNotification } from '@/utils/common'
interface IProps {
modelValue: boolean
proxy: string
npmProxy: string
npmRegistry: string
}
const props = defineProps<IProps>()
const [form, updateProps] = useVModelValues<IProps>(props, ['proxy', 'npmProxy', 'npmRegistry'])
const dialogVisible = useVModel(props, 'modelValue')
function confirmProxy () {
dialogVisible.value = false
saveConfig({
'picBed.proxy': form.proxy,
'settings.npmProxy': form.npmProxy,
'settings.npmRegistry': form.npmRegistry
})
showNotification($T('SETTINGS_SET_PROXY_AND_MIRROR'), $T('TIPS_SET_SUCCEED'))
updateProps()
}
async function cancelProxy () {
dialogVisible.value = false
form.proxy = props.proxy || ''
form.npmProxy = props.npmProxy || ''
form.npmRegistry = props.npmRegistry || ''
}
</script>
<script lang="ts">
export default {
name: 'ProxySettingDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,108 @@
<template>
<el-dialog
v-model="dialogVisible"
class="server-dialog"
width="60%"
:title="$T('SETTINGS_SET_PICGO_SERVER')"
:append-to-body="true"
>
<div class="notice-text">
{{ $T('SETTINGS_TIPS_SERVER_NOTICE') }}
</div>
<el-form
label-position="right"
label-width="120px"
>
<el-form-item
:label="$T('SETTINGS_ENABLE_SERVER')"
>
<el-switch
v-model="form.enable"
:active-text="$T('SETTINGS_OPEN')"
:inactive-text="$T('SETTINGS_CLOSE')"
/>
</el-form-item>
<template v-if="form.enable">
<el-form-item
:label="$T('SETTINGS_SET_SERVER_HOST')"
>
<el-input
v-model="form.host"
type="input"
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_HOST')"
/>
</el-form-item>
<el-form-item
:label="$T('SETTINGS_SET_SERVER_PORT')"
>
<el-input
v-model="form.port"
type="number"
:placeholder="$T('SETTINGS_TIP_PLACEHOLDER_PORT')"
/>
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button
size="default"
round
@click="cancelServerSetting"
>
{{ $T('CANCEL') }}
</el-button>
<el-button
size="default"
type="primary"
round
@click="confirmServerSetting"
>
{{ $T('CONFIRM') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { T as $T } from '@/i18n'
import { useVModel } from '@/hooks/useVModel'
import { useVModelValues } from '@/hooks/useVModelValues'
import { saveConfig, sendToMain } from '@/utils/dataSender'
import { showNotification } from '@/utils/common'
interface IProps {
modelValue: boolean
port: number
host: string
enable: boolean
}
const props = defineProps<IProps>()
const dialogVisible = useVModel(props, 'modelValue')
const [form, updateProps] = useVModelValues<IProps>(props, ['port', 'host', 'enable'])
function confirmServerSetting () {
form.port = parseInt(form.port as unknown as string, 10)
saveConfig({
'settings.server': form
})
showNotification($T('SETTINGS_SET_PICGO_SERVER'), $T('TIPS_SET_SUCCEED'))
dialogVisible.value = false
sendToMain('updateServer')
updateProps()
}
async function cancelServerSetting () {
dialogVisible.value = false
form.port = props.port
form.host = props.host
form.enable = props.enable
}
</script>
<script lang="ts">
export default {
name: 'ServerSettingsDialog'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,71 @@
<template>
<el-form-item
:style="{ marginRight: '-64px' }"
:label="$T('CHOOSE_SHOWED_PICBED')"
>
<el-checkbox-group
v-model="showPicBedList"
@change="handleShowPicBedListChange"
>
<el-checkbox
v-for="item in picBed"
:key="item.name"
:label="item.name"
/>
</el-checkbox-group>
</el-form-item>
</template>
<script lang="ts" setup>
import { onBeforeMount, ref } from 'vue'
import { T as $T } from '@/i18n'
import { GET_PICBEDS } from '#/events/constants'
import { saveConfig, sendToMain } from '@/utils/dataSender'
import { useIPCOn } from '@/hooks/useIPC'
import { useVModel } from '@/hooks/useVModel'
interface IProps {
showPicBedList: string[]
}
const props = defineProps<IProps>()
const picBed = ref<IPicBedType[]>([])
const showPicBedList = useVModel(props, 'showPicBedList')
function handleShowPicBedListChange (val: ISwitchValueType[]) {
const list = picBed.value.map(item => {
if (!val.includes(item.name)) {
item.visible = false
} else {
item.visible = true
}
return item
})
saveConfig({
'picBed.list': list
})
sendToMain(GET_PICBEDS)
}
onBeforeMount(() => {
sendToMain(GET_PICBEDS)
useIPCOn(GET_PICBEDS, getPicBeds)
})
function getPicBeds (event: Event, picBeds: IPicBedType[]) {
picBed.value = picBeds
showPicBedList.value = picBed.value.map(item => {
if (item.visible) {
return item.name
}
return null
}).filter(item => item) as string[]
}
</script>
<script lang="ts">
export default {
name: 'ChoosePicBed'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,22 @@
<template>
<ChoosePicBed
v-model:showPicBedList="form.showPicBedList"
/>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import ChoosePicBed from './ChoosePicBed.vue'
interface IProps {
settings: ISettingForm
}
const props = defineProps<IProps>()
const form = reactive(props.settings)
</script>
<script lang="ts">
export default {
name: 'CustomAreaSettings'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,44 @@
<template>
<SelectFormItem
v-model="form.language"
:list="languageList"
:label="$T('SETTINGS_CHOOSE_LANGUAGE')"
:placeholder="$T('SETTINGS_CHOOSE_LANGUAGE')"
@change="handleLanguageChange"
/>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import { T as $T, i18nManager } from '@/i18n'
import { saveConfig, sendToMain } from '@/utils/dataSender'
import { GET_PICBEDS } from '~/universal/events/constants'
import SelectFormItem from '@/components/settings/SelectFormItem.vue'
interface IProps {
settings: ISettingForm
}
const props = defineProps<IProps>()
const form = reactive(props.settings)
const languageList = i18nManager.languageList.map(item => ({
label: item.label,
value: item.value
}))
function handleLanguageChange (val: string) {
i18nManager.setCurrentLanguage(val)
saveConfig({
'settings.language': val
})
sendToMain(GET_PICBEDS)
}
</script>
<script lang="ts">
export default {
name: 'SelectAreaSettings'
}
</script>
<style lang='stylus'>
</style>
@@ -0,0 +1,101 @@
<template>
<SwitchFormItem
v-model="form.showUpdateTip"
setting-props="showUpdateTip"
:label="$T('SETTINGS_OPEN_UPDATE_HELPER')"
/>
<SwitchFormItem
v-show="form.showUpdateTip"
v-model="form.checkBetaUpdate"
setting-props="checkBetaUpdate"
:label="$T('SETTINGS_ACCEPT_BETA_UPDATE')"
/>
<SwitchFormItem
v-model="form.autoStart"
setting-props="autoStart"
:label="$T('SETTINGS_LAUNCH_ON_BOOT')"
@change="handleAutoStartChange"
/>
<SwitchFormItem
v-model="form.rename"
setting-props="rename"
:label="$T('SETTINGS_RENAME_BEFORE_UPLOAD')"
/>
<SwitchFormItem
v-model="form.autoRename"
setting-props="autoRename"
:label="$T('SETTINGS_TIMESTAMP_RENAME')"
/>
<SwitchFormItem
v-model="form.uploadNotification"
setting-props="uploadNotification"
:label="$T('SETTINGS_OPEN_UPLOAD_TIPS')"
/>
<SwitchFormItem
v-if="os !== 'darwin'"
v-model="form.miniWindowOnTop"
:label="$T('SETTINGS_MINI_WINDOW_ON_TOP')"
setting-props="miniWindowOnTop"
@change="handleMiniWindowOnTop"
/>
<SwitchFormItem
v-model="form.autoCopyUrl"
:label="$T('SETTINGS_AUTO_COPY_URL_AFTER_UPLOAD')"
setting-props="autoCopyUrl"
/>
<SwitchFormItem
v-model="form.useBuiltinClipboard"
:label="$T('SETTINGS_USE_BUILTIN_CLIPBOARD_UPLOAD')"
:tooltips="$T('BUILTIN_CLIPBOARD_TIPS')"
setting-props="useBuiltinClipboard"
/>
<SwitchFormItem
v-model="form.encodeOutputURL"
:label="$T('SETTINGS_ENCODE_OUTPUT_URL')"
setting-props="encodeOutputURL"
/>
<SwitchFormItem
v-if="os === 'darwin'"
v-model="form.showDockIcon"
setting-props="showDockIcon"
:label="$T('SETTINGS_SHOW_DOCK_ICON')"
@change="handleShowDockIcon"
/>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { T as $T } from '@/i18n/index'
import { ElMessage as $message } from 'element-plus'
import { sendRPC, sendToMain } from '@/utils/dataSender'
import { IRPCActionType } from '~/universal/types/enum'
import SwitchFormItem from '@/components/settings/SwitchFormItem.vue'
const os = ref(process.platform)
interface IProps {
settings: ISettingForm
}
const props = defineProps<IProps>()
const form = reactive(props.settings)
function handleAutoStartChange (val: ISwitchValueType) {
sendToMain('autoStart', val)
}
function handleMiniWindowOnTop () {
$message.info($T('TIPS_NEED_RELOAD'))
}
function handleShowDockIcon (val: ISwitchValueType) {
sendRPC(IRPCActionType.SHOW_DOCK_ICON, val)
}
</script>
<script lang="ts">
export default {
name: 'SwitchAreaSettings'
}
</script>
<style lang='stylus'>
</style>
+19
View File
@@ -1,4 +1,6 @@
import { isReactive, isRef, toRaw, unref } from 'vue'
import { sendToMain } from './dataSender'
import { OPEN_URL, PICGO_OPEN_FILE } from '~/universal/events/constants'
const isDevelopment = process.env.NODE_ENV !== 'production'
/* eslint-disable camelcase */
@@ -51,3 +53,20 @@ export const getRawData = (args: any): any => {
}
return args
}
export const showNotification = (title: string, body: string) => {
const notification = new Notification(title, {
body
})
notification.onclick = () => {
return true
}
}
export const openFile = (fileName: string) => {
sendToMain(PICGO_OPEN_FILE, fileName)
}
export const openURL = (url: string) => {
sendToMain(OPEN_URL, url)
}
+4 -1
View File
@@ -1,4 +1,7 @@
// global
type FN = (...args: any) => any
interface IObj {
[propName: string]: any
}
@@ -430,4 +433,4 @@ interface IUploaderConfigItem {
type IUploaderConfigListItem = IStringKeyMap & IUploaderListItemMetaInfo
type ICheckBoxValueType = boolean | string | number
type ISwitchValueType = boolean | string | number
+10 -2
View File
@@ -1,11 +1,11 @@
interface ISettingForm {
updateHelper: boolean
showUpdateTip: boolean
showPicBedList: string[]
autoStart: boolean
rename: boolean
autoRename: boolean
uploadNotification: boolean
miniWindowOntop: boolean
miniWindowOnTop: boolean
logLevel: string[]
autoCopyUrl: boolean
checkBetaUpdate: boolean
@@ -14,6 +14,14 @@ interface ISettingForm {
logFileSizeLimit: number
encodeOutputURL: boolean
showDockIcon: boolean
customLink: string
npmProxy: string
npmRegistry: string
server: {
port: number
host: string
enable: boolean
}
}
interface IShortKeyMap {
+18
View File
@@ -0,0 +1,18 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./public/index.html',
'./src/**/*.{js,ts,jsx,tsx,vue}'
],
theme: {
extend: {
colors: {
blue: '#49B1F5',
green: '#44B363',
red: '#F15140',
yellow: '#F1BE48'
}
}
},
plugins: []
}
+484 -108
View File
File diff suppressed because it is too large Load Diff