mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
✨ Feature(custom): rewrite upload config and plugin page
This commit is contained in:
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -20,11 +20,11 @@
|
|||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"i18n-ally.localesPaths": ["src\\renderer\\i18n\\locales"],
|
"i18n-ally.localesPaths": ["src\\renderer\\i18n\\locales", "resources\\i18n"],
|
||||||
"i18n-ally.keystyle": "nested",
|
"i18n-ally.keystyle": "nested",
|
||||||
"i18n-ally.sortKeys": true,
|
"i18n-ally.sortKeys": true,
|
||||||
"i18n-ally.namespace": true,
|
"i18n-ally.namespace": true,
|
||||||
"i18n-ally.enabledParsers": ["json"],
|
"i18n-ally.enabledParsers": ["json", "yaml"],
|
||||||
"i18n-ally.sourceLanguage": "en",
|
"i18n-ally.sourceLanguage": "en",
|
||||||
"i18n-ally.displayLanguage": "zh-CN",
|
"i18n-ally.displayLanguage": "zh-CN",
|
||||||
"i18n-ally.enabledFrameworks": ["vue"],
|
"i18n-ally.enabledFrameworks": ["vue"],
|
||||||
|
|||||||
@@ -121,6 +121,7 @@
|
|||||||
"dpdm": "^3.14.0",
|
"dpdm": "^3.14.0",
|
||||||
"electron": "^36.7.3",
|
"electron": "^36.7.3",
|
||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
|
"electron-devtools-installer": "^4.0.0",
|
||||||
"electron-vite": "^4.0.0",
|
"electron-vite": "^4.0.0",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.32.0",
|
||||||
"eslint-plugin-prettier": "^5.5.3",
|
"eslint-plugin-prettier": "^5.5.3",
|
||||||
|
|||||||
@@ -122,12 +122,12 @@ if (db.get(configPaths.settings.miniWindowOntop)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renameWindowOptions = {
|
const renameWindowOptions = {
|
||||||
height: 175,
|
height: 250,
|
||||||
width: 300,
|
width: 350,
|
||||||
show: true,
|
show: true,
|
||||||
fullscreenable: false,
|
fullscreenable: false,
|
||||||
resizable: false,
|
icon: logo,
|
||||||
vibrancy: 'ultra-dark',
|
resizable: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
preload: preloadPath,
|
preload: preloadPath,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const getPluginList = async (): Promise<IPicGoPlugin[]> => {
|
|||||||
fullName: pluginList[i],
|
fullName: pluginList[i],
|
||||||
author: pluginPKG.author.name || pluginPKG.author,
|
author: pluginPKG.author.name || pluginPKG.author,
|
||||||
description: pluginPKG.description,
|
description: pluginPKG.description,
|
||||||
logo: 'file://' + path.join(pluginPath, 'logo.png').split(path.sep).join('/'),
|
logo: path.join(pluginPath, 'logo.png').split(path.sep).join('/'),
|
||||||
version: pluginPKG.version,
|
version: pluginPKG.version,
|
||||||
gui,
|
gui,
|
||||||
config: {
|
config: {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis
|
|||||||
import windowManager from 'apis/app/window/windowManager'
|
import windowManager from 'apis/app/window/windowManager'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { app, dialog, globalShortcut, Notification, protocol, screen, shell } from 'electron'
|
import { app, dialog, globalShortcut, Notification, protocol, screen, shell } from 'electron'
|
||||||
|
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||||
import updater from 'electron-updater'
|
import updater from 'electron-updater'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
|
|
||||||
@@ -167,10 +168,17 @@ class LifeCycle {
|
|||||||
|
|
||||||
#onReady () {
|
#onReady () {
|
||||||
const readyFunction = async () => {
|
const readyFunction = async () => {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
installExtension(VUEJS_DEVTOOLS).then(name => {
|
||||||
|
console.log(`Added Extension: ${JSON.stringify(name)}`)
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('An error occurred: ', err)
|
||||||
|
})
|
||||||
|
const setwin = windowManager.get(IWindowList.SETTING_WINDOW)
|
||||||
|
setwin?.webContents?.openDevTools({ mode: 'detach' })
|
||||||
|
}
|
||||||
windowManager.create(IWindowList.TRAY_WINDOW)
|
windowManager.create(IWindowList.TRAY_WINDOW)
|
||||||
windowManager.create(IWindowList.SETTING_WINDOW)
|
windowManager.create(IWindowList.SETTING_WINDOW)
|
||||||
const setwin = windowManager.get(IWindowList.SETTING_WINDOW)
|
|
||||||
setwin?.webContents?.openDevTools({ mode: 'detach' })
|
|
||||||
const isAutoListenClipboard = db.get(configPaths.settings.isAutoListenClipboard) || false
|
const isAutoListenClipboard = db.get(configPaths.settings.isAutoListenClipboard) || false
|
||||||
const ClipboardWatcher = clipboardPoll
|
const ClipboardWatcher = clipboardPoll
|
||||||
if (isAutoListenClipboard) {
|
if (isAutoListenClipboard) {
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ import type { IConfig } from 'piclist'
|
|||||||
import { onBeforeMount, onMounted } from 'vue'
|
import { onBeforeMount, onMounted } from 'vue'
|
||||||
|
|
||||||
import UIServiceProvider from '@/components/ui/UIServiceProvider.vue'
|
import UIServiceProvider from '@/components/ui/UIServiceProvider.vue'
|
||||||
|
import { useATagClick } from '@/hooks/useATagClick'
|
||||||
import { useStore } from '@/hooks/useStore'
|
import { useStore } from '@/hooks/useStore'
|
||||||
import { getConfig } from '@/utils/dataSender'
|
import { getConfig } from '@/utils/dataSender'
|
||||||
import { pageReloadCount } from '@/utils/global'
|
import { pageReloadCount } from '@/utils/global'
|
||||||
|
|
||||||
import { useAppStore } from './hooks/useAppStore'
|
import { useAppStore } from './hooks/useAppStore'
|
||||||
|
useATagClick()
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|||||||
@@ -1,257 +0,0 @@
|
|||||||
<!-- eslint-disable vue/no-v-html -->
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
id="config-form"
|
|
||||||
:class="props.colorMode === 'white' ? 'white' : ''"
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
ref="$form"
|
|
||||||
label-position="left"
|
|
||||||
label-width="50%"
|
|
||||||
:model="ruleForm"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<el-form-item
|
|
||||||
:label="$t('UPLOADER_CONFIG_NAME')"
|
|
||||||
required
|
|
||||||
prop="_configName"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
v-model="ruleForm._configName"
|
|
||||||
type="input"
|
|
||||||
:placeholder="$t('UPLOADER_CONFIG_PLACEHOLDER')"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<!-- dynamic config -->
|
|
||||||
<el-form-item
|
|
||||||
v-for="(item, index) in configList"
|
|
||||||
:key="item.name + index"
|
|
||||||
:required="item.required"
|
|
||||||
:prop="item.name"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
<el-row align="middle">
|
|
||||||
{{ item.alias || item.name }}
|
|
||||||
<template v-if="item.tips">
|
|
||||||
<el-tooltip
|
|
||||||
class="item"
|
|
||||||
effect="dark"
|
|
||||||
placement="right"
|
|
||||||
:persistent="false"
|
|
||||||
teleported
|
|
||||||
>
|
|
||||||
<template #content>
|
|
||||||
<span
|
|
||||||
class="config-form-common-tips"
|
|
||||||
v-html="transformMarkdownToHTML(item.tips)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<el-icon class="ml-[4px] cursor-pointer hover:text-blue">
|
|
||||||
<InfoFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</template>
|
|
||||||
</el-row>
|
|
||||||
</template>
|
|
||||||
<el-input
|
|
||||||
v-if="item.type === 'input' || item.type === 'password'"
|
|
||||||
v-model="ruleForm[item.name]"
|
|
||||||
type="input"
|
|
||||||
:placeholder="item.message || item.name"
|
|
||||||
/>
|
|
||||||
<el-select
|
|
||||||
v-else-if="item.type === 'list' && item.choices"
|
|
||||||
v-model="ruleForm[item.name]"
|
|
||||||
:placeholder="item.message || item.name"
|
|
||||||
:persistent="false"
|
|
||||||
teleported
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="choice in item.choices"
|
|
||||||
:key="choice.name || choice.value || choice"
|
|
||||||
:label="choice.name || choice.value || choice"
|
|
||||||
:value="choice.value || choice"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-select
|
|
||||||
v-else-if="item.type === 'checkbox' && item.choices"
|
|
||||||
v-model="ruleForm[item.name]"
|
|
||||||
:placeholder="item.message || item.name"
|
|
||||||
multiple
|
|
||||||
collapse-tags
|
|
||||||
:persistent="false"
|
|
||||||
teleported
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="choice in item.choices"
|
|
||||||
:key="choice.value || choice"
|
|
||||||
:label="choice.name || choice.value || choice"
|
|
||||||
:value="choice.value || choice"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-switch
|
|
||||||
v-else-if="item.type === 'confirm'"
|
|
||||||
v-model="ruleForm[item.name]"
|
|
||||||
:active-text="item.confirmText || 'yes'"
|
|
||||||
:inactive-text="item.cancelText || 'no'"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<slot />
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { InfoFilled } from '@element-plus/icons-vue'
|
|
||||||
import type { FormInstance } from 'element-plus'
|
|
||||||
import { cloneDeep, union } from 'lodash-es'
|
|
||||||
import { marked } from 'marked'
|
|
||||||
import { reactive, ref, toRefs, watch } from 'vue'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
|
|
||||||
import { getConfig } from '@/utils/dataSender'
|
|
||||||
import type { IPicGoPluginConfig, IStringKeyMap } from '#/types/types'
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function handleConfigChange (val: any) {
|
|
||||||
handleConfig(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validate (): Promise<IStringKeyMap | false> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
$form.value?.validate((valid: boolean) => {
|
|
||||||
if (valid) {
|
|
||||||
resolve(ruleForm)
|
|
||||||
} else {
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformMarkdownToHTML (markdown: string) {
|
|
||||||
try {
|
|
||||||
return marked.parse(markdown)
|
|
||||||
} catch (e) {
|
|
||||||
return markdown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfigType () {
|
|
||||||
switch (props.type) {
|
|
||||||
case 'plugin': {
|
|
||||||
return props.id
|
|
||||||
}
|
|
||||||
case 'uploader': {
|
|
||||||
return `picBed.${props.id}`
|
|
||||||
}
|
|
||||||
case 'transformer': {
|
|
||||||
return `transformer.${props.id}`
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleConfig (val: IPicGoPluginConfig[]) {
|
|
||||||
const config = await getCurConfigFormData()
|
|
||||||
const configId = $route.params.configId
|
|
||||||
Object.assign(ruleForm, config)
|
|
||||||
if (val.length > 0) {
|
|
||||||
configList.value = cloneDeep(val).map(item => {
|
|
||||||
if (!configId) return item
|
|
||||||
let defaultValue = item.default !== undefined ? item.default : item.type === 'checkbox' ? [] : null
|
|
||||||
if (item.type === 'checkbox') {
|
|
||||||
const defaults =
|
|
||||||
item.choices
|
|
||||||
?.filter((i: any) => {
|
|
||||||
return i.checked
|
|
||||||
})
|
|
||||||
.map((i: any) => i.value) || []
|
|
||||||
defaultValue = union(defaultValue, defaults)
|
|
||||||
}
|
|
||||||
if (config && config[item.name] !== undefined) {
|
|
||||||
defaultValue = config[item.name]
|
|
||||||
}
|
|
||||||
ruleForm[item.name] = defaultValue
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getCurConfigFormData () {
|
|
||||||
const configId = $route.params.configId
|
|
||||||
const curTypeConfigList = (await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`)) || []
|
|
||||||
return curTypeConfigList.find(i => i._id === configId) || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateRuleForm (key: string, value: any) {
|
|
||||||
try {
|
|
||||||
ruleForm[key] = value
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
updateRuleForm,
|
|
||||||
validate,
|
|
||||||
getConfigType
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<style lang="stylus">
|
|
||||||
.config-form-common-tips
|
|
||||||
a
|
|
||||||
color #409EFF
|
|
||||||
text-decoration none
|
|
||||||
#config-form
|
|
||||||
.el-form
|
|
||||||
label
|
|
||||||
line-height 22px
|
|
||||||
padding-bottom 0
|
|
||||||
&-item
|
|
||||||
display: flex
|
|
||||||
justify-content space-between
|
|
||||||
border-bottom 1px solid darken(#eee, 50%)
|
|
||||||
padding-bottom 16px
|
|
||||||
&:last-child
|
|
||||||
border-bottom none
|
|
||||||
&__content
|
|
||||||
justify-content flex-end
|
|
||||||
.el-button-group
|
|
||||||
width 100%
|
|
||||||
.el-button
|
|
||||||
width 50%
|
|
||||||
.el-radio-group
|
|
||||||
margin-left 25px
|
|
||||||
.el-switch__label
|
|
||||||
&.is-active
|
|
||||||
color #409EFF
|
|
||||||
&.white
|
|
||||||
.el-form-item
|
|
||||||
border-bottom 1px solid #ddd
|
|
||||||
</style>
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
id="config-form"
|
|
||||||
:class="props.colorMode === 'white' ? 'white' : ''"
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
ref="$form"
|
|
||||||
label-position="left"
|
|
||||||
label-width="50%"
|
|
||||||
:model="ruleForm"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<el-form-item
|
|
||||||
:label="$t('UPLOADER_CONFIG_NAME')"
|
|
||||||
required
|
|
||||||
prop="_configName"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
v-model="ruleForm._configName"
|
|
||||||
type="input"
|
|
||||||
:placeholder="$t('UPLOADER_CONFIG_PLACEHOLDER')"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<!-- dynamic config -->
|
|
||||||
<el-form-item
|
|
||||||
v-for="(item, index) in configList"
|
|
||||||
:key="item.name + index"
|
|
||||||
:label="item.alias || item.name"
|
|
||||||
:required="item.required"
|
|
||||||
:prop="item.name"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
v-if="item.type === 'input' || item.type === 'password'"
|
|
||||||
v-model="ruleForm[item.name]"
|
|
||||||
type="input"
|
|
||||||
:placeholder="item.message || item.name"
|
|
||||||
/>
|
|
||||||
<el-select
|
|
||||||
v-else-if="item.type === 'list' && item.choices"
|
|
||||||
v-model="ruleForm[item.name]"
|
|
||||||
:placeholder="item.message || item.name"
|
|
||||||
:persistent="false"
|
|
||||||
teleported
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="choice in item.choices"
|
|
||||||
:key="choice.name || choice.value || choice"
|
|
||||||
:label="choice.name || choice.value || choice"
|
|
||||||
:value="choice.value || choice"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-select
|
|
||||||
v-else-if="item.type === 'checkbox' && item.choices"
|
|
||||||
v-model="ruleForm[item.name]"
|
|
||||||
:placeholder="item.message || item.name"
|
|
||||||
multiple
|
|
||||||
collapse-tags
|
|
||||||
:persistent="false"
|
|
||||||
teleported
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="choice in item.choices"
|
|
||||||
:key="choice.value || choice"
|
|
||||||
:label="choice.name || choice.value || choice"
|
|
||||||
:value="choice.value || choice"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-switch
|
|
||||||
v-else-if="item.type === 'confirm'"
|
|
||||||
v-model="ruleForm[item.name]"
|
|
||||||
active-text="yes"
|
|
||||||
inactive-text="no"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<slot />
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { FormInstance } from 'element-plus'
|
|
||||||
import { cloneDeep, union } from 'lodash-es'
|
|
||||||
import { reactive, ref, watch } from 'vue'
|
|
||||||
|
|
||||||
import { getConfig } from '@/utils/dataSender'
|
|
||||||
import type { IPicGoPluginConfig, IStringKeyMap } from '#/types/types'
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
config: any[]
|
|
||||||
type: 'uploader' | 'transformer' | 'plugin'
|
|
||||||
id: string
|
|
||||||
colorMode?: 'white' | 'dark'
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<IProps>()
|
|
||||||
const $form = ref<FormInstance>()
|
|
||||||
|
|
||||||
const configList = ref<IPicGoPluginConfig[]>([])
|
|
||||||
const ruleForm = reactive<IStringKeyMap>({})
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.config,
|
|
||||||
(val: IPicGoPluginConfig[]) => {
|
|
||||||
handleConfigChange(val)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function handleConfigChange (val: any) {
|
|
||||||
handleConfig(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validate (): Promise<IStringKeyMap | false> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
$form.value?.validate((valid: boolean) => {
|
|
||||||
if (valid) {
|
|
||||||
resolve(ruleForm)
|
|
||||||
} else {
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfigType () {
|
|
||||||
switch (props.type) {
|
|
||||||
case 'plugin': {
|
|
||||||
return props.id
|
|
||||||
}
|
|
||||||
case 'uploader': {
|
|
||||||
return `picBed.${props.id}`
|
|
||||||
}
|
|
||||||
case 'transformer': {
|
|
||||||
return `transformer.${props.id}`
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleConfig (val: IPicGoPluginConfig[]) {
|
|
||||||
const config = await getCurConfigFormData()
|
|
||||||
Object.assign(ruleForm, config)
|
|
||||||
if (val.length > 0) {
|
|
||||||
configList.value = cloneDeep(val).map(item => {
|
|
||||||
let defaultValue = item.default !== undefined ? item.default : item.type === 'checkbox' ? [] : null
|
|
||||||
if (item.type === 'checkbox') {
|
|
||||||
const defaults =
|
|
||||||
item.choices
|
|
||||||
?.filter((i: any) => {
|
|
||||||
return i.checked
|
|
||||||
})
|
|
||||||
.map((i: any) => i.value) || []
|
|
||||||
defaultValue = union(defaultValue, defaults)
|
|
||||||
}
|
|
||||||
if (config && config[item.name] !== undefined) {
|
|
||||||
defaultValue = config[item.name]
|
|
||||||
}
|
|
||||||
ruleForm[item.name] = defaultValue
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getCurConfigFormData () {
|
|
||||||
return (await getConfig<IStringKeyMap>(`${props.id}`)) || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateRuleForm (key: string, value: any) {
|
|
||||||
try {
|
|
||||||
ruleForm[key] = value
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
updateRuleForm,
|
|
||||||
validate,
|
|
||||||
getConfigType
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<style lang="stylus">
|
|
||||||
#config-form
|
|
||||||
.el-form
|
|
||||||
label
|
|
||||||
line-height 22px
|
|
||||||
padding-bottom 0
|
|
||||||
&-item
|
|
||||||
display: flex
|
|
||||||
justify-content space-between
|
|
||||||
border-bottom 1px solid darken(#eee, 50%)
|
|
||||||
padding-bottom 16px
|
|
||||||
&:last-child
|
|
||||||
border-bottom none
|
|
||||||
&__content
|
|
||||||
justify-content flex-end
|
|
||||||
.el-button-group
|
|
||||||
width 100%
|
|
||||||
.el-button
|
|
||||||
width 50%
|
|
||||||
.el-radio-group
|
|
||||||
margin-left 25px
|
|
||||||
.el-switch__label
|
|
||||||
&.is-active
|
|
||||||
color #409EFF
|
|
||||||
&.white
|
|
||||||
.el-form-item
|
|
||||||
border-bottom 1px solid #ddd
|
|
||||||
</style>
|
|
||||||
@@ -1,40 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<Teleport to="body">
|
||||||
v-model="showInputBoxVisible"
|
<div
|
||||||
:title="inputBoxOptions.title || $t('INPUT')"
|
v-if="showInputBoxVisible"
|
||||||
:modal-append-to-body="false"
|
class="inputbox-overlay"
|
||||||
append-to-body
|
@click="handleInputBoxCancel"
|
||||||
>
|
>
|
||||||
<el-input
|
<div
|
||||||
v-model="inputBoxValue"
|
class="inputbox-container"
|
||||||
:placeholder="inputBoxOptions.placeholder"
|
@click.stop
|
||||||
/>
|
|
||||||
<template #footer>
|
|
||||||
<el-button
|
|
||||||
round
|
|
||||||
@click="handleInputBoxCancel"
|
|
||||||
>
|
>
|
||||||
{{ $t('CANCEL') }}
|
<div class="inputbox-header">
|
||||||
</el-button>
|
<h3 class="inputbox-title">
|
||||||
<el-button
|
{{ inputBoxOptions.title || t('pages.inputBox.title') }}
|
||||||
type="primary"
|
</h3>
|
||||||
round
|
<button
|
||||||
@click="handleInputBoxConfirm"
|
class="inputbox-close"
|
||||||
>
|
@click="handleInputBoxCancel"
|
||||||
{{ $t('CONFIRM') }}
|
>
|
||||||
</el-button>
|
<X :size="20" />
|
||||||
</template>
|
</button>
|
||||||
</el-dialog>
|
</div>
|
||||||
|
<div class="inputbox-content">
|
||||||
|
<input
|
||||||
|
v-model="inputBoxValue"
|
||||||
|
:placeholder="inputBoxOptions.placeholder"
|
||||||
|
class="inputbox-input"
|
||||||
|
type="text"
|
||||||
|
@keyup.enter="handleInputBoxConfirm"
|
||||||
|
@keyup.escape="handleInputBoxCancel"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="inputbox-actions">
|
||||||
|
<button
|
||||||
|
class="inputbox-btn cancel-btn"
|
||||||
|
@click="handleInputBoxCancel"
|
||||||
|
>
|
||||||
|
{{ t('common.cancel') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="inputbox-btn confirm-btn primary"
|
||||||
|
@click="handleInputBoxConfirm"
|
||||||
|
>
|
||||||
|
{{ t('common.confirm') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { IpcRendererEvent } from 'electron'
|
import type { IpcRendererEvent } from 'electron'
|
||||||
|
import { X } from 'lucide-vue-next'
|
||||||
import { onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue'
|
import { onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import $bus from '@/utils/bus'
|
import $bus from '@/utils/bus'
|
||||||
import { SHOW_INPUT_BOX, SHOW_INPUT_BOX_RESPONSE } from '@/utils/constant'
|
import { SHOW_INPUT_BOX, SHOW_INPUT_BOX_RESPONSE } from '@/utils/constant'
|
||||||
import type { IShowInputBoxOption } from '#/types/types'
|
import type { IShowInputBoxOption } from '#/types/types'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const inputBoxValue = ref('')
|
const inputBoxValue = ref('')
|
||||||
const showInputBoxVisible = ref(false)
|
const showInputBoxVisible = ref(false)
|
||||||
const inputBoxOptions = reactive({
|
const inputBoxOptions = reactive({
|
||||||
@@ -81,4 +106,174 @@ export default {
|
|||||||
name: 'InputBoxDialog'
|
name: 'InputBoxDialog'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="stylus"></style>
|
<style scoped>
|
||||||
|
.inputbox-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
max-width: 32rem;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .inputbox-container,
|
||||||
|
:root.auto.dark .inputbox-container {
|
||||||
|
background: rgb(31 41 55);
|
||||||
|
border: 1px solid rgb(55 65 81);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-header {
|
||||||
|
padding: 1.5rem 1.5rem 0 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgb(17 24 39);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .inputbox-title,
|
||||||
|
:root.auto.dark .inputbox-title {
|
||||||
|
color: rgb(243 244 246);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: rgb(107 114 128);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-close:hover {
|
||||||
|
background: rgb(243 244 246);
|
||||||
|
color: rgb(17 24 39);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .inputbox-close,
|
||||||
|
:root.auto.dark .inputbox-close {
|
||||||
|
color: rgb(156 163 175);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .inputbox-close:hover,
|
||||||
|
:root.auto.dark .inputbox-close:hover {
|
||||||
|
background: rgb(55 65 81);
|
||||||
|
color: rgb(243 244 246);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-content {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 1px solid rgb(209 213 219);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: white;
|
||||||
|
color: rgb(17 24 39);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-input:focus {
|
||||||
|
border-color: rgb(59 130 246);
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-input::placeholder {
|
||||||
|
color: rgb(156 163 175);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .inputbox-input,
|
||||||
|
:root.auto.dark .inputbox-input {
|
||||||
|
background: rgb(55 65 81);
|
||||||
|
border-color: rgb(75 85 99);
|
||||||
|
color: rgb(243 244 246);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .inputbox-input:focus,
|
||||||
|
:root.auto.dark .inputbox-input:focus {
|
||||||
|
border-color: rgb(59 130 246);
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .inputbox-input::placeholder,
|
||||||
|
:root.auto.dark .inputbox-input::placeholder {
|
||||||
|
color: rgb(107 114 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0 1.5rem 1.5rem 1.5rem;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputbox-btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background: rgb(243 244 246);
|
||||||
|
color: rgb(75 85 99);
|
||||||
|
border: 1px solid rgb(209 213 219);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background: rgb(229 231 235);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .cancel-btn,
|
||||||
|
:root.auto.dark .cancel-btn {
|
||||||
|
background: rgb(55 65 81);
|
||||||
|
color: rgb(209 213 219);
|
||||||
|
border-color: rgb(75 85 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .cancel-btn:hover,
|
||||||
|
:root.auto.dark .cancel-btn:hover {
|
||||||
|
background: rgb(75 85 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn.primary {
|
||||||
|
background: rgb(59 130 246);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn.primary:hover {
|
||||||
|
background: rgb(37 99 235);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
:title="$t('navigation.moreOptions')"
|
:title="$t('navigation.moreOptions')"
|
||||||
@click="openMenu"
|
@click="openMenu"
|
||||||
>
|
>
|
||||||
<BadgeInfoIcon :size="20" />
|
<Info :size="20" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -215,7 +215,7 @@ import {
|
|||||||
} from '@headlessui/vue'
|
} from '@headlessui/vue'
|
||||||
import { ElMessage as $message } from 'element-plus'
|
import { ElMessage as $message } from 'element-plus'
|
||||||
import { pick } from 'lodash-es'
|
import { pick } from 'lodash-es'
|
||||||
import { BadgeInfoIcon, CheckIcon, ChevronDownIcon, CopyIcon, DatabaseIcon, FolderIcon, PieChartIcon, PlugIcon, Settings, UploadIcon } from 'lucide-vue-next'
|
import { CheckIcon, ChevronDownIcon, CopyIcon, DatabaseIcon, FolderIcon, Info, PieChartIcon, PlugIcon, Settings, UploadIcon } from 'lucide-vue-next'
|
||||||
import QrcodeVue from 'qrcode.vue'
|
import QrcodeVue from 'qrcode.vue'
|
||||||
import pkg from 'root/package.json'
|
import pkg from 'root/package.json'
|
||||||
import { computed, nextTick, onBeforeMount, reactive, Ref, ref, watch } from 'vue'
|
import { computed, nextTick, onBeforeMount, reactive, Ref, ref, watch } from 'vue'
|
||||||
|
|||||||
775
src/renderer/components/UnifiedConfigForm.vue
Normal file
775
src/renderer/components/UnifiedConfigForm.vue
Normal file
@@ -0,0 +1,775 @@
|
|||||||
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
id="config-form"
|
||||||
|
:class="[{ white: props.colorMode === 'white' }]"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
ref="$form"
|
||||||
|
class="config-form"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<!-- Config Name Field -->
|
||||||
|
<div class="form-group required">
|
||||||
|
<label class="form-label">{{ t('pages.configForm.configName') }}</label>
|
||||||
|
<div class="form-control">
|
||||||
|
<input
|
||||||
|
v-model="ruleForm._configName"
|
||||||
|
type="text"
|
||||||
|
class="form-input"
|
||||||
|
:placeholder="t('pages.configForm.configNamePlaceholder')"
|
||||||
|
:class="{ error: validationErrors._configName }"
|
||||||
|
@input="clearFieldError('_configName')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="validationErrors._configName"
|
||||||
|
class="error-message"
|
||||||
|
>
|
||||||
|
{{ validationErrors._configName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dynamic Config Fields -->
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in configList"
|
||||||
|
:key="item.name + index"
|
||||||
|
class="form-group"
|
||||||
|
:class="{ required: item.required }"
|
||||||
|
>
|
||||||
|
<div class="form-label-wrapper">
|
||||||
|
<label class="form-label">{{ item.alias || item.name }}</label>
|
||||||
|
<div
|
||||||
|
v-if="showTooltips && item.tips"
|
||||||
|
class="tooltip-wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="info-icon"
|
||||||
|
@click="toggleTooltip(item.name + index)"
|
||||||
|
>
|
||||||
|
<Info :size="20" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="visibleTooltips[item.name + index]"
|
||||||
|
class="tooltip-content"
|
||||||
|
v-html="transformMarkdownToHTML(item.tips)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control">
|
||||||
|
<!-- Text/Password Input -->
|
||||||
|
<input
|
||||||
|
v-if="item.type === 'input' || item.type === 'password'"
|
||||||
|
v-model="ruleForm[item.name]"
|
||||||
|
type="text"
|
||||||
|
class="form-input"
|
||||||
|
:placeholder="item.message || item.name"
|
||||||
|
:class="{ error: validationErrors[item.name] }"
|
||||||
|
@input="clearFieldError(item.name)"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- Select (Single) -->
|
||||||
|
<div
|
||||||
|
v-else-if="item.type === 'list' && item.choices"
|
||||||
|
class="select-wrapper"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="ruleForm[item.name]"
|
||||||
|
class="form-select"
|
||||||
|
:class="{ error: validationErrors[item.name] }"
|
||||||
|
@change="clearFieldError(item.name)"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value=""
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
{{ item.message || item.name }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="choice in item.choices"
|
||||||
|
:key="choice.name || choice.value || choice"
|
||||||
|
:value="choice.value || choice"
|
||||||
|
>
|
||||||
|
{{ choice.name || choice.value || choice }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="select-arrow">
|
||||||
|
<ChevronDownIcon :size="20" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Multi-Select (Checkbox style) -->
|
||||||
|
<div
|
||||||
|
v-else-if="item.type === 'checkbox' && item.choices"
|
||||||
|
class="checkbox-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="choice in item.choices"
|
||||||
|
:key="choice.value || choice"
|
||||||
|
class="checkbox-item"
|
||||||
|
>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:value="choice.value || choice"
|
||||||
|
:checked="Array.isArray(ruleForm[item.name]) && ruleForm[item.name].includes(choice.value || choice)"
|
||||||
|
class="checkbox-input"
|
||||||
|
@change="handleCheckboxChange(item.name, choice.value || choice, $event)"
|
||||||
|
>
|
||||||
|
<span class="checkbox-custom" />
|
||||||
|
<span class="checkbox-text">{{ choice.name || choice.value || choice }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Switch/Toggle -->
|
||||||
|
<label
|
||||||
|
v-else-if="item.type === 'confirm'"
|
||||||
|
class="switch-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="ruleForm[item.name]"
|
||||||
|
type="checkbox"
|
||||||
|
class="switch-input"
|
||||||
|
@change="clearFieldError(item.name)"
|
||||||
|
>
|
||||||
|
<span class="switch-slider">
|
||||||
|
<span class="switch-button" />
|
||||||
|
</span>
|
||||||
|
<span class="switch-text">
|
||||||
|
{{ ruleForm[item.name] ? (item.confirmText || 'Yes') : (item.cancelText || 'No') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Validation Error -->
|
||||||
|
<div
|
||||||
|
v-if="validationErrors[item.name]"
|
||||||
|
class="error-message"
|
||||||
|
>
|
||||||
|
{{ validationErrors[item.name] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { cloneDeep, union } from 'lodash-es'
|
||||||
|
import { ChevronDownIcon, Info } from 'lucide-vue-next'
|
||||||
|
import { marked } from 'marked'
|
||||||
|
import { reactive, ref, toRefs, watch } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
import { getConfig } from '@/utils/dataSender'
|
||||||
|
import type { IPicGoPluginConfig, IStringKeyMap } from '#/types/types'
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
config: any[]
|
||||||
|
type: 'uploader' | 'transformer' | 'plugin'
|
||||||
|
id: string
|
||||||
|
colorMode?: 'white' | 'dark'
|
||||||
|
mode?: 'picbed' | 'plugin'
|
||||||
|
showTooltips?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
|
colorMode: undefined,
|
||||||
|
mode: 'picbed',
|
||||||
|
showTooltips: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const $route = useRoute()
|
||||||
|
const $form = ref<HTMLFormElement>()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const configList = ref<IPicGoPluginConfig[]>([])
|
||||||
|
const ruleForm = reactive<IStringKeyMap>({})
|
||||||
|
const validationErrors = reactive<IStringKeyMap>({})
|
||||||
|
const visibleTooltips = reactive<{ [key: string]: boolean }>({})
|
||||||
|
|
||||||
|
// Watch for config changes
|
||||||
|
watch(
|
||||||
|
toRefs(props.config),
|
||||||
|
(val: IPicGoPluginConfig[]) => {
|
||||||
|
handleConfigChange(val)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function handleConfigChange (val: any) {
|
||||||
|
handleConfig(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateField (fieldName: string, value: any, config?: IPicGoPluginConfig): string | null {
|
||||||
|
if (fieldName === '_configName') {
|
||||||
|
if (!value || value.trim() === '') {
|
||||||
|
return 'Configuration name is required'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config?.required && (!value || (Array.isArray(value) && value.length === 0))) {
|
||||||
|
return `${config.alias || config.name} is required`
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateForm (): boolean {
|
||||||
|
const errors: IStringKeyMap = {}
|
||||||
|
|
||||||
|
const configNameError = validateField('_configName', ruleForm._configName)
|
||||||
|
if (configNameError) {
|
||||||
|
errors._configName = configNameError
|
||||||
|
}
|
||||||
|
|
||||||
|
configList.value.forEach(config => {
|
||||||
|
const error = validateField(config.name, ruleForm[config.name], config)
|
||||||
|
if (error) {
|
||||||
|
errors[config.name] = error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.keys(validationErrors).forEach(key => {
|
||||||
|
delete validationErrors[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.assign(validationErrors, errors)
|
||||||
|
|
||||||
|
return Object.keys(errors).length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFieldError (fieldName: string) {
|
||||||
|
if (validationErrors[fieldName]) {
|
||||||
|
delete validationErrors[fieldName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTooltip (key: string) {
|
||||||
|
visibleTooltips[key] = !visibleTooltips[key]
|
||||||
|
|
||||||
|
Object.keys(visibleTooltips).forEach(otherKey => {
|
||||||
|
if (otherKey !== key) {
|
||||||
|
visibleTooltips[otherKey] = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheckboxChange (fieldName: string, value: any, event: Event) {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
const currentValues = Array.isArray(ruleForm[fieldName]) ? [...ruleForm[fieldName]] : []
|
||||||
|
|
||||||
|
if (target.checked) {
|
||||||
|
if (!currentValues.includes(value)) {
|
||||||
|
currentValues.push(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const index = currentValues.indexOf(value)
|
||||||
|
if (index > -1) {
|
||||||
|
currentValues.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleForm[fieldName] = currentValues
|
||||||
|
clearFieldError(fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validate (): Promise<IStringKeyMap | false> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const isValid = validateForm()
|
||||||
|
if (isValid) {
|
||||||
|
resolve(ruleForm)
|
||||||
|
} else {
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformMarkdownToHTML (markdown: string) {
|
||||||
|
try {
|
||||||
|
return marked.parse(markdown)
|
||||||
|
} catch (e) {
|
||||||
|
return markdown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfigType () {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'plugin': {
|
||||||
|
return props.id
|
||||||
|
}
|
||||||
|
case 'uploader': {
|
||||||
|
return `picBed.${props.id}`
|
||||||
|
}
|
||||||
|
case 'transformer': {
|
||||||
|
return `transformer.${props.id}`
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleConfig (val: IPicGoPluginConfig[]) {
|
||||||
|
const config = await getCurConfigFormData()
|
||||||
|
const configId = props.mode === 'picbed' ? $route.params.configId : null
|
||||||
|
|
||||||
|
Object.assign(ruleForm, config)
|
||||||
|
|
||||||
|
if (val.length > 0) {
|
||||||
|
configList.value = cloneDeep(val).map(item => {
|
||||||
|
// For plugin mode, don't check configId
|
||||||
|
if (props.mode === 'plugin' || !configId) {
|
||||||
|
let defaultValue = item.default !== undefined ? item.default : item.type === 'checkbox' ? [] : null
|
||||||
|
|
||||||
|
if (item.type === 'checkbox') {
|
||||||
|
const defaults =
|
||||||
|
item.choices
|
||||||
|
?.filter((i: any) => i.checked)
|
||||||
|
.map((i: any) => i.value) || []
|
||||||
|
defaultValue = union(defaultValue, defaults)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config && config[item.name] !== undefined) {
|
||||||
|
defaultValue = config[item.name]
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleForm[item.name] = defaultValue
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultValue = item.default !== undefined ? item.default : item.type === 'checkbox' ? [] : null
|
||||||
|
|
||||||
|
if (item.type === 'checkbox') {
|
||||||
|
const defaults =
|
||||||
|
item.choices
|
||||||
|
?.filter((i: any) => i.checked)
|
||||||
|
.map((i: any) => i.value) || []
|
||||||
|
defaultValue = union(defaultValue, defaults)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config && config[item.name] !== undefined) {
|
||||||
|
defaultValue = config[item.name]
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleForm[item.name] = defaultValue
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCurConfigFormData () {
|
||||||
|
if (props.mode === 'plugin') {
|
||||||
|
return (await getConfig<IStringKeyMap>(`${props.id}`)) || {}
|
||||||
|
} else {
|
||||||
|
const configId = $route.params.configId
|
||||||
|
const curTypeConfigList = (await getConfig<IStringKeyMap[]>(`uploader.${props.id}.configList`)) || []
|
||||||
|
return curTypeConfigList.find(i => i._id === configId) || {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRuleForm (key: string, value: any) {
|
||||||
|
try {
|
||||||
|
ruleForm[key] = value
|
||||||
|
clearFieldError(key)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
updateRuleForm,
|
||||||
|
validate,
|
||||||
|
getConfigType
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#config-form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Groups */
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group.required .form-label::after {
|
||||||
|
content: ' *';
|
||||||
|
color: var(--color-error, #ef4444);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip Styles */
|
||||||
|
.tooltip-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon:hover {
|
||||||
|
color: var(--color-accent);
|
||||||
|
background: rgba(0, 122, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 300px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Styles */
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input::placeholder {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input.error {
|
||||||
|
border-color: var(--color-error, #ef4444);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input.error:focus {
|
||||||
|
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select Styles */
|
||||||
|
.select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 2.5rem 0.75rem 1rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select.error {
|
||||||
|
border-color: var(--color-error, #ef4444);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select.error:focus {
|
||||||
|
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper:hover .select-arrow,
|
||||||
|
.form-select:focus + .select-arrow {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox Group Styles */
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label:hover {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-custom {
|
||||||
|
position: relative;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-custom::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 3px;
|
||||||
|
top: 0px;
|
||||||
|
width: 6px;
|
||||||
|
height: 10px;
|
||||||
|
border: solid white;
|
||||||
|
border-width: 0 2px 2px 0;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
opacity: 0;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-input:checked + .checkbox-custom {
|
||||||
|
background: var(--color-accent);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-input:checked + .checkbox-custom::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-input:focus + .checkbox-custom {
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Switch Styles */
|
||||||
|
.switch-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-slider {
|
||||||
|
position: relative;
|
||||||
|
width: 3rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
background: var(--color-border);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-input:checked + .switch-slider {
|
||||||
|
background: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-input:checked + .switch-slider .switch-button {
|
||||||
|
transform: translateX(1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-input:focus + .switch-slider {
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-text {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-input:checked ~ .switch-text {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error Message */
|
||||||
|
.error-message {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-error, #ef4444);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* White theme adjustments */
|
||||||
|
.white .form-input,
|
||||||
|
.white .form-select {
|
||||||
|
background: white;
|
||||||
|
border-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white .form-input:focus,
|
||||||
|
.white .form-select:focus {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.white .checkbox-custom {
|
||||||
|
background: white;
|
||||||
|
border-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white .switch-slider {
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white .tooltip-content {
|
||||||
|
background: white;
|
||||||
|
border-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.config-form {
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input,
|
||||||
|
.form-select {
|
||||||
|
padding: 0.625rem 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
padding-right: 2.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-content {
|
||||||
|
min-width: 150px;
|
||||||
|
max-width: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode adjustments */
|
||||||
|
:root.dark .form-input,
|
||||||
|
:root.auto.dark .form-input,
|
||||||
|
:root.dark .form-select,
|
||||||
|
:root.auto.dark .form-select {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .checkbox-custom,
|
||||||
|
:root.auto.dark .checkbox-custom {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .switch-slider,
|
||||||
|
:root.auto.dark .switch-slider {
|
||||||
|
background: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .tooltip-content,
|
||||||
|
:root.auto.dark .tooltip-content {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles for accessibility */
|
||||||
|
.form-input:focus-visible,
|
||||||
|
.form-select:focus-visible,
|
||||||
|
.checkbox-input:focus-visible + .checkbox-custom,
|
||||||
|
.switch-input:focus-visible + .switch-slider,
|
||||||
|
.info-icon:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -144,16 +144,18 @@ export default {
|
|||||||
|
|
||||||
.message-toast {
|
.message-toast {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
max-width: 24rem;
|
max-width: 24rem;
|
||||||
|
min-width: 20rem;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px solid rgb(229 231 235);
|
border: 1px solid rgb(229 231 235);
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark .message-toast,
|
:root.dark .message-toast,
|
||||||
@@ -196,6 +198,7 @@ export default {
|
|||||||
|
|
||||||
.message-icon {
|
.message-icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
margin-top: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-content {
|
.message-content {
|
||||||
@@ -203,6 +206,11 @@ export default {
|
|||||||
color: rgb(75 85 99);
|
color: rgb(75 85 99);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
hyphens: auto;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark .message-content,
|
:root.dark .message-content,
|
||||||
@@ -220,6 +228,8 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-close:hover {
|
.message-close:hover {
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ export function useATagClick () {
|
|||||||
const handleATagClick = (e: MouseEvent) => {
|
const handleATagClick = (e: MouseEvent) => {
|
||||||
if (e.target instanceof HTMLAnchorElement) {
|
if (e.target instanceof HTMLAnchorElement) {
|
||||||
if (e.target.href) {
|
if (e.target.href) {
|
||||||
e.preventDefault()
|
// avoid opening localhost development URLs in external browser
|
||||||
window.electron.sendRPC(IRPCActionType.OPEN_URL, e.target.href)
|
if (!e.target.href.startsWith('http://localhost:3000')) {
|
||||||
|
e.preventDefault()
|
||||||
|
window.electron.sendRPC(IRPCActionType.OPEN_URL, e.target.href)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"app": { "title": "PicList" },
|
"app": { "title": "PicList" },
|
||||||
"titleBar": { "alwaysOnTop": "Always On Top", "close": "Close", "minimize": "Minimize", "miniWindow": "Mini Window" },
|
"titleBar": { "alwaysOnTop": "Always On Top", "close": "Close", "minimize": "Minimize", "miniWindow": "Mini Window" },
|
||||||
"common": { "confirm": "Confirm", "cancel": "Cancel", "close": "Close" },
|
"common": { "confirm": "Confirm", "cancel": "Cancel", "close": "Close", "reset": "Reset", "import": "Import" },
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"manage": "Manage",
|
"manage": "Manage",
|
||||||
@@ -220,6 +220,7 @@
|
|||||||
"enableAdvancedRname": "Enable Advanced Rename",
|
"enableAdvancedRname": "Enable Advanced Rename",
|
||||||
"advancedRnameFormat": "Advanced Rename Format",
|
"advancedRnameFormat": "Advanced Rename Format",
|
||||||
"availablePlaceholders": "Available Placeholders",
|
"availablePlaceholders": "Available Placeholders",
|
||||||
|
"copySuccess": "Copy Successful: {content}",
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"categoryTime": "Time Related",
|
"categoryTime": "Time Related",
|
||||||
"categoryHash": "Hash Related",
|
"categoryHash": "Hash Related",
|
||||||
@@ -320,6 +321,91 @@
|
|||||||
"hasNewVersion": "PicList has been updated, please click OK to restart and trigger the update",
|
"hasNewVersion": "PicList has been updated, please click OK to restart and trigger the update",
|
||||||
"networkError": "Network error, please check your network connection"
|
"networkError": "Network error, please check your network connection"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"plugin": {
|
||||||
|
"title": "Plugins",
|
||||||
|
"description": "PicList Plugin Management",
|
||||||
|
"importLocal": "Import Local Plugin",
|
||||||
|
"updateAll": "Update All Plugins",
|
||||||
|
"list": "Plugin List",
|
||||||
|
"searchPlaceholder": "Search for PicGo plugins on npm, or click the button above to view the excellent plugin list",
|
||||||
|
"needRestart": "Need Restart to Take Effect",
|
||||||
|
"restartApp": "Restart Application",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"install": "Install",
|
||||||
|
"installing": "Installing...",
|
||||||
|
"installed": "Installed",
|
||||||
|
"doingSomething": "Doing Something...",
|
||||||
|
"settings": "Settings",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"noPluginsFound": "No Plugins Found",
|
||||||
|
"tryDifferentSearch": "Try Different Search Keywords",
|
||||||
|
"NoPluginsInstalled": "No Plugins Installed",
|
||||||
|
"installPluginsToGetStarted": "Please install plugins to get started",
|
||||||
|
"browsePlugins": "Browse Plugins",
|
||||||
|
"configThing": "Config {c}",
|
||||||
|
"pluginList": "Plugin List",
|
||||||
|
"notGuiImplement": "This plugin does not have a GUI implementation, continue?",
|
||||||
|
"updateSuccess": "Update Success",
|
||||||
|
"setSuccess": "Set Success"
|
||||||
|
},
|
||||||
|
"inputBox": {
|
||||||
|
"title": "Input Box"
|
||||||
|
},
|
||||||
|
"configForm": {
|
||||||
|
"configName": "Config Name",
|
||||||
|
"configNamePlaceholder": "Please enter config name"
|
||||||
|
},
|
||||||
|
"rename": {
|
||||||
|
"placeholder": "Please enter new file name"
|
||||||
|
},
|
||||||
|
"shortKey": {
|
||||||
|
"title": "Shortcut Key",
|
||||||
|
"name": "Shortcut Key Name",
|
||||||
|
"bind": "Shortcut Key Binding",
|
||||||
|
"status": "Status",
|
||||||
|
"source": "Source",
|
||||||
|
"handle": "Action",
|
||||||
|
"noBinding": "Not Bound",
|
||||||
|
"enable": "Enable",
|
||||||
|
"disable": "Disable",
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"edit": "Edit",
|
||||||
|
"changeUpload": "Change Upload Shortcut",
|
||||||
|
"keyBinding": "Key Binding",
|
||||||
|
"pressKeys": "Press the keys to set the shortcut",
|
||||||
|
"pressHint": "Click the input box and press the keys you want to bind"
|
||||||
|
},
|
||||||
|
"picBedConfigs": {
|
||||||
|
"title": "Config",
|
||||||
|
"viewDoc": "View Document",
|
||||||
|
"copyAPI": "Copy API Address",
|
||||||
|
"noConfigOptions": "No Config Options",
|
||||||
|
"setSuccess": "Set Success",
|
||||||
|
"setFailedInfo": "Set Failed, Please Check If Config Options Are Correct",
|
||||||
|
"loadConfigFailed": "Load Config Failed, Please Check If Config File Is Correct",
|
||||||
|
"loadPicBedListFailed": "Load PicBed List Failed",
|
||||||
|
"importConfigSuccess": "Import Config Success",
|
||||||
|
"importConfigFailed": "Import Config Failed",
|
||||||
|
"resetSuccess": "Reset Success",
|
||||||
|
"resetFailed": "Reset Failed, Please Check If Config Options Are Correct",
|
||||||
|
"viewDocFailed": "View Document Failed",
|
||||||
|
"noConfigs": "No Configs",
|
||||||
|
"copyAPISucceed": "Copy API Address Succeed",
|
||||||
|
"copyAPIFailed": "Copy API Address Failed"
|
||||||
|
},
|
||||||
|
"uploaderConfig": {
|
||||||
|
"title": "PicBed Config",
|
||||||
|
"selected": "Selected",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"addNew": "Add New",
|
||||||
|
"setAsDefault": "Set as Default PicBed",
|
||||||
|
"setSuccess": "Set Success",
|
||||||
|
"deleteTitle": "Notification",
|
||||||
|
"deleteConfirm": "Are you sure you want to delete this PicBed config?",
|
||||||
|
"deleteSuccess": "Delete Success"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"OPEN_MAIN_WINDOW": "Open Main Window",
|
"OPEN_MAIN_WINDOW": "Open Main Window",
|
||||||
@@ -339,7 +425,6 @@
|
|||||||
"TOOLBOX_SUCCESS_TIPS": "Congratulations, no problems were found",
|
"TOOLBOX_SUCCESS_TIPS": "Congratulations, no problems were found",
|
||||||
"UPLOAD_VIEW_HINT": "Click to open picbeds settings",
|
"UPLOAD_VIEW_HINT": "Click to open picbeds settings",
|
||||||
"REFRESH": "Refresh",
|
"REFRESH": "Refresh",
|
||||||
"PLUGIN_SETTINGS": "Plugins",
|
|
||||||
"CHOOSE_PICBED": "Choose Picbed",
|
"CHOOSE_PICBED": "Choose Picbed",
|
||||||
"COPY_PICBED_CONFIG": "Copy Picbed Config",
|
"COPY_PICBED_CONFIG": "Copy Picbed Config",
|
||||||
"COPY_PICBED_CONFIG_SUCCEED": "Copy Picbed Config Succeed",
|
"COPY_PICBED_CONFIG_SUCCEED": "Copy Picbed Config Succeed",
|
||||||
@@ -396,14 +481,6 @@
|
|||||||
"WAIT_TO_UPLOAD": "Wait to Upload",
|
"WAIT_TO_UPLOAD": "Wait to Upload",
|
||||||
"ALREADY_UPLOAD": "Already Uploaded",
|
"ALREADY_UPLOAD": "Already Uploaded",
|
||||||
"TIPS_DRAG_VALID_PICTURE_OR_URL": "Drag valid picture or url to here",
|
"TIPS_DRAG_VALID_PICTURE_OR_URL": "Drag valid picture or url to here",
|
||||||
"PLUGIN_SEARCH_PLACEHOLDER": "Search picgo plugins on npm, or click the button to view the awesome plugins list",
|
|
||||||
"PLUGIN_INSTALL": "Install",
|
|
||||||
"PLUGIN_INSTALLING": "Installing...",
|
|
||||||
"PLUGIN_INSTALLED": "Installed",
|
|
||||||
"PLUGIN_DOING_SOMETHING": "Doing...",
|
|
||||||
"PLUGIN_LIST": "Plugin List",
|
|
||||||
"PLUGIN_IMPORT_LOCAL": "Import Local Plugins",
|
|
||||||
"PLUGIN_UPDATE_ALL": "Update All Plugins",
|
|
||||||
"TIPS_REMOVE_LINK": "This operation will remove the picture from the album, continue?",
|
"TIPS_REMOVE_LINK": "This operation will remove the picture from the album, continue?",
|
||||||
"TIPS_WILL_REMOVE_CHOOSED_IMAGES": "This operation will remove the picture from the album, continue?",
|
"TIPS_WILL_REMOVE_CHOOSED_IMAGES": "This operation will remove the picture from the album, continue?",
|
||||||
"TIPS_MUST_CONTAINS_URL": "Must contains $url or $fileName or $extName",
|
"TIPS_MUST_CONTAINS_URL": "Must contains $url or $fileName or $extName",
|
||||||
@@ -412,7 +489,6 @@
|
|||||||
"TIPS_PLEASE_CHOOSE_LOG_LEVEL": "Please choose log level",
|
"TIPS_PLEASE_CHOOSE_LOG_LEVEL": "Please choose log level",
|
||||||
"TIPS_SET_SUCCEED": "Set successfully",
|
"TIPS_SET_SUCCEED": "Set successfully",
|
||||||
"TIPS_RESET_SUCCEED": "Reset successfully",
|
"TIPS_RESET_SUCCEED": "Reset successfully",
|
||||||
"TIPS_PLUGIN_NOT_GUI_IMPLEMENT": "This plugin is not optimized for the GUI, continue?",
|
|
||||||
"MANAGE_SETTING_TITLE": "Manage Setting",
|
"MANAGE_SETTING_TITLE": "Manage Setting",
|
||||||
"MANAGE_SETTING_ISAUTOREFRESH_TITLE": "Auto refresh file list when entering new directory",
|
"MANAGE_SETTING_ISAUTOREFRESH_TITLE": "Auto refresh file list when entering new directory",
|
||||||
"MANAGE_SETTING_ISAUTOREFRESH_TIPS": "Only applies to non-paginated mode, data is cached to indexdb to speed up loading speed",
|
"MANAGE_SETTING_ISAUTOREFRESH_TIPS": "Only applies to non-paginated mode, data is cached to indexdb to speed up loading speed",
|
||||||
@@ -968,6 +1044,5 @@
|
|||||||
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_R": "Public Read",
|
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_R": "Public Read",
|
||||||
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PRIVATE": "Private",
|
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PRIVATE": "Private",
|
||||||
"MANAGE_NEW_BUCKET_S3PLIST_ACL_AUTHENTICATED_READ": "Authenticated Read",
|
"MANAGE_NEW_BUCKET_S3PLIST_ACL_AUTHENTICATED_READ": "Authenticated Read",
|
||||||
"PLUGIN_UPDATE_SUCCEED": "Plugin update succeed",
|
|
||||||
"TIPS_NOTICE": "Tips"
|
"TIPS_NOTICE": "Tips"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"app": { "title": "PicList" },
|
"app": { "title": "PicList" },
|
||||||
"titleBar": { "alwaysOnTop": "置顶", "close": "关闭", "minimize": "最小化", "miniWindow": "迷你窗口" },
|
"titleBar": { "alwaysOnTop": "置顶", "close": "关闭", "minimize": "最小化", "miniWindow": "迷你窗口" },
|
||||||
"common": { "confirm": "确认", "cancel": "取消", "close": "关闭" },
|
"common": { "confirm": "确认", "cancel": "取消", "close": "关闭", "reset": "重置", "import": "导入" },
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"upload": "上传",
|
"upload": "上传",
|
||||||
"manage": "管理",
|
"manage": "管理",
|
||||||
@@ -215,6 +215,7 @@
|
|||||||
"enableAdvancedRname": "开启高级重命名",
|
"enableAdvancedRname": "开启高级重命名",
|
||||||
"advancedRnameFormat": "高级重命名格式",
|
"advancedRnameFormat": "高级重命名格式",
|
||||||
"availablePlaceholders": "可用占位符",
|
"availablePlaceholders": "可用占位符",
|
||||||
|
"copySuccess": "复制成功: {content}",
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"categoryTime": "时间相关",
|
"categoryTime": "时间相关",
|
||||||
"categoryHash": "哈希相关",
|
"categoryHash": "哈希相关",
|
||||||
@@ -315,6 +316,91 @@
|
|||||||
"hasNewVersion": "PicList 更新啦,请点击确定重启触发更新",
|
"hasNewVersion": "PicList 更新啦,请点击确定重启触发更新",
|
||||||
"networkError": "网络错误,请检查网络连接"
|
"networkError": "网络错误,请检查网络连接"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"plugin": {
|
||||||
|
"title": "插件",
|
||||||
|
"description": "PicList 插件管理页面",
|
||||||
|
"importLocal": "导入本地插件",
|
||||||
|
"updateAll": "更新全部插件",
|
||||||
|
"list": "插件列表",
|
||||||
|
"searchPlaceholder": "搜索 npm 上的 PicGo 插件,或者点击上方按钮查看优秀插件列表",
|
||||||
|
"needRestart": "需要重启生效",
|
||||||
|
"restartApp": "重启应用",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"install": "安装",
|
||||||
|
"installing": "安装中",
|
||||||
|
"installed": "已安装",
|
||||||
|
"doingSomething": "进行中",
|
||||||
|
"settings": "设置",
|
||||||
|
"disabled": "已禁用",
|
||||||
|
"noPluginsFound": "未找到插件",
|
||||||
|
"tryDifferentSearch": "尝试不同的搜索关键词",
|
||||||
|
"NoPluginsInstalled": "暂无已安装插件",
|
||||||
|
"installPluginsToGetStarted": "请先安装插件以开始使用",
|
||||||
|
"browsePlugins": "浏览插件",
|
||||||
|
"configThing": "配置 {c}",
|
||||||
|
"pluginList": "插件列表",
|
||||||
|
"notGuiImplement": "该插件未对可视化界面进行优化, 是否继续安装?",
|
||||||
|
"updateSuccess": "插件更新成功",
|
||||||
|
"setSuccess": "设置成功"
|
||||||
|
},
|
||||||
|
"inputBox": {
|
||||||
|
"title": "输入框"
|
||||||
|
},
|
||||||
|
"configForm": {
|
||||||
|
"configName": "配置名",
|
||||||
|
"configNamePlaceholder": "请输入配置名称"
|
||||||
|
},
|
||||||
|
"rename": {
|
||||||
|
"placeholder": "请输入新的文件名"
|
||||||
|
},
|
||||||
|
"shortKey": {
|
||||||
|
"title": "快捷键",
|
||||||
|
"name": "快捷键名称",
|
||||||
|
"bind": "快捷键绑定",
|
||||||
|
"status": "状态",
|
||||||
|
"source": "来源",
|
||||||
|
"handle": "操作",
|
||||||
|
"noBinding": "未绑定",
|
||||||
|
"enable": "启用",
|
||||||
|
"disable": "禁用",
|
||||||
|
"enabled": "已启用",
|
||||||
|
"disabled": "已禁用",
|
||||||
|
"edit": "编辑",
|
||||||
|
"changeUpload": "修改上传快捷键",
|
||||||
|
"keyBinding": "按键绑定",
|
||||||
|
"pressKeys": "按下要设置的快捷键",
|
||||||
|
"pressHint": "点击输入框并按下你想要绑定的按键"
|
||||||
|
},
|
||||||
|
"picBedConfigs": {
|
||||||
|
"title": "配置",
|
||||||
|
"viewDoc": "查看文档",
|
||||||
|
"copyAPI": "复制API地址",
|
||||||
|
"noConfigOptions": "暂无配置项",
|
||||||
|
"setSuccess": "设置成功",
|
||||||
|
"setFailedInfo": "设置失败, 请检查配置项是否正确",
|
||||||
|
"loadConfigFailed": "加载配置失败, 请检查配置文件是否正确",
|
||||||
|
"loadPicBedListFailed": "加载图床列表失败",
|
||||||
|
"importConfigSuccess": "导入配置成功",
|
||||||
|
"importConfigFailed": "导入配置失败",
|
||||||
|
"resetSuccess": "重置成功",
|
||||||
|
"resetFailed": "重置失败, 请检查配置项是否正确",
|
||||||
|
"viewDocFailed": "查看文档失败",
|
||||||
|
"noConfigs": "配置为空",
|
||||||
|
"copyAPISucceed": "复制API地址成功",
|
||||||
|
"copyAPIFailed": "复制API地址失败"
|
||||||
|
},
|
||||||
|
"uploaderConfig": {
|
||||||
|
"title": "图床配置",
|
||||||
|
"selected": "已选中",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"addNew": "新增",
|
||||||
|
"setAsDefault": "设为默认图床",
|
||||||
|
"setSuccess": "设置成功",
|
||||||
|
"deleteTitle": "通知",
|
||||||
|
"deleteConfirm": "确认删除图床配置吗?",
|
||||||
|
"deleteSuccess": "删除成功"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"OPEN_MAIN_WINDOW": "打开主窗口",
|
"OPEN_MAIN_WINDOW": "打开主窗口",
|
||||||
@@ -334,7 +420,6 @@
|
|||||||
"TOOLBOX_SUCCESS_TIPS": "恭喜你,没有检查出问题",
|
"TOOLBOX_SUCCESS_TIPS": "恭喜你,没有检查出问题",
|
||||||
"UPLOAD_VIEW_HINT": "点击打开图床设置",
|
"UPLOAD_VIEW_HINT": "点击打开图床设置",
|
||||||
"REFRESH": "刷新",
|
"REFRESH": "刷新",
|
||||||
"PLUGIN_SETTINGS": "插件",
|
|
||||||
"CHOOSE_PICBED": "选择图床",
|
"CHOOSE_PICBED": "选择图床",
|
||||||
"COPY_PICBED_CONFIG": "复制图床配置",
|
"COPY_PICBED_CONFIG": "复制图床配置",
|
||||||
"COPY_PICBED_CONFIG_SUCCEED": "复制图床配置成功",
|
"COPY_PICBED_CONFIG_SUCCEED": "复制图床配置成功",
|
||||||
@@ -391,14 +476,6 @@
|
|||||||
"WAIT_TO_UPLOAD": "等待上传",
|
"WAIT_TO_UPLOAD": "等待上传",
|
||||||
"ALREADY_UPLOAD": "已上传",
|
"ALREADY_UPLOAD": "已上传",
|
||||||
"TIPS_DRAG_VALID_PICTURE_OR_URL": "请拖入合法的图片文件或者图片URL地址",
|
"TIPS_DRAG_VALID_PICTURE_OR_URL": "请拖入合法的图片文件或者图片URL地址",
|
||||||
"PLUGIN_SEARCH_PLACEHOLDER": "搜索npm上的PicGo插件,或者点击上方按钮查看优秀插件列表",
|
|
||||||
"PLUGIN_INSTALL": "安装",
|
|
||||||
"PLUGIN_INSTALLING": "安装中",
|
|
||||||
"PLUGIN_INSTALLED": "已安装",
|
|
||||||
"PLUGIN_DOING_SOMETHING": "进行中",
|
|
||||||
"PLUGIN_LIST": "插件列表",
|
|
||||||
"PLUGIN_IMPORT_LOCAL": "导入本地插件",
|
|
||||||
"PLUGIN_UPDATE_ALL": "更新全部插件",
|
|
||||||
"TIPS_REMOVE_LINK": "此操作将把该图片移出相册, 是否继续?",
|
"TIPS_REMOVE_LINK": "此操作将把该图片移出相册, 是否继续?",
|
||||||
"TIPS_WILL_REMOVE_CHOOSED_IMAGES": "将在相册中移除刚才选中的 ${m} 张图片,是否继续?",
|
"TIPS_WILL_REMOVE_CHOOSED_IMAGES": "将在相册中移除刚才选中的 ${m} 张图片,是否继续?",
|
||||||
"TIPS_MUST_CONTAINS_URL": "必须含有$url 或 $fileName 或 $extName",
|
"TIPS_MUST_CONTAINS_URL": "必须含有$url 或 $fileName 或 $extName",
|
||||||
@@ -407,7 +484,6 @@
|
|||||||
"TIPS_PLEASE_CHOOSE_LOG_LEVEL": "请选择日志记录等级",
|
"TIPS_PLEASE_CHOOSE_LOG_LEVEL": "请选择日志记录等级",
|
||||||
"TIPS_SET_SUCCEED": "设置成功",
|
"TIPS_SET_SUCCEED": "设置成功",
|
||||||
"TIPS_RESET_SUCCEED": "重置成功",
|
"TIPS_RESET_SUCCEED": "重置成功",
|
||||||
"TIPS_PLUGIN_NOT_GUI_IMPLEMENT": "该插件未对可视化界面进行优化, 是否继续安装?",
|
|
||||||
"MANAGE_SETTING_TITLE": "管理页面设置",
|
"MANAGE_SETTING_TITLE": "管理页面设置",
|
||||||
"MANAGE_SETTING_ISAUTOREFRESH_TITLE": "每次进入新目录时,是否自动刷新文件列表",
|
"MANAGE_SETTING_ISAUTOREFRESH_TITLE": "每次进入新目录时,是否自动刷新文件列表",
|
||||||
"MANAGE_SETTING_ISAUTOREFRESH_TIPS": "仅对不分页模式有效,默认在加载过一次后自动缓存到数据库来加快下次加载速度",
|
"MANAGE_SETTING_ISAUTOREFRESH_TIPS": "仅对不分页模式有效,默认在加载过一次后自动缓存到数据库来加快下次加载速度",
|
||||||
@@ -963,6 +1039,5 @@
|
|||||||
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_R": "公共读",
|
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_R": "公共读",
|
||||||
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PRIVATE": "私有",
|
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PRIVATE": "私有",
|
||||||
"MANAGE_NEW_BUCKET_S3PLIST_ACL_AUTHENTICATED_READ": "授权读",
|
"MANAGE_NEW_BUCKET_S3PLIST_ACL_AUTHENTICATED_READ": "授权读",
|
||||||
"PLUGIN_UPDATE_SUCCEED": "插件更新成功",
|
|
||||||
"TIPS_NOTICE": "注意"
|
"TIPS_NOTICE": "注意"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"app": { "title": "PicList" },
|
"app": { "title": "PicList" },
|
||||||
"titleBar": { "alwaysOnTop": "置頂", "close": "關閉", "minimize": "最小化", "miniWindow": "迷你視窗" },
|
"titleBar": { "alwaysOnTop": "置頂", "close": "關閉", "minimize": "最小化", "miniWindow": "迷你視窗" },
|
||||||
"common": { "confirm": "確認", "cancel": "取消", "close": "關閉" },
|
"common": { "confirm": "確認", "cancel": "取消", "close": "關閉", "reset": "重置", "import": "匯入" },
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"upload": "上傳",
|
"upload": "上傳",
|
||||||
"manage": "管理",
|
"manage": "管理",
|
||||||
@@ -215,6 +215,7 @@
|
|||||||
"enableAdvancedRname": "開啟高級重命名",
|
"enableAdvancedRname": "開啟高級重命名",
|
||||||
"advancedRnameFormat": "高級重命名格式",
|
"advancedRnameFormat": "高級重命名格式",
|
||||||
"availablePlaceholders": "可用占位符",
|
"availablePlaceholders": "可用占位符",
|
||||||
|
"copySuccess": "複製成功: {content}",
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"categoryTime": "時間相關",
|
"categoryTime": "時間相關",
|
||||||
"categoryHash": "哈希相關",
|
"categoryHash": "哈希相關",
|
||||||
@@ -315,6 +316,91 @@
|
|||||||
"hasNewVersion": "PicList 更新啦,請點擊確定重啟觸發更新",
|
"hasNewVersion": "PicList 更新啦,請點擊確定重啟觸發更新",
|
||||||
"networkError": "網絡錯誤,請檢查網絡連接"
|
"networkError": "網絡錯誤,請檢查網絡連接"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"plugin": {
|
||||||
|
"title": "插件",
|
||||||
|
"description": "PicList 插件管理頁面",
|
||||||
|
"importLocal": "匯入本地插件",
|
||||||
|
"updateAll": "更新全部插件",
|
||||||
|
"list": "插件列表",
|
||||||
|
"searchPlaceholder": "搜尋 npm 上的 PicGo 插件,或者點擊上方按鈕查看優秀插件列表",
|
||||||
|
"needRestart": "需要重啟生效",
|
||||||
|
"restartApp": "重啟應用",
|
||||||
|
"loading": "載入中...",
|
||||||
|
"install": "安裝",
|
||||||
|
"installing": "安裝中",
|
||||||
|
"installed": "已安裝",
|
||||||
|
"doingSomething": "進行中",
|
||||||
|
"settings": "設定",
|
||||||
|
"disabled": "已停用",
|
||||||
|
"noPluginsFound": "未找到插件",
|
||||||
|
"tryDifferentSearch": "嘗試不同的搜尋關鍵詞",
|
||||||
|
"NoPluginsInstalled": "尚未安裝任何插件",
|
||||||
|
"installPluginsToGetStarted": "請先安裝插件以開始使用",
|
||||||
|
"browsePlugins": "瀏覽插件",
|
||||||
|
"configThing": "配置 {c}",
|
||||||
|
"pluginList": "插件列表",
|
||||||
|
"notGuiImplement": "該插件未針對圖形介面進行優化,是否繼續安裝?",
|
||||||
|
"updateSuccess": "插件更新成功",
|
||||||
|
"setSuccess": "設定成功"
|
||||||
|
},
|
||||||
|
"inputBox": {
|
||||||
|
"title": "輸入框"
|
||||||
|
},
|
||||||
|
"configForm": {
|
||||||
|
"configName": "配置名稱",
|
||||||
|
"configNamePlaceholder": "請輸入配置名稱"
|
||||||
|
},
|
||||||
|
"rename": {
|
||||||
|
"placeholder": "請輸入新的檔案名稱"
|
||||||
|
},
|
||||||
|
"shortKey": {
|
||||||
|
"title": "快捷鍵",
|
||||||
|
"name": "快捷鍵名稱",
|
||||||
|
"bind": "快捷鍵綁定",
|
||||||
|
"status": "狀態",
|
||||||
|
"source": "來源",
|
||||||
|
"handle": "操作",
|
||||||
|
"noBinding": "未綁定",
|
||||||
|
"enable": "啟用",
|
||||||
|
"disable": "停用",
|
||||||
|
"enabled": "已啟用",
|
||||||
|
"disabled": "已停用",
|
||||||
|
"edit": "編輯",
|
||||||
|
"changeUpload": "修改上傳快捷鍵",
|
||||||
|
"keyBinding": "按鍵綁定",
|
||||||
|
"pressKeys": "按下要設定的快捷鍵",
|
||||||
|
"pressHint": "點擊輸入框並按下你想要綁定的按鍵"
|
||||||
|
},
|
||||||
|
"picBedConfigs": {
|
||||||
|
"title": "配置",
|
||||||
|
"viewDoc": "查看文件",
|
||||||
|
"copyAPI": "複製 API 位址",
|
||||||
|
"noConfigOptions": "暫無配置項",
|
||||||
|
"setSuccess": "設定成功",
|
||||||
|
"setFailedInfo": "設定失敗,請檢查配置項是否正確",
|
||||||
|
"loadConfigFailed": "載入配置失敗,請檢查配置文件是否正確",
|
||||||
|
"loadPicBedListFailed": "載入圖床列表失敗",
|
||||||
|
"importConfigSuccess": "匯入配置成功",
|
||||||
|
"importConfigFailed": "匯入配置失敗",
|
||||||
|
"resetSuccess": "重設成功",
|
||||||
|
"resetFailed": "重設失敗,請檢查配置項是否正確",
|
||||||
|
"viewDocFailed": "查看文件失敗",
|
||||||
|
"noConfigs": "配置為空",
|
||||||
|
"copyAPISucceed": "複製 API 位址成功",
|
||||||
|
"copyAPIFailed": "複製 API 位址失敗"
|
||||||
|
},
|
||||||
|
"uploaderConfig": {
|
||||||
|
"title": "圖床配置",
|
||||||
|
"selected": "已選取",
|
||||||
|
"edit": "編輯",
|
||||||
|
"delete": "刪除",
|
||||||
|
"addNew": "新增",
|
||||||
|
"setAsDefault": "設為預設圖床",
|
||||||
|
"setSuccess": "設定成功",
|
||||||
|
"deleteTitle": "通知",
|
||||||
|
"deleteConfirm": "確認刪除圖床配置嗎?",
|
||||||
|
"deleteSuccess": "刪除成功"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"OPEN_MAIN_WINDOW": "打開主視窗",
|
"OPEN_MAIN_WINDOW": "打開主視窗",
|
||||||
@@ -334,7 +420,6 @@
|
|||||||
"TOOLBOX_SUCCESS_TIPS": "恭喜你,沒有檢查出問題",
|
"TOOLBOX_SUCCESS_TIPS": "恭喜你,沒有檢查出問題",
|
||||||
"UPLOAD_VIEW_HINT": "點擊打開圖床設定",
|
"UPLOAD_VIEW_HINT": "點擊打開圖床設定",
|
||||||
"REFRESH": "刷新",
|
"REFRESH": "刷新",
|
||||||
"PLUGIN_SETTINGS": "插件",
|
|
||||||
"CHOOSE_PICBED": "選擇圖床",
|
"CHOOSE_PICBED": "選擇圖床",
|
||||||
"COPY_PICBED_CONFIG": "複製圖床設定",
|
"COPY_PICBED_CONFIG": "複製圖床設定",
|
||||||
"COPY_PICBED_CONFIG_SUCCEED": "複製圖床設定成功",
|
"COPY_PICBED_CONFIG_SUCCEED": "複製圖床設定成功",
|
||||||
@@ -391,14 +476,6 @@
|
|||||||
"WAIT_TO_UPLOAD": "等待上傳",
|
"WAIT_TO_UPLOAD": "等待上傳",
|
||||||
"ALREADY_UPLOAD": "已上傳",
|
"ALREADY_UPLOAD": "已上傳",
|
||||||
"TIPS_DRAG_VALID_PICTURE_OR_URL": "請拖入合法的圖片檔案或者圖片URL地址",
|
"TIPS_DRAG_VALID_PICTURE_OR_URL": "請拖入合法的圖片檔案或者圖片URL地址",
|
||||||
"PLUGIN_SEARCH_PLACEHOLDER": "搜尋npm上的PicGo插件,或者點擊上方按鈕查看優秀插件列表",
|
|
||||||
"PLUGIN_INSTALL": "安裝",
|
|
||||||
"PLUGIN_INSTALLING": "安裝中",
|
|
||||||
"PLUGIN_INSTALLED": "已安裝",
|
|
||||||
"PLUGIN_DOING_SOMETHING": "進行中",
|
|
||||||
"PLUGIN_LIST": "插件列表",
|
|
||||||
"PLUGIN_IMPORT_LOCAL": "導入本地插件",
|
|
||||||
"PLUGIN_UPDATE_ALL": "更新全部插件",
|
|
||||||
"TIPS_REMOVE_LINK": "此操作將在相簿中移除該圖片,是否繼續?",
|
"TIPS_REMOVE_LINK": "此操作將在相簿中移除該圖片,是否繼續?",
|
||||||
"TIPS_WILL_REMOVE_CHOOSED_IMAGES": "將在相簿中移除剛才選中的 ${m} 張圖片,是否繼續?",
|
"TIPS_WILL_REMOVE_CHOOSED_IMAGES": "將在相簿中移除剛才選中的 ${m} 張圖片,是否繼續?",
|
||||||
"TIPS_MUST_CONTAINS_URL": "必須含有$url 或 $fileName 或 $extName",
|
"TIPS_MUST_CONTAINS_URL": "必須含有$url 或 $fileName 或 $extName",
|
||||||
@@ -407,7 +484,6 @@
|
|||||||
"TIPS_PLEASE_CHOOSE_LOG_LEVEL": "請選擇記錄等級",
|
"TIPS_PLEASE_CHOOSE_LOG_LEVEL": "請選擇記錄等級",
|
||||||
"TIPS_SET_SUCCEED": "設定成功",
|
"TIPS_SET_SUCCEED": "設定成功",
|
||||||
"TIPS_RESET_SUCCEED": "重置成功",
|
"TIPS_RESET_SUCCEED": "重置成功",
|
||||||
"TIPS_PLUGIN_NOT_GUI_IMPLEMENT": "該插件未對GUI進行優化,是否繼續安裝?",
|
|
||||||
"MANAGE_SETTING_TITLE": "管理設定",
|
"MANAGE_SETTING_TITLE": "管理設定",
|
||||||
"MANAGE_SETTING_ISAUTOREFRESH_TITLE": "每次進入新目錄時,是否自動重新整理檔案列表",
|
"MANAGE_SETTING_ISAUTOREFRESH_TITLE": "每次進入新目錄時,是否自動重新整理檔案列表",
|
||||||
"MANAGE_SETTING_ISAUTOREFRESH_TIPS": "僅對不分頁模式有效,預設會在載入後自動快取至資料庫以提升下次載入速度",
|
"MANAGE_SETTING_ISAUTOREFRESH_TIPS": "僅對不分頁模式有效,預設會在載入後自動快取至資料庫以提升下次載入速度",
|
||||||
@@ -963,6 +1039,5 @@
|
|||||||
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_R": "公共讀",
|
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PUBLIC_R": "公共讀",
|
||||||
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PRIVATE": "私有",
|
"MANAGE_NEW_BUCKET_S3PLIST_ACL_PRIVATE": "私有",
|
||||||
"MANAGE_NEW_BUCKET_S3PLIST_ACL_AUTHENTICATED_READ": "授權讀",
|
"MANAGE_NEW_BUCKET_S3PLIST_ACL_AUTHENTICATED_READ": "授權讀",
|
||||||
"PLUGIN_UPDATE_SUCCEED": "插件更新成功",
|
|
||||||
"TIPS_NOTICE": "注意"
|
"TIPS_NOTICE": "注意"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="main" class="app-container">
|
<div
|
||||||
|
id="main"
|
||||||
|
class="app-container"
|
||||||
|
>
|
||||||
|
<InputBoxDialog />
|
||||||
<TitleBar />
|
<TitleBar />
|
||||||
<div class="app-background">
|
<div class="app-background">
|
||||||
<div class="bg-gradient" />
|
<div class="bg-gradient" />
|
||||||
@@ -8,9 +12,15 @@
|
|||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<router-view v-slot="{ Component, route }">
|
<router-view v-slot="{ Component, route }">
|
||||||
<transition name="page" mode="out-in">
|
<transition
|
||||||
|
name="page"
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
<keep-alive :include="keepAlivePages">
|
<keep-alive :include="keepAlivePages">
|
||||||
<component :is="Component" :key="route.path" />
|
<component
|
||||||
|
:is="Component"
|
||||||
|
:key="route.path"
|
||||||
|
/>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
@@ -22,6 +32,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import InputBoxDialog from '@/components/InputBoxDialog.vue'
|
||||||
import Navigation from '@/components/NavigationPage.vue'
|
import Navigation from '@/components/NavigationPage.vue'
|
||||||
import TitleBar from '@/components/ui/TitleBar.vue'
|
import TitleBar from '@/components/ui/TitleBar.vue'
|
||||||
|
|
||||||
@@ -68,7 +79,7 @@ export default { name: 'MainPage' }
|
|||||||
--color-accent-hover: #0056b3;
|
--color-accent-hover: #0056b3;
|
||||||
--color-blue-common: #409eff;
|
--color-blue-common: #409eff;
|
||||||
--color-success: #34c759;
|
--color-success: #34c759;
|
||||||
--color-warning: #ff9500;
|
--color-warning: #f1930f;
|
||||||
--color-danger: #ff3b30;
|
--color-danger: #ff3b30;
|
||||||
|
|
||||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,245 +1,314 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="plugin-view">
|
<div class="plugin-container">
|
||||||
<div class="view-title">
|
<!-- Header Card -->
|
||||||
{{ $t('PLUGIN_SETTINGS') }} -
|
<div class="plugin-card header-card">
|
||||||
<el-tooltip
|
<div class="card-header">
|
||||||
:content="pluginListToolTip"
|
<div class="header-content">
|
||||||
placement="right"
|
<div class="header-icon">
|
||||||
:persistent="false"
|
<DatabaseIcon :size="24" />
|
||||||
teleported
|
|
||||||
>
|
|
||||||
<el-icon
|
|
||||||
class="el-icon-goods"
|
|
||||||
@click="goAwesomeList"
|
|
||||||
>
|
|
||||||
<Goods />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip
|
|
||||||
:content="updateAllToolTip"
|
|
||||||
placement="left"
|
|
||||||
:persistent="false"
|
|
||||||
teleported
|
|
||||||
>
|
|
||||||
<el-icon
|
|
||||||
class="el-icon-update"
|
|
||||||
@click="handleUpdateAllPlugin"
|
|
||||||
>
|
|
||||||
<Refresh />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip
|
|
||||||
:content="importLocalPluginToolTip"
|
|
||||||
placement="left"
|
|
||||||
>
|
|
||||||
<el-icon
|
|
||||||
class="el-icon-download"
|
|
||||||
:persistent="false"
|
|
||||||
teleported
|
|
||||||
@click="handleImportLocalPlugin"
|
|
||||||
>
|
|
||||||
<Download />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
<el-row
|
|
||||||
class="handle-bar"
|
|
||||||
:class="{ 'cut-width': pluginList.length > 6 }"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
v-model="searchText"
|
|
||||||
:placeholder="$t('PLUGIN_SEARCH_PLACEHOLDER')"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<template #suffix>
|
|
||||||
<el-icon
|
|
||||||
class="el-input__icon"
|
|
||||||
style="cursor: pointer"
|
|
||||||
@click="cleanSearch"
|
|
||||||
>
|
|
||||||
<close />
|
|
||||||
</el-icon>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-row>
|
|
||||||
<el-row
|
|
||||||
id="pluginList"
|
|
||||||
v-loading="loading"
|
|
||||||
:gutter="10"
|
|
||||||
class="plugin-list"
|
|
||||||
>
|
|
||||||
<el-col
|
|
||||||
v-for="item in pluginList"
|
|
||||||
:key="item.fullName"
|
|
||||||
class="plugin-item__container"
|
|
||||||
:xs="24"
|
|
||||||
:sm="pluginList.length === 1 ? 24 : 12"
|
|
||||||
:md="pluginList.length === 1 ? 24 : 12"
|
|
||||||
:lg="pluginList.length === 1 ? 24 : 12"
|
|
||||||
:xl="pluginList.length === 1 ? 24 : 12"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="plugin-item"
|
|
||||||
:class="{ darwin: osGlobal === 'darwin' }"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="!item.gui"
|
|
||||||
class="cli-only-badge"
|
|
||||||
title="CLI only"
|
|
||||||
>
|
|
||||||
CLI
|
|
||||||
</div>
|
</div>
|
||||||
<img
|
<div>
|
||||||
class="plugin-item__logo"
|
<h1>{{ t('pages.plugin.title') }}</h1>
|
||||||
:src="item.logo"
|
<p>{{ t('pages.plugin.description') }}</p>
|
||||||
:onerror="defaultLogo"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="plugin-item__content"
|
|
||||||
:class="{ disabled: !item.enabled }"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="plugin-item__name"
|
|
||||||
@click="openHomepage(item.homepage)"
|
|
||||||
>
|
|
||||||
{{ item.name }} <small>{{ ' ' + item.version }}</small>
|
|
||||||
<!-- 升级提示 -->
|
|
||||||
<el-tag
|
|
||||||
v-if="latestVersionMap[item.fullName] && latestVersionMap[item.fullName] !== item.version"
|
|
||||||
type="success"
|
|
||||||
size="small"
|
|
||||||
round
|
|
||||||
effect="plain"
|
|
||||||
>
|
|
||||||
new
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="plugin-item__desc"
|
|
||||||
:title="item.description"
|
|
||||||
>
|
|
||||||
{{ item.description }}
|
|
||||||
</div>
|
|
||||||
<div class="plugin-item__info-bar">
|
|
||||||
<span class="plugin-item__author">
|
|
||||||
{{ item.author.replace(/<.*>/, '') }}
|
|
||||||
</span>
|
|
||||||
<span class="plugin-item__config">
|
|
||||||
<template v-if="searchText">
|
|
||||||
<template v-if="!item.hasInstall">
|
|
||||||
<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"
|
|
||||||
>
|
|
||||||
{{ $t('PLUGIN_INSTALLING') }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="config-button ing"
|
|
||||||
>
|
|
||||||
{{ $t('PLUGIN_INSTALLED') }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span
|
|
||||||
v-if="item.ing"
|
|
||||||
class="config-button ing"
|
|
||||||
>
|
|
||||||
{{ $t('PLUGIN_DOING_SOMETHING') }}
|
|
||||||
</span>
|
|
||||||
<template v-else>
|
|
||||||
<el-icon
|
|
||||||
v-if="item.enabled"
|
|
||||||
class="el-icon-setting"
|
|
||||||
@click="buildContextMenu(item)"
|
|
||||||
>
|
|
||||||
<Tools />
|
|
||||||
</el-icon>
|
|
||||||
<el-icon
|
|
||||||
v-else
|
|
||||||
class="el-icon-remove-outline"
|
|
||||||
@click="buildContextMenu(item)"
|
|
||||||
>
|
|
||||||
<Remove />
|
|
||||||
</el-icon>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
<div class="header-actions">
|
||||||
</el-row>
|
<button
|
||||||
<el-row
|
class="action-button secondary"
|
||||||
v-show="needReload"
|
:title="t('pages.plugin.importLocal')"
|
||||||
class="reload-mask"
|
@click="handleImportLocalPlugin"
|
||||||
:class="{ 'cut-width': pluginList.length > 6 }"
|
>
|
||||||
justify="center"
|
<DownloadIcon :size="16" />
|
||||||
>
|
{{ t('pages.plugin.importLocal') }}
|
||||||
<el-button
|
</button>
|
||||||
type="primary"
|
<button
|
||||||
size="small"
|
class="action-button secondary"
|
||||||
round
|
:title="t('pages.plugin.updateAll')"
|
||||||
@click="reloadApp"
|
@click="handleUpdateAllPlugin"
|
||||||
|
>
|
||||||
|
<RefreshCwIcon :size="16" />
|
||||||
|
{{ t('pages.plugin.updateAll') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="action-button"
|
||||||
|
:title="t('pages.plugin.pluginList')"
|
||||||
|
@click="goAwesomeList"
|
||||||
|
>
|
||||||
|
<ExternalLinkIcon :size="16" />
|
||||||
|
{{ t('pages.plugin.list') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Card -->
|
||||||
|
<div class="plugin-card search-card">
|
||||||
|
<div class="search-container">
|
||||||
|
<div class="search-input-wrapper">
|
||||||
|
<SearchIcon
|
||||||
|
class="search-icon"
|
||||||
|
:size="20"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
v-model="searchText"
|
||||||
|
type="text"
|
||||||
|
class="search-input"
|
||||||
|
:placeholder="t('pages.plugin.searchPlaceholder')"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="searchText"
|
||||||
|
class="clear-button"
|
||||||
|
@click="cleanSearch"
|
||||||
|
>
|
||||||
|
<XIcon :size="16" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reload Notice -->
|
||||||
|
<transition name="notice">
|
||||||
|
<div
|
||||||
|
v-if="needReload"
|
||||||
|
class="plugin-card notice-card"
|
||||||
>
|
>
|
||||||
{{ $t('TIPS_NEED_RELOAD') }}
|
<div class="notice-content">
|
||||||
</el-button>
|
<AlertCircleIcon
|
||||||
</el-row>
|
class="notice-icon"
|
||||||
<el-dialog
|
:size="20"
|
||||||
v-model="dialogVisible"
|
/>
|
||||||
:modal-append-to-body="false"
|
<span class="notice-text">{{ t('pages.plugin.needRestart') }}</span>
|
||||||
:title="
|
<button
|
||||||
$t('CONFIG_THING', {
|
class="action-button small"
|
||||||
c: configName
|
@click="reloadApp"
|
||||||
})
|
>
|
||||||
"
|
{{ t('pages.plugin.restartApp') }}
|
||||||
width="70%"
|
</button>
|
||||||
append-to-body
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<!-- Loading Overlay -->
|
||||||
|
<div
|
||||||
|
v-if="loading"
|
||||||
|
class="loading-overlay"
|
||||||
>
|
>
|
||||||
<config-form
|
<div class="loading-spinner" />
|
||||||
:id="configName"
|
<span class="loading-text">{{ t('pages.plugin.loading') }}</span>
|
||||||
ref="$configForm"
|
</div>
|
||||||
:config="config"
|
|
||||||
:type="currentType"
|
<!-- Plugin Grid -->
|
||||||
color-mode="white"
|
<div class="plugin-grid">
|
||||||
/>
|
<div
|
||||||
<template #footer>
|
v-for="item in pluginList"
|
||||||
<el-button
|
:key="item.fullName"
|
||||||
round
|
class="plugin-card plugin-item-card"
|
||||||
@click="dialogVisible = false"
|
:class="{ disabled: !item.enabled && !searchText }"
|
||||||
|
>
|
||||||
|
<!-- Plugin Badge -->
|
||||||
|
<div
|
||||||
|
v-if="!item.gui"
|
||||||
|
class="cli-badge"
|
||||||
>
|
>
|
||||||
{{ $t('CANCEL') }}
|
CLI
|
||||||
</el-button>
|
</div>
|
||||||
<el-button
|
|
||||||
type="primary"
|
<!-- Update Badge -->
|
||||||
round
|
<div
|
||||||
@click="handleConfirmConfig"
|
v-if="latestVersionMap[item.fullName] && latestVersionMap[item.fullName] !== item.version"
|
||||||
|
class="update-badge"
|
||||||
>
|
>
|
||||||
{{ $t('CONFIRM') }}
|
NEW
|
||||||
</el-button>
|
</div>
|
||||||
</template>
|
|
||||||
</el-dialog>
|
<!-- Plugin Header -->
|
||||||
|
<div class="plugin-header">
|
||||||
|
<img
|
||||||
|
class="plugin-logo"
|
||||||
|
:src="item.logo"
|
||||||
|
:onerror="defaultLogo"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
<div class="plugin-info">
|
||||||
|
<h3
|
||||||
|
class="plugin-name"
|
||||||
|
@click="openHomepage(item.homepage)"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
<span class="plugin-version">v{{ item.version }}</span>
|
||||||
|
</h3>
|
||||||
|
<p class="plugin-author">
|
||||||
|
{{ item.author.replace(/<.*>/, '') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Plugin Description -->
|
||||||
|
<div class="plugin-description">
|
||||||
|
<p :title="item.description">
|
||||||
|
{{ item.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Plugin Actions -->
|
||||||
|
<div class="plugin-actions">
|
||||||
|
<template v-if="searchText">
|
||||||
|
<template v-if="!item.hasInstall">
|
||||||
|
<button
|
||||||
|
v-if="!item.ing"
|
||||||
|
class="plugin-button install-button"
|
||||||
|
@click="installPlugin(item)"
|
||||||
|
>
|
||||||
|
<DownloadIcon :size="16" />
|
||||||
|
{{ t('pages.plugin.install') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="plugin-button installing-button"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<div class="button-spinner" />
|
||||||
|
{{ t('pages.plugin.installing') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="plugin-button installed-button"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<CheckIcon :size="16" />
|
||||||
|
{{ t('pages.plugin.installed') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<button
|
||||||
|
v-if="item.ing"
|
||||||
|
class="plugin-button processing-button"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<div class="button-spinner" />
|
||||||
|
{{ t('pages.plugin.doingSomething') }}
|
||||||
|
</button>
|
||||||
|
<template v-else>
|
||||||
|
<button
|
||||||
|
v-if="item.enabled"
|
||||||
|
class="plugin-button settings-button"
|
||||||
|
@click="buildContextMenu(item)"
|
||||||
|
>
|
||||||
|
<SettingsIcon :size="16" />
|
||||||
|
{{ t('pages.plugin.settings') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="plugin-button disabled-button"
|
||||||
|
@click="buildContextMenu(item)"
|
||||||
|
>
|
||||||
|
<XCircleIcon :size="16" />
|
||||||
|
{{ t('pages.plugin.disabled') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div
|
||||||
|
v-if="!loading && pluginList.length === 0"
|
||||||
|
class="plugin-card empty-state"
|
||||||
|
>
|
||||||
|
<div class="empty-content">
|
||||||
|
<PackageIcon
|
||||||
|
class="empty-icon"
|
||||||
|
:size="48"
|
||||||
|
/>
|
||||||
|
<h3>{{ searchText ? t('pages.plugin.noPluginsFound') : t('pages.plugin.NoPluginsInstalled') }}</h3>
|
||||||
|
<p>{{ searchText ? t('pages.plugin.tryDifferentSearch') : t('pages.plugin.installPluginsToGetStarted') }}</p>
|
||||||
|
<button
|
||||||
|
v-if="!searchText"
|
||||||
|
class="action-button"
|
||||||
|
@click="goAwesomeList"
|
||||||
|
>
|
||||||
|
<ExternalLinkIcon :size="16" />
|
||||||
|
{{ t('pages.plugin.browsePlugins') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Config Modal -->
|
||||||
|
<transition name="modal">
|
||||||
|
<div
|
||||||
|
v-if="dialogVisible"
|
||||||
|
class="modal-overlay"
|
||||||
|
@click="dialogVisible = false"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="modal-container"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title">
|
||||||
|
{{ t('pages.plugin.configThing', { c: configName }) }}
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
class="modal-close"
|
||||||
|
@click="dialogVisible = false"
|
||||||
|
>
|
||||||
|
<XIcon :size="20" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<config-form
|
||||||
|
:id="configName"
|
||||||
|
ref="$configForm"
|
||||||
|
:config="config"
|
||||||
|
:type="currentType"
|
||||||
|
color-mode="white"
|
||||||
|
mode="plugin"
|
||||||
|
:show-tooltips="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button
|
||||||
|
class="btn btn-secondary"
|
||||||
|
@click="dialogVisible = false"
|
||||||
|
>
|
||||||
|
{{ t('common.cancel') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="handleConfirmConfig"
|
||||||
|
>
|
||||||
|
{{ t('common.confirm') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Close, Download, Goods, Refresh, Remove, Tools } from '@element-plus/icons-vue'
|
|
||||||
import type { IpcRendererEvent } from 'electron'
|
import type { IpcRendererEvent } from 'electron'
|
||||||
import { ElMessageBox } from 'element-plus'
|
|
||||||
import { debounce, DebouncedFunc } from 'lodash-es'
|
import { debounce, DebouncedFunc } from 'lodash-es'
|
||||||
|
import {
|
||||||
|
AlertCircleIcon,
|
||||||
|
CheckIcon,
|
||||||
|
DatabaseIcon,
|
||||||
|
DownloadIcon,
|
||||||
|
ExternalLinkIcon,
|
||||||
|
PackageIcon,
|
||||||
|
RefreshCwIcon,
|
||||||
|
SearchIcon,
|
||||||
|
SettingsIcon,
|
||||||
|
XCircleIcon,
|
||||||
|
XIcon
|
||||||
|
} from 'lucide-vue-next'
|
||||||
import { computed, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, toRaw, watch } from 'vue'
|
import { computed, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, toRaw, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import ConfigForm from '@/components/ConfigFormForPlugin.vue'
|
import ConfigForm from '@/components/UnifiedConfigForm.vue'
|
||||||
import { handleStreamlinePluginName } from '@/utils/common'
|
import { getRawData, handleStreamlinePluginName } from '@/utils/common'
|
||||||
import { configPaths } from '@/utils/configPaths'
|
import { configPaths } from '@/utils/configPaths'
|
||||||
import {
|
import {
|
||||||
PICGO_CONFIG_PLUGIN,
|
PICGO_CONFIG_PLUGIN,
|
||||||
@@ -249,11 +318,10 @@ import {
|
|||||||
} from '@/utils/constant'
|
} from '@/utils/constant'
|
||||||
import { getConfig, saveConfig } from '@/utils/dataSender'
|
import { getConfig, saveConfig } from '@/utils/dataSender'
|
||||||
import { IRPCActionType } from '@/utils/enum'
|
import { IRPCActionType } from '@/utils/enum'
|
||||||
import { osGlobal, updatePicBedGlobal } from '@/utils/global'
|
import { updatePicBedGlobal } from '@/utils/global'
|
||||||
import type { INPMSearchResultObject, IPicGoPlugin } from '#/types/types'
|
import type { INPMSearchResultObject, IPicGoPlugin } from '#/types/types'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const $confirm = ElMessageBox.confirm
|
|
||||||
const searchText = ref('')
|
const searchText = ref('')
|
||||||
const pluginList = ref<IPicGoPlugin[]>([])
|
const pluginList = ref<IPicGoPlugin[]>([])
|
||||||
const config = ref<any[]>([])
|
const config = ref<any[]>([])
|
||||||
@@ -264,11 +332,9 @@ const pluginNameList = ref<string[]>([])
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const needReload = ref(false)
|
const needReload = ref(false)
|
||||||
const latestVersionMap = reactive<{ [key: string]: string }>({})
|
const latestVersionMap = reactive<{ [key: string]: string }>({})
|
||||||
const pluginListToolTip = t('PLUGIN_LIST')
|
|
||||||
const importLocalPluginToolTip = t('PLUGIN_IMPORT_LOCAL')
|
|
||||||
const updateAllToolTip = t('PLUGIN_UPDATE_ALL')
|
|
||||||
const defaultLogo = ref('this.src=\'/roundLogo.png\'')
|
const defaultLogo = ref('this.src=\'/roundLogo.png\'')
|
||||||
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
|
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
|
||||||
|
|
||||||
const npmSearchText = computed(() => {
|
const npmSearchText = computed(() => {
|
||||||
return searchText.value.match('picgo-plugin-')
|
return searchText.value.match('picgo-plugin-')
|
||||||
? searchText.value
|
? searchText.value
|
||||||
@@ -276,11 +342,11 @@ const npmSearchText = computed(() => {
|
|||||||
? `picgo-plugin-${searchText.value}`
|
? `picgo-plugin-${searchText.value}`
|
||||||
: searchText.value
|
: searchText.value
|
||||||
})
|
})
|
||||||
|
|
||||||
let getSearchResult: DebouncedFunc<(val: string) => void>
|
let getSearchResult: DebouncedFunc<(val: string) => void>
|
||||||
|
|
||||||
watch(npmSearchText, (val: string) => {
|
watch(npmSearchText, (val: string) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
loading.value = true
|
|
||||||
pluginList.value = []
|
pluginList.value = []
|
||||||
getSearchResult(val)
|
getSearchResult(val)
|
||||||
} else {
|
} else {
|
||||||
@@ -290,11 +356,9 @@ watch(npmSearchText, (val: string) => {
|
|||||||
|
|
||||||
watch(dialogVisible, (val: boolean) => {
|
watch(dialogVisible, (val: boolean) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
document.body.style.overflow = 'hidden'
|
||||||
document.querySelector('.main-content.el-row').style.zIndex = 101
|
|
||||||
} else {
|
} else {
|
||||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
document.body.style.overflow = 'auto'
|
||||||
document.querySelector('.main-content.el-row').style.zIndex = 10
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -393,7 +457,6 @@ const picgoHandlePluginIngHandler = (_: IpcRendererEvent, fullName: string) => {
|
|||||||
item.ing = true
|
item.ing = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
loading.value = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const picgoTogglePluginHandler = (_: IpcRendererEvent, fullName: string, enabled: boolean) => {
|
const picgoTogglePluginHandler = (_: IpcRendererEvent, fullName: string, enabled: boolean) => {
|
||||||
@@ -421,14 +484,11 @@ onBeforeMount(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function buildContextMenu (plugin: IPicGoPlugin) {
|
async function buildContextMenu (plugin: IPicGoPlugin) {
|
||||||
window.electron.sendRPC(IRPCActionType.SHOW_PLUGIN_PAGE_MENU, plugin)
|
window.electron.sendRPC(IRPCActionType.SHOW_PLUGIN_PAGE_MENU, getRawData(plugin))
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleResize () {
|
function handleResize () {
|
||||||
const myDiv = document.getElementById('pluginList') as HTMLElement
|
// No longer needed with new layout
|
||||||
const windowHeight = window.innerHeight
|
|
||||||
const newHeight = windowHeight * 0.75
|
|
||||||
myDiv.style.height = newHeight + 'px'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -441,18 +501,10 @@ function getPluginList () {
|
|||||||
|
|
||||||
function installPlugin (item: IPicGoPlugin) {
|
function installPlugin (item: IPicGoPlugin) {
|
||||||
if (!item.gui) {
|
if (!item.gui) {
|
||||||
$confirm(t('TIPS_PLUGIN_NOT_GUI_IMPLEMENT'), t('TIPS_NOTICE'), {
|
if (confirm(t('pages.plugin.notGuiImplement'))) {
|
||||||
confirmButtonText: t('CONFIRM'),
|
item.ing = true
|
||||||
cancelButtonText: t('CANCEL'),
|
window.electron.sendRPC(IRPCActionType.PLUGIN_INSTALL, item.fullName)
|
||||||
type: 'warning'
|
}
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
item.ing = true
|
|
||||||
window.electron.sendRPC(IRPCActionType.PLUGIN_INSTALL, item.fullName)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log('Install canceled')
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
item.ing = true
|
item.ing = true
|
||||||
window.electron.sendRPC(IRPCActionType.PLUGIN_INSTALL, item.fullName)
|
window.electron.sendRPC(IRPCActionType.PLUGIN_INSTALL, item.fullName)
|
||||||
@@ -468,11 +520,13 @@ async function handleReload () {
|
|||||||
needReload: true
|
needReload: true
|
||||||
})
|
})
|
||||||
needReload.value = true
|
needReload.value = true
|
||||||
const successNotification = new Notification(t('PLUGIN_UPDATE_SUCCEED'), {
|
if ('Notification' in window) {
|
||||||
body: t('TIPS_NEED_RELOAD')
|
const successNotification = new Notification(t('pages.plugin.updateSuccess'), {
|
||||||
})
|
body: t('pages.plugin.needRestart')
|
||||||
successNotification.onclick = () => {
|
})
|
||||||
reloadApp()
|
successNotification.onclick = () => {
|
||||||
|
reloadApp()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,11 +554,13 @@ async function handleConfirmConfig () {
|
|||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
const successNotification = new Notification(t('SETTINGS_RESULT'), {
|
if ('Notification' in window) {
|
||||||
body: t('TIPS_SET_SUCCEED')
|
const successNotification = new Notification(t('SETTINGS_RESULT'), {
|
||||||
})
|
body: t('pages.plugin.setSuccess')
|
||||||
successNotification.onclick = () => {
|
})
|
||||||
return true
|
successNotification.onclick = () => {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
getPluginList()
|
getPluginList()
|
||||||
@@ -605,179 +661,16 @@ onBeforeUnmount(() => {
|
|||||||
window.electron.ipcRendererRemoveListener(PICGO_CONFIG_PLUGIN, picgoConfigPluginHandler)
|
window.electron.ipcRendererRemoveListener(PICGO_CONFIG_PLUGIN, picgoConfigPluginHandler)
|
||||||
window.electron.ipcRendererRemoveListener(PICGO_HANDLE_PLUGIN_ING, picgoHandlePluginIngHandler)
|
window.electron.ipcRendererRemoveListener(PICGO_HANDLE_PLUGIN_ING, picgoHandlePluginIngHandler)
|
||||||
window.electron.ipcRendererRemoveListener(PICGO_TOGGLE_PLUGIN, picgoTogglePluginHandler)
|
window.electron.ipcRendererRemoveListener(PICGO_TOGGLE_PLUGIN, picgoTogglePluginHandler)
|
||||||
|
|
||||||
|
// Reset body overflow
|
||||||
|
document.body.style.overflow = 'auto'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'PluginPage'
|
name: 'PluginPage'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="stylus">
|
|
||||||
$darwinBg = #172426
|
<style scoped src="./css/PluginPage.css"></style>
|
||||||
#plugin-view
|
|
||||||
position absolute
|
|
||||||
left 142px
|
|
||||||
right 0
|
|
||||||
.el-loading-mask
|
|
||||||
background-color rgba(0, 0, 0, 0.8)
|
|
||||||
.plugin-list
|
|
||||||
align-content flex-start
|
|
||||||
height: 600px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 8px 15px;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
position: absolute;
|
|
||||||
top: 70px;
|
|
||||||
left: 5px;
|
|
||||||
transition: all 0.2s ease-in-out 0.1s;
|
|
||||||
width: 100%
|
|
||||||
.el-loading-mask
|
|
||||||
left: 20px
|
|
||||||
width: calc(100% - 40px)
|
|
||||||
.view-title
|
|
||||||
color #eee
|
|
||||||
font-size 20px
|
|
||||||
text-align center
|
|
||||||
margin 10px auto
|
|
||||||
position relative
|
|
||||||
i.el-icon-goods
|
|
||||||
margin-left 4px
|
|
||||||
font-size 20px
|
|
||||||
vertical-align middle
|
|
||||||
cursor pointer
|
|
||||||
transition color .2s ease-in-out
|
|
||||||
&:hover
|
|
||||||
color #49B1F5
|
|
||||||
i.el-icon-update
|
|
||||||
position absolute
|
|
||||||
right 35px
|
|
||||||
top 8px
|
|
||||||
font-size 20px
|
|
||||||
vertical-align middle
|
|
||||||
cursor pointer
|
|
||||||
transition color .2s ease-in-out
|
|
||||||
&:hover
|
|
||||||
color #49B1F5
|
|
||||||
i.el-icon-download
|
|
||||||
position absolute
|
|
||||||
right 5px
|
|
||||||
top 8px
|
|
||||||
font-size 20px
|
|
||||||
vertical-align middle
|
|
||||||
cursor pointer
|
|
||||||
transition color .2s ease-in-out
|
|
||||||
&:hover
|
|
||||||
color #49B1F5
|
|
||||||
.handle-bar
|
|
||||||
margin-bottom 20px
|
|
||||||
&.cut-width
|
|
||||||
padding-right: 8px
|
|
||||||
.el-input__inner
|
|
||||||
border-radius 0
|
|
||||||
.plugin-item
|
|
||||||
box-sizing border-box
|
|
||||||
height 80px
|
|
||||||
background #444
|
|
||||||
padding 8px
|
|
||||||
user-select text
|
|
||||||
transition all .2s ease-in-out
|
|
||||||
position relative
|
|
||||||
&__container
|
|
||||||
height 80px
|
|
||||||
margin-bottom 10px
|
|
||||||
.cli-only-badge
|
|
||||||
position absolute
|
|
||||||
right 0px
|
|
||||||
top 0
|
|
||||||
font-size 12px
|
|
||||||
padding 3px 8px
|
|
||||||
background #49B1F5
|
|
||||||
color #eee
|
|
||||||
&.darwin
|
|
||||||
background transparentify($darwinBg, #000, 0.75)
|
|
||||||
&:hover
|
|
||||||
background transparentify($darwinBg, #000, 0.85)
|
|
||||||
&:hover
|
|
||||||
background #333
|
|
||||||
&__logo
|
|
||||||
width 64px
|
|
||||||
height 64px
|
|
||||||
float left
|
|
||||||
&__content
|
|
||||||
float left
|
|
||||||
width calc(100% - 72px)
|
|
||||||
height 64px
|
|
||||||
color #ddd
|
|
||||||
margin-left 8px
|
|
||||||
display flex
|
|
||||||
flex-direction column
|
|
||||||
justify-content space-between
|
|
||||||
&.disabled
|
|
||||||
color #aaa
|
|
||||||
&__name
|
|
||||||
font-size 16px
|
|
||||||
height 22px
|
|
||||||
line-height 22px
|
|
||||||
font-weight 600
|
|
||||||
cursor pointer
|
|
||||||
text-overflow ellipsis
|
|
||||||
white-space nowrap
|
|
||||||
overflow hidden
|
|
||||||
transition all .2s ease-in-out
|
|
||||||
&:hover
|
|
||||||
color: #1B9EF3
|
|
||||||
&__desc
|
|
||||||
font-size 14px
|
|
||||||
height 21px
|
|
||||||
line-height 21px
|
|
||||||
overflow hidden
|
|
||||||
text-overflow ellipsis
|
|
||||||
white-space nowrap
|
|
||||||
&__info-bar
|
|
||||||
font-size 14px
|
|
||||||
height 21px
|
|
||||||
line-height 28px
|
|
||||||
position relative
|
|
||||||
&__author
|
|
||||||
overflow hidden
|
|
||||||
text-overflow ellipsis
|
|
||||||
white-space nowrap
|
|
||||||
&__config
|
|
||||||
float right
|
|
||||||
font-size 16px
|
|
||||||
cursor pointer
|
|
||||||
transition all .2s ease-in-out
|
|
||||||
&:hover
|
|
||||||
color: #1B9EF3
|
|
||||||
.config-button
|
|
||||||
font-size 12px
|
|
||||||
color #ddd
|
|
||||||
background #222
|
|
||||||
padding 1px 8px
|
|
||||||
height 18px
|
|
||||||
line-height 18px
|
|
||||||
text-align center
|
|
||||||
position absolute
|
|
||||||
top 4px
|
|
||||||
right 20px
|
|
||||||
transition all .2s ease-in-out
|
|
||||||
&.reload
|
|
||||||
right 0px
|
|
||||||
&.ing
|
|
||||||
right 0px
|
|
||||||
&.install
|
|
||||||
right 0px
|
|
||||||
&:hover
|
|
||||||
background: #1B9EF3
|
|
||||||
color #fff
|
|
||||||
.reload-mask
|
|
||||||
position absolute
|
|
||||||
width calc(100% - 40px)
|
|
||||||
bottom -320px
|
|
||||||
text-align center
|
|
||||||
background rgba(0,0,0,0.4)
|
|
||||||
padding 10px 0
|
|
||||||
&.cut-width
|
|
||||||
width calc(100% - 48px)
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,65 +1,73 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="rename-page">
|
<div class="rename-container">
|
||||||
<el-form
|
<div class="rename-card">
|
||||||
ref="formRef"
|
<form @submit.prevent="confirmName">
|
||||||
:model="form"
|
<div class="form-content">
|
||||||
@submit.prevent
|
<div class="form-group">
|
||||||
>
|
<div class="input-wrapper">
|
||||||
<el-form-item
|
<input
|
||||||
:label="$t('FILE_RENAME')"
|
ref="fileNameInput"
|
||||||
prop="fileName"
|
v-model="form.fileName"
|
||||||
:rules="[{ required: true, message: 'file name is required', trigger: 'blur' }]"
|
type="text"
|
||||||
>
|
class="form-input"
|
||||||
<el-input
|
:class="{ 'input-error': validationError }"
|
||||||
v-model="form.fileName"
|
:placeholder="t('pages.rename.placeholder')"
|
||||||
size="small"
|
autofocus
|
||||||
autofocus
|
@keyup.enter="confirmName"
|
||||||
@keyup.enter="confirmName"
|
@input="clearValidationError"
|
||||||
>
|
>
|
||||||
<template #suffix>
|
<button
|
||||||
<el-icon
|
v-if="form.fileName"
|
||||||
class="el-input__icon"
|
type="button"
|
||||||
style="cursor: pointer"
|
class="input-clear"
|
||||||
@click="form.fileName = ''"
|
@click="clearFileName"
|
||||||
|
>
|
||||||
|
<XIcon :size="16" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="validationError"
|
||||||
|
class="validation-error"
|
||||||
>
|
>
|
||||||
<close />
|
{{ validationError }}
|
||||||
</el-icon>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</el-input>
|
</div>
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
<!-- Actions -->
|
||||||
<el-row>
|
<div class="form-actions">
|
||||||
<div class="pull-right">
|
<button
|
||||||
<el-button
|
type="button"
|
||||||
round
|
class="btn btn-secondary"
|
||||||
size="small"
|
@click="cancel"
|
||||||
@click="cancel"
|
>
|
||||||
>
|
{{ $t('common.cancel') }}
|
||||||
{{ $t('CANCEL') }}
|
</button>
|
||||||
</el-button>
|
<button
|
||||||
<el-button
|
type="submit"
|
||||||
type="primary"
|
class="btn btn-primary"
|
||||||
round
|
:disabled="!form.fileName.trim()"
|
||||||
size="small"
|
>
|
||||||
@click="confirmName"
|
{{ $t('common.confirm') }}
|
||||||
>
|
</button>
|
||||||
{{ $t('CONFIRM') }}
|
</div>
|
||||||
</el-button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</el-row>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Close } from '@element-plus/icons-vue'
|
|
||||||
import type { IpcRendererEvent } from 'electron'
|
import type { IpcRendererEvent } from 'electron'
|
||||||
import type { FormInstance } from 'element-plus'
|
import { XIcon } from 'lucide-vue-next'
|
||||||
import { onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue'
|
import { nextTick, onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME } from '@/utils/constant'
|
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME } from '@/utils/constant'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const id = ref<string | null>(null)
|
const id = ref<string | null>(null)
|
||||||
const formRef = ref<FormInstance>()
|
const fileNameInput = ref<HTMLInputElement>()
|
||||||
|
const validationError = ref<string>('')
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
fileName: '',
|
fileName: '',
|
||||||
@@ -70,6 +78,10 @@ const handleFileName = (_: IpcRendererEvent, newName: string, _originName: strin
|
|||||||
form.fileName = newName
|
form.fileName = newName
|
||||||
form.originName = _originName
|
form.originName = _originName
|
||||||
id.value = _id
|
id.value = _id
|
||||||
|
nextTick(() => {
|
||||||
|
fileNameInput.value?.focus()
|
||||||
|
fileNameInput.value?.select()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
window.electron.ipcRendererOn(RENAME_FILE_NAME, handleFileName)
|
window.electron.ipcRendererOn(RENAME_FILE_NAME, handleFileName)
|
||||||
@@ -78,19 +90,49 @@ onBeforeMount(() => {
|
|||||||
window.electron.sendToMain(GET_RENAME_FILE_NAME, '')
|
window.electron.sendToMain(GET_RENAME_FILE_NAME, '')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function validateFileName (fileName: string): string {
|
||||||
|
if (!fileName.trim()) {
|
||||||
|
return 'File name is required'
|
||||||
|
}
|
||||||
|
const invalidChars = /[<>:"/\\|?*]/g
|
||||||
|
if (invalidChars.test(fileName)) {
|
||||||
|
return 'File name contains invalid characters'
|
||||||
|
}
|
||||||
|
const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i
|
||||||
|
if (reservedNames.test(fileName.trim())) {
|
||||||
|
return 'This is a reserved file name'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
function confirmName () {
|
function confirmName () {
|
||||||
formRef.value?.validate(valid => {
|
const error = validateFileName(form.fileName)
|
||||||
if (valid) {
|
if (error) {
|
||||||
window.electron.sendToMain(`${RENAME_FILE_NAME}${id.value}`, form.fileName)
|
validationError.value = error
|
||||||
}
|
return
|
||||||
})
|
}
|
||||||
|
|
||||||
|
window.electron.sendToMain(`${RENAME_FILE_NAME}${id.value}`, form.fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel () {
|
function cancel () {
|
||||||
// if cancel, use origin file name
|
|
||||||
window.electron.sendToMain(`${RENAME_FILE_NAME}${id.value}`, form.originName)
|
window.electron.sendToMain(`${RENAME_FILE_NAME}${id.value}`, form.originName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearFileName () {
|
||||||
|
form.fileName = ''
|
||||||
|
validationError.value = ''
|
||||||
|
nextTick(() => {
|
||||||
|
fileNameInput.value?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearValidationError () {
|
||||||
|
if (validationError.value) {
|
||||||
|
validationError.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.electron.ipcRendererRemoveListener(RENAME_FILE_NAME, handleFileName)
|
window.electron.ipcRendererRemoveListener(RENAME_FILE_NAME, handleFileName)
|
||||||
})
|
})
|
||||||
@@ -100,11 +142,228 @@ export default {
|
|||||||
name: 'RenamePage'
|
name: 'RenamePage'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="stylus">
|
<style scoped>
|
||||||
#rename-page
|
.rename-container {
|
||||||
padding 0 20px
|
padding: 2rem;
|
||||||
.pull-right
|
min-height: 100vh;
|
||||||
float right
|
background: var(--color-background-secondary);
|
||||||
.el-form-item__label
|
display: flex;
|
||||||
color #ddd
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rename-card {
|
||||||
|
background: var(--color-background-primary);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form */
|
||||||
|
.form-content {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.875rem 1rem;
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--color-background-primary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-blue-common);
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input.input-error {
|
||||||
|
border-color: #f56c6c;
|
||||||
|
box-shadow: 0 0 0 2px rgba(245, 108, 108, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-clear {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.75rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
background: var(--color-background-tertiary);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-clear:hover {
|
||||||
|
background: var(--color-background-secondary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.validation-error {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #f56c6c;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem 2rem 2rem;
|
||||||
|
background: var(--color-background-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #409eff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background: #66b1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--color-background-primary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover:not(:disabled) {
|
||||||
|
background: var(--color-background-secondary);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.rename-container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rename-card {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
padding: 1rem 1.5rem 1.5rem;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.rename-container {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles for accessibility */
|
||||||
|
.btn:focus-visible,
|
||||||
|
.input-clear:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation for error state */
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateX(-4px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-error {
|
||||||
|
animation: shake 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode adjustments */
|
||||||
|
:root.dark .rename-card,
|
||||||
|
:root.auto.dark .rename-card {
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,120 +1,148 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="shortcut-page">
|
<div class="shortkey-container">
|
||||||
<div class="view-title">
|
<!-- Header -->
|
||||||
{{ $t('SETTINGS_SET_SHORTCUT') }}
|
<div class="shortkey-header">
|
||||||
|
<div class="header-content">
|
||||||
|
<KeyboardIcon
|
||||||
|
:size="24"
|
||||||
|
class="header-icon"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<h1>{{ t('pages.shortKey.title') }}</h1>
|
||||||
|
<p>{{ ' ' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-row>
|
|
||||||
<el-col
|
<!-- Shortcuts Table Card -->
|
||||||
:span="20"
|
<div class="shortkey-card">
|
||||||
:offset="2"
|
<div class="table-container">
|
||||||
|
<table class="shortkey-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ t('pages.shortKey.name') }}</th>
|
||||||
|
<th>{{ t('pages.shortKey.bind') }}</th>
|
||||||
|
<th>{{ t('pages.shortKey.status') }}</th>
|
||||||
|
<th>{{ t('pages.shortKey.source') }}</th>
|
||||||
|
<th>{{ t('pages.shortKey.handle') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(item, index) in list"
|
||||||
|
:key="item.name"
|
||||||
|
class="table-row"
|
||||||
|
>
|
||||||
|
<td class="name-cell">
|
||||||
|
<div class="shortcut-name">
|
||||||
|
{{ item.label ? item.label : item.name }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="key-cell">
|
||||||
|
<div class="key-binding">
|
||||||
|
<kbd
|
||||||
|
v-if="item.key"
|
||||||
|
class="key-display"
|
||||||
|
>{{ item.key }}</kbd>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="no-binding"
|
||||||
|
>{{ t('pages.shortKey.noBinding') }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="status-cell">
|
||||||
|
<span
|
||||||
|
class="status-badge"
|
||||||
|
:class="{ 'status-enabled': item.enable, 'status-disabled': !item.enable }"
|
||||||
|
>
|
||||||
|
{{ item.enable ? t('pages.shortKey.enabled') : t('pages.shortKey.disabled') }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="source-cell">
|
||||||
|
<span class="source-name">{{ calcOriginShowName(item.from || '') }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="actions-cell">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="item.enable ? 'btn-danger' : 'btn-success'"
|
||||||
|
@click="toggleEnable(item)"
|
||||||
|
>
|
||||||
|
{{ item.enable ? t('pages.shortKey.disable') : t('pages.shortKey.enable') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-secondary"
|
||||||
|
@click="openKeyBindingDialog(item, index)"
|
||||||
|
>
|
||||||
|
<Edit :size="14" />
|
||||||
|
{{ t('pages.shortKey.edit') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Key Binding Modal -->
|
||||||
|
<transition name="modal">
|
||||||
|
<div
|
||||||
|
v-if="keyBindingVisible"
|
||||||
|
class="modal-overlay"
|
||||||
|
@click.self="cancelKeyBinding"
|
||||||
>
|
>
|
||||||
<el-table
|
<div class="modal-content">
|
||||||
class="shortcut-page-table-border"
|
<div class="modal-header">
|
||||||
:data="list"
|
<h3 class="modal-title">
|
||||||
size="small"
|
{{ t('pages.shortKey.changeUpload') }}
|
||||||
header-cell-class-name="shortcut-page-table-border"
|
</h3>
|
||||||
cell-class-name="shortcut-page-table-border"
|
<button
|
||||||
>
|
class="modal-close"
|
||||||
<el-table-column :label="$t('SHORTCUT_NAME')">
|
@click="cancelKeyBinding"
|
||||||
<template #default="scope">
|
>
|
||||||
{{ scope.row.label ? scope.row.label : scope.row.name }}
|
<XIcon :size="20" />
|
||||||
</template>
|
</button>
|
||||||
</el-table-column>
|
</div>
|
||||||
<el-table-column
|
<div class="modal-body">
|
||||||
width="160px"
|
<div class="form-group">
|
||||||
:label="$t('SHORTCUT_BIND')"
|
<label>{{ t('pages.shortKey.keyBinding') }}</label>
|
||||||
prop="key"
|
<input
|
||||||
/>
|
v-model="shortKey"
|
||||||
<el-table-column :label="$t('SHORTCUT_STATUS')">
|
class="form-input key-input"
|
||||||
<template #default="scope">
|
:placeholder="t('pages.shortKey.pressKeys')"
|
||||||
<el-tag
|
readonly
|
||||||
size="small"
|
@keydown.prevent="keyDetect($event as KeyboardEvent)"
|
||||||
:type="scope.row.enable ? 'success' : 'danger'"
|
|
||||||
>
|
>
|
||||||
{{ scope.row.enable ? $t('SHORTCUT_ENABLED') : $t('SHORTCUT_DISABLED') }}
|
<div class="input-hint">
|
||||||
</el-tag>
|
{{ t('pages.shortKey.pressHint') }}
|
||||||
</template>
|
</div>
|
||||||
</el-table-column>
|
</div>
|
||||||
<el-table-column
|
</div>
|
||||||
:label="$t('SHORTCUT_SOURCE')"
|
<div class="modal-footer">
|
||||||
width="100px"
|
<button
|
||||||
>
|
class="btn btn-secondary"
|
||||||
<template #default="scope">
|
@click="cancelKeyBinding"
|
||||||
{{ calcOriginShowName(scope.row.from) }}
|
>
|
||||||
</template>
|
{{ $t('CANCEL') }}
|
||||||
</el-table-column>
|
</button>
|
||||||
<el-table-column
|
<button
|
||||||
:label="$t('SHORTCUT_HANDLE')"
|
class="btn btn-primary"
|
||||||
width="100px"
|
@click="confirmKeyBinding"
|
||||||
>
|
>
|
||||||
<template #default="scope">
|
{{ $t('common.confirm') }}
|
||||||
<el-row>
|
</button>
|
||||||
<el-button
|
</div>
|
||||||
size="small"
|
</div>
|
||||||
:class="{
|
</div>
|
||||||
disabled: scope.row.enable
|
</transition>
|
||||||
}"
|
|
||||||
type="info"
|
|
||||||
:link="true"
|
|
||||||
@click="toggleEnable(scope.row)"
|
|
||||||
>
|
|
||||||
{{ scope.row.enable ? $t('SHORTCUT_DISABLE') : $t('SHORTCUT_ENABLE') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
class="edit"
|
|
||||||
size="small"
|
|
||||||
type="info"
|
|
||||||
:link="true"
|
|
||||||
@click="openKeyBindingDialog(scope.row, scope.$index)"
|
|
||||||
>
|
|
||||||
{{ $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')"
|
|
||||||
:modal-append-to-body="false"
|
|
||||||
append-to-body
|
|
||||||
>
|
|
||||||
<el-form
|
|
||||||
label-position="top"
|
|
||||||
label-width="80px"
|
|
||||||
>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input
|
|
||||||
v-model="shortKey"
|
|
||||||
class="align-center"
|
|
||||||
:autofocus="true"
|
|
||||||
@keydown.prevent="keyDetect($event as KeyboardEvent)"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button
|
|
||||||
round
|
|
||||||
@click="cancelKeyBinding"
|
|
||||||
>
|
|
||||||
{{ $t('CANCEL') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
round
|
|
||||||
@click="confirmKeyBinding"
|
|
||||||
>
|
|
||||||
{{ $t('CONFIRM') }}
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { Edit, KeyboardIcon, XIcon } from 'lucide-vue-next'
|
||||||
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
|
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import { configPaths } from '@/utils/configPaths'
|
import { configPaths } from '@/utils/configPaths'
|
||||||
import { getConfig } from '@/utils/dataSender'
|
import { getConfig } from '@/utils/dataSender'
|
||||||
@@ -122,6 +150,7 @@ import { IRPCActionType } from '@/utils/enum'
|
|||||||
import keyBinding from '@/utils/key-binding'
|
import keyBinding from '@/utils/key-binding'
|
||||||
import type { IShortKeyConfig, IShortKeyConfigs } from '#/types/types'
|
import type { IShortKeyConfig, IShortKeyConfigs } from '#/types/types'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const list = ref<IShortKeyConfig[]>([])
|
const list = ref<IShortKeyConfig[]>([])
|
||||||
const keyBindingVisible = ref(false)
|
const keyBindingVisible = ref(false)
|
||||||
const command = ref('')
|
const command = ref('')
|
||||||
@@ -195,40 +224,457 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style scoped>
|
||||||
#shortcut-page
|
.shortkey-container {
|
||||||
.shortcut-page-table-border
|
padding: 1.5rem;
|
||||||
border-color darken(#eee, 50%)
|
min-height: 100vh;
|
||||||
.el-dialog__body
|
background: var(--color-background-secondary);
|
||||||
padding 10px 20px
|
color: var(--color-text-primary);
|
||||||
.el-form-item
|
overflow-y: auto;
|
||||||
margin-bottom 0
|
scrollbar-width: none;
|
||||||
.el-button
|
-ms-overflow-style: none;
|
||||||
&.disabled
|
}
|
||||||
color: #F56C6C
|
|
||||||
&.edit
|
.shortkey-container::-webkit-scrollbar {
|
||||||
color: #67C23A
|
display: none;
|
||||||
&--text
|
}
|
||||||
padding-left 4px
|
|
||||||
padding-right 4px
|
/* Header */
|
||||||
.el-table
|
.shortkey-header {
|
||||||
background-color: transparent
|
display: flex;
|
||||||
color #ddd
|
justify-content: space-between;
|
||||||
&::before
|
align-items: center;
|
||||||
background-color darken(#eee, 50%)
|
background: var(--color-surface);
|
||||||
thead
|
border-radius: 12px;
|
||||||
color #bbb
|
padding: 1.5rem;
|
||||||
th,tr
|
margin-bottom: 1.5rem;
|
||||||
background-color: transparent
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
&__body
|
border: 1px solid var(--color-border);
|
||||||
tr.el-table__row--striped
|
}
|
||||||
td
|
|
||||||
background transparent
|
.header-content {
|
||||||
&--enable-row-hover
|
display: flex;
|
||||||
.el-table__body
|
align-items: center;
|
||||||
tr:hover
|
gap: 1rem;
|
||||||
&>td
|
}
|
||||||
background #333
|
|
||||||
.el-button+.el-button
|
.header-icon {
|
||||||
margin-left 4px
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-header p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card */
|
||||||
|
.shortkey-card {
|
||||||
|
background: var(--color-background-primary);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px var(--color-border);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-table th {
|
||||||
|
background: var(--color-background-tertiary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-table td {
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--color-border-secondary);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row:hover {
|
||||||
|
background: var(--color-background-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Cells */
|
||||||
|
.name-cell {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-cell {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-binding {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-display {
|
||||||
|
background: var(--color-background-tertiary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-binding {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-cell {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-enabled {
|
||||||
|
background: rgba(103, 194, 58, 0.1);
|
||||||
|
color: #67c23a;
|
||||||
|
border: 1px solid rgba(103, 194, 58, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-disabled {
|
||||||
|
background: rgba(245, 108, 108, 0.1);
|
||||||
|
color: #f56c6c;
|
||||||
|
border: 1px solid rgba(245, 108, 108, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-cell {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-name {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-cell {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
padding: 0.5rem 0.875rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
min-width: fit-content;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #409eff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background: #66b1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--color-background-tertiary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover:not(:disabled) {
|
||||||
|
background: var(--color-background-secondary);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: #67c23a;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success:hover:not(:disabled) {
|
||||||
|
background: #85ce61;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: #f56c6c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover:not(:disabled) {
|
||||||
|
background: #f78989;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--color-background-primary);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--color-border-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
background: var(--color-background-tertiary);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
background: var(--color-background-secondary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-top: 1px solid var(--color-border-secondary);
|
||||||
|
background: var(--color-background-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Elements */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--color-background-primary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-blue-common);
|
||||||
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-input {
|
||||||
|
font-family: monospace;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-hint {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transitions */
|
||||||
|
.modal-enter-active,
|
||||||
|
.modal-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-from,
|
||||||
|
.modal-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-active .modal-content,
|
||||||
|
.modal-leave-active .modal-content {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-from .modal-content,
|
||||||
|
.modal-leave-to .modal-content {
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.shortkey-container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-table th,
|
||||||
|
.shortkey-table td {
|
||||||
|
padding: 0.75rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header,
|
||||||
|
.modal-body,
|
||||||
|
.modal-footer {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.shortkey-container {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-header h1 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-table {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortkey-table th,
|
||||||
|
.shortkey-table td {
|
||||||
|
padding: 0.5rem 0.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles for accessibility */
|
||||||
|
.btn:focus-visible,
|
||||||
|
.modal-close:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -200,13 +200,13 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { IpcRendererEvent } from 'electron'
|
import type { IpcRendererEvent } from 'electron'
|
||||||
import { ElMessage as $message } from 'element-plus'
|
|
||||||
import { ChevronDownIcon, ClipboardIcon, DatabaseIcon, LinkIcon, Settings, UploadCloudIcon, XIcon } from 'lucide-vue-next'
|
import { ChevronDownIcon, ClipboardIcon, DatabaseIcon, LinkIcon, Settings, UploadCloudIcon, XIcon } from 'lucide-vue-next'
|
||||||
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
|
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
import ImageProcessSetting from '@/components/ImageProcessSetting.vue'
|
import ImageProcessSetting from '@/components/ImageProcessSetting.vue'
|
||||||
|
import useMessage from '@/hooks/useMessage'
|
||||||
import { PICBEDS_PAGE } from '@/router/config'
|
import { PICBEDS_PAGE } from '@/router/config'
|
||||||
import $bus from '@/utils/bus'
|
import $bus from '@/utils/bus'
|
||||||
import { isUrl } from '@/utils/common'
|
import { isUrl } from '@/utils/common'
|
||||||
@@ -221,6 +221,7 @@ import type { IFileWithPath, IUploaderConfigItem } from '#/types/types'
|
|||||||
useDragEventListeners()
|
useDragEventListeners()
|
||||||
const $router = useRouter()
|
const $router = useRouter()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
const imageProcessDialogVisible = ref(false)
|
const imageProcessDialogVisible = ref(false)
|
||||||
const useShortUrl = ref(false)
|
const useShortUrl = ref(false)
|
||||||
@@ -327,7 +328,7 @@ function onDrop (e: DragEvent) {
|
|||||||
if (isUrl(str)) {
|
if (isUrl(str)) {
|
||||||
window.electron.sendRPC(IRPCActionType.UPLOAD_CHOOSED_FILES, [{ path: str }])
|
window.electron.sendRPC(IRPCActionType.UPLOAD_CHOOSED_FILES, [{ path: str }])
|
||||||
} else {
|
} else {
|
||||||
$message.error(t('pages.upload.dragValidPictureOrUrl'))
|
message.error(t('pages.upload.dragValidPictureOrUrl'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,7 +346,7 @@ function handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer)
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
$message.error(t('pages.upload.dragValidPictureOrUrl'))
|
message.error(t('pages.upload.dragValidPictureOrUrl'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +416,7 @@ function handleInputBoxValue (val: string) {
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
$message.error(t('pages.upload.inputValidUrl'))
|
message.error(t('pages.upload.inputValidUrl'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,102 +1,95 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="config-list-view">
|
<div class="config-container">
|
||||||
<div class="view-title">
|
<!-- Header Card -->
|
||||||
{{ $t('SETTINGS') }}
|
<div class="config-card header-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h1 class="page-title">
|
||||||
|
{{ t('pages.uploaderConfig.title') }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-row
|
|
||||||
:gutter="15"
|
<!-- Config Items Card -->
|
||||||
justify="space-between"
|
<div class="config-card main-card">
|
||||||
align="middle"
|
<div class="config-grid">
|
||||||
type="flex"
|
|
||||||
class="config-list"
|
|
||||||
>
|
|
||||||
<el-col
|
|
||||||
v-for="item in curConfigList"
|
|
||||||
:key="item._id"
|
|
||||||
class="config-item-col"
|
|
||||||
:xs="24"
|
|
||||||
:sm="curConfigList.length === 1 ? 24 : 12"
|
|
||||||
:md="curConfigList.length === 1 ? 24 : 12"
|
|
||||||
:lg="curConfigList.length === 1 ? 12 : 6"
|
|
||||||
:xl="curConfigList.length === 1 ? 12 : 3"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
|
v-for="item in curConfigList"
|
||||||
|
:key="item._id"
|
||||||
:class="`config-item ${defaultConfigId === item._id ? 'selected' : ''}`"
|
:class="`config-item ${defaultConfigId === item._id ? 'selected' : ''}`"
|
||||||
@click="() => selectItem(item._id)"
|
@click="() => selectItem(item._id)"
|
||||||
>
|
>
|
||||||
<div class="config-name">
|
<div class="config-content">
|
||||||
{{ item._configName }}
|
<div class="config-name">
|
||||||
</div>
|
{{ item._configName }}
|
||||||
<div class="config-update-time">
|
</div>
|
||||||
{{ formatTime(item._updatedAt) }}
|
<div class="config-update-time">
|
||||||
</div>
|
{{ formatTime(item._updatedAt) }}
|
||||||
<div
|
</div>
|
||||||
v-if="defaultConfigId === item._id"
|
<div
|
||||||
class="default-text"
|
v-if="defaultConfigId === item._id"
|
||||||
>
|
class="default-badge"
|
||||||
{{ $t('SELECTED_SETTING_HINT') }}
|
|
||||||
</div>
|
|
||||||
<div class="operation-container">
|
|
||||||
<el-icon
|
|
||||||
class="el-icon-edit"
|
|
||||||
@click="openEditPage(item._id)"
|
|
||||||
>
|
>
|
||||||
<Edit />
|
{{ t('pages.uploaderConfig.selected') }}
|
||||||
</el-icon>
|
</div>
|
||||||
<el-icon
|
</div>
|
||||||
class="el-icon-delete"
|
<div class="config-actions">
|
||||||
|
<button
|
||||||
|
class="action-btn edit-btn"
|
||||||
|
:title="t('pages.uploaderConfig.edit')"
|
||||||
|
@click.stop="openEditPage(item._id)"
|
||||||
|
>
|
||||||
|
<Edit :size="16" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="action-btn delete-btn"
|
||||||
:class="curConfigList.length <= 1 ? 'disabled' : ''"
|
:class="curConfigList.length <= 1 ? 'disabled' : ''"
|
||||||
|
:title="t('pages.uploaderConfig.delete')"
|
||||||
|
:disabled="curConfigList.length <= 1"
|
||||||
@click.stop="() => deleteConfig(item._id)"
|
@click.stop="() => deleteConfig(item._id)"
|
||||||
>
|
>
|
||||||
<Delete />
|
<Trash2 :size="16" />
|
||||||
</el-icon>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
|
||||||
<el-col
|
<!-- Add New Config Button -->
|
||||||
class="config-item-col"
|
|
||||||
:xs="24"
|
|
||||||
:sm="curConfigList.length === 1 ? 24 : 12"
|
|
||||||
:md="curConfigList.length === 1 ? 24 : 12"
|
|
||||||
:lg="curConfigList.length === 1 ? 12 : 6"
|
|
||||||
:xl="curConfigList.length === 1 ? 12 : 3"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="config-item config-item-add"
|
class="config-item config-item-add"
|
||||||
@click="addNewConfig"
|
@click="addNewConfig"
|
||||||
>
|
>
|
||||||
<el-icon class="el-icon-plus">
|
<div class="add-content">
|
||||||
<Plus />
|
<Plus :size="32" />
|
||||||
</el-icon>
|
<span class="add-text">{{ t('pages.uploaderConfig.addNew') }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
</div>
|
||||||
<el-row
|
|
||||||
type="flex"
|
<!-- Actions Card -->
|
||||||
justify="center"
|
<div class="config-card actions-card">
|
||||||
:span="24"
|
<div class="card-actions">
|
||||||
class="set-default-container"
|
<button
|
||||||
>
|
class="primary-button"
|
||||||
<el-button
|
:disabled="store?.state.defaultPicBed === type"
|
||||||
class="set-default-btn"
|
@click="setDefaultPicBed(type)"
|
||||||
type="success"
|
>
|
||||||
round
|
<DatabaseIcon :size="16" />
|
||||||
:disabled="store?.state.defaultPicBed === type"
|
<span>{{ t('pages.uploaderConfig.setAsDefault') }}</span>
|
||||||
@click="setDefaultPicBed(type)"
|
</button>
|
||||||
>
|
</div>
|
||||||
{{ $t('SETTINGS_SET_DEFAULT_PICBED') }}
|
</div>
|
||||||
</el-button>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Delete, Edit, Plus } from '@element-plus/icons-vue'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { DatabaseIcon, Edit, Plus, Trash2 } from 'lucide-vue-next'
|
||||||
import { onBeforeMount, ref } from 'vue'
|
import { onBeforeMount, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
|
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import useConfirm from '@/hooks/useConfirm'
|
||||||
|
import useMessage from '@/hooks/useMessage'
|
||||||
import { useStore } from '@/hooks/useStore'
|
import { useStore } from '@/hooks/useStore'
|
||||||
import { PICBEDS_PAGE, UPLOADER_CONFIG_PAGE } from '@/router/config'
|
import { PICBEDS_PAGE, UPLOADER_CONFIG_PAGE } from '@/router/config'
|
||||||
import { configPaths } from '@/utils/configPaths'
|
import { configPaths } from '@/utils/configPaths'
|
||||||
@@ -105,6 +98,8 @@ import { IRPCActionType } from '@/utils/enum'
|
|||||||
import type { IStringKeyMap, IUploaderConfigItem } from '#/types/types'
|
import type { IStringKeyMap, IUploaderConfigItem } from '#/types/types'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const message = useMessage()
|
||||||
|
const { confirm } = useConfirm()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -157,14 +152,25 @@ function openEditPage (configId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatTime (time: number): string {
|
function formatTime (time: number): string {
|
||||||
return dayjs(time).format('YY-MM-DD HH:mm')
|
return dayjs(time).format('YYYY-MM-DD HH:mm')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteConfig (id: string) {
|
async function deleteConfig (id: string) {
|
||||||
const res = await window.electron.triggerRPC<IUploaderConfigItem>(IRPCActionType.PICBED_DELETE_CONFIG, type.value, id)
|
confirm({
|
||||||
if (!res) return
|
title: t('pages.uploaderConfig.deleteTitle'),
|
||||||
curConfigList.value = res.configList
|
message: t('pages.uploaderConfig.deleteConfirm'),
|
||||||
defaultConfigId.value = res.defaultId
|
type: 'warning',
|
||||||
|
confirmButtonText: t('common.confirm'),
|
||||||
|
cancelButtonText: t('common.cancel'),
|
||||||
|
center: true
|
||||||
|
}).then(async result => {
|
||||||
|
if (!result) return
|
||||||
|
const res = await window.electron.triggerRPC<IUploaderConfigItem>(IRPCActionType.PICBED_DELETE_CONFIG, type.value, id)
|
||||||
|
if (!res) return
|
||||||
|
curConfigList.value = res.configList
|
||||||
|
defaultConfigId.value = res.defaultId
|
||||||
|
message.success(t('pages.uploaderConfig.deleteSuccess'))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewConfig () {
|
function addNewConfig () {
|
||||||
@@ -186,12 +192,7 @@ function setDefaultPicBed (type: string) {
|
|||||||
store?.setDefaultPicBed(type)
|
store?.setDefaultPicBed(type)
|
||||||
const currentConfigName = curConfigList.value.find(item => item._id === defaultConfigId.value)?._configName
|
const currentConfigName = curConfigList.value.find(item => item._id === defaultConfigId.value)?._configName
|
||||||
window.electron.sendRPC(IRPCActionType.TRAY_SET_TOOL_TIP, `${type} ${currentConfigName || ''}`)
|
window.electron.sendRPC(IRPCActionType.TRAY_SET_TOOL_TIP, `${type} ${currentConfigName || ''}`)
|
||||||
const successNotification = new Notification(t('SETTINGS_DEFAULT_PICBED'), {
|
message.success(t('pages.uploaderConfig.setSuccess'))
|
||||||
body: t('TIPS_SET_SUCCEED')
|
|
||||||
})
|
|
||||||
successNotification.onclick = () => {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -199,80 +200,301 @@ export default {
|
|||||||
name: 'UploaderConfigPage'
|
name: 'UploaderConfigPage'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="stylus">
|
<style scoped>
|
||||||
#config-list-view
|
/* Container */
|
||||||
position absolute
|
.config-container {
|
||||||
min-height 100%
|
padding: 1rem;
|
||||||
left 162px
|
width: 100%;
|
||||||
right 0
|
margin: 0;
|
||||||
overflow-x hidden
|
display: flex;
|
||||||
overflow-y auto
|
flex-direction: column;
|
||||||
padding-bottom 50px
|
gap: 1.25rem;
|
||||||
box-sizing border-box
|
min-height: 100vh;
|
||||||
.config-list
|
box-sizing: border-box;
|
||||||
flex-wrap wrap
|
overflow-y: auto;
|
||||||
width: 98%
|
}
|
||||||
.config-item
|
|
||||||
height 85px
|
/* Card Base */
|
||||||
margin-bottom 20px
|
.config-card {
|
||||||
border-radius 4px
|
background: var(--color-surface);
|
||||||
cursor pointer
|
border: 1px solid var(--color-border-secondary);
|
||||||
box-sizing border-box
|
border-radius: var(--radius-xl);
|
||||||
padding 8px
|
overflow: hidden;
|
||||||
background rgba(130, 130, 130, .2)
|
transition: var(--transition-medium);
|
||||||
border 1px solid transparent
|
box-shadow: var(--shadow-sm);
|
||||||
box-shadow 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)
|
}
|
||||||
position relative
|
|
||||||
.config-name
|
.config-card:hover {
|
||||||
color #eee
|
box-shadow: var(--shadow-md);
|
||||||
font-size 16px
|
border-color: var(--color-border);
|
||||||
.config-update-time
|
}
|
||||||
color #aaa
|
|
||||||
font-size 14px
|
/* Header Card */
|
||||||
margin-top 10px
|
.header-card .card-header {
|
||||||
.default-text
|
padding: 1.5rem 2rem;
|
||||||
color #67C23A
|
border-bottom: 1px solid var(--color-border-secondary);
|
||||||
font-size 12px
|
}
|
||||||
margin-top 5px
|
|
||||||
.operation-container
|
.page-title {
|
||||||
position absolute
|
font-size: 1.5rem;
|
||||||
right 5px
|
font-weight: 600;
|
||||||
top 8px
|
color: var(--color-text-primary);
|
||||||
left 0
|
margin: 0;
|
||||||
font-size 18pxc
|
letter-spacing: -0.025em;
|
||||||
word-break break-all
|
}
|
||||||
display flex
|
|
||||||
align-items center
|
/* Main Card */
|
||||||
color #eee
|
.main-card {
|
||||||
.el-icon-edit
|
background: var( --color-background-tertiary);
|
||||||
right 20px
|
padding: 1.5rem;
|
||||||
position absolute
|
}
|
||||||
top 2px
|
|
||||||
margin-right 10px
|
.config-grid {
|
||||||
cursor pointer
|
display: grid;
|
||||||
.el-icon-delete
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
position absolute
|
gap: 1rem;
|
||||||
top 2px
|
width: 100%;
|
||||||
margin-right 10px
|
}
|
||||||
right 0
|
|
||||||
cursor pointer
|
@media (max-width: 768px) {
|
||||||
.el-icon-edit
|
.config-grid {
|
||||||
margin-right 10px
|
grid-template-columns: 1fr;
|
||||||
.disabled
|
gap: 0.75rem;
|
||||||
cursor not-allowed
|
}
|
||||||
color #aaa
|
}
|
||||||
.config-item-add
|
|
||||||
display: flex
|
@media (min-width: 1200px) {
|
||||||
justify-content: center
|
.config-grid {
|
||||||
align-items: center
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
color: #eee
|
gap: 1.25rem;
|
||||||
font-size: 28px
|
}
|
||||||
.selected
|
}
|
||||||
border 1px solid #409EFF
|
|
||||||
.set-default-container
|
/* Config Items */
|
||||||
position absolute
|
.config-item {
|
||||||
bottom 10px
|
background: var(--color-surface-elevated);
|
||||||
width 100%
|
border: 1px solid var(--color-border);
|
||||||
.set-default-btn
|
border-radius: var(--radius-lg);
|
||||||
width 250px
|
padding: 1.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-medium);
|
||||||
|
position: relative;
|
||||||
|
min-height: 120px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-item:hover {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-item.selected {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
background: var(--color-surface);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-content {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-update-time {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: white;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
background: var(--color-surface);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-btn:hover {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn:hover:not(.disabled) {
|
||||||
|
border-color: var(--color-danger);
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add New Config Item */
|
||||||
|
.config-item-add {
|
||||||
|
border: 2px dashed var(--color-border);
|
||||||
|
background: var(--color-surface);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-item-add:hover {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-item-add:hover .add-content {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-text {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions Card */
|
||||||
|
.actions-card {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-actions {
|
||||||
|
padding: 1.25rem 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.875rem 2rem;
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
font-family: inherit;
|
||||||
|
min-width: 200px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button:hover:not(:disabled) {
|
||||||
|
background: var(--color-accent-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button:disabled {
|
||||||
|
background: var(--color-border);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.config-container {
|
||||||
|
padding: 0.75rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-card .card-header {
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-card {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-item {
|
||||||
|
padding: 1rem;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-actions {
|
||||||
|
top: 0.75rem;
|
||||||
|
right: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.config-container {
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -740,11 +740,11 @@ small {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-item code {
|
.placeholder-item code {
|
||||||
background: linear-gradient(135deg, var(--color-accent) 0%, #667eea 100%);
|
background: var(--color-blue-common);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 0.3rem 0.6rem;
|
padding: 0.3rem 0.6rem;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 0.75rem;
|
font-size: 1rem;
|
||||||
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
margin-right: 0.875rem;
|
margin-right: 0.875rem;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
@@ -808,6 +808,6 @@ small {
|
|||||||
|
|
||||||
:root.dark .placeholder-item code,
|
:root.dark .placeholder-item code,
|
||||||
:root.auto.dark .placeholder-item code {
|
:root.auto.dark .placeholder-item code {
|
||||||
background: linear-gradient(135deg, var(--color-accent) 0%, #764ba2 100%);
|
background: var(--color-blue-common);
|
||||||
border-color: rgba(255, 255, 255, 0.15);
|
border-color: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|||||||
772
src/renderer/pages/css/PluginPage.css
Normal file
772
src/renderer/pages/css/PluginPage.css
Normal file
@@ -0,0 +1,772 @@
|
|||||||
|
/* Global scrolling behavior */
|
||||||
|
html, body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.plugin-container {
|
||||||
|
padding: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.25rem;
|
||||||
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Base */
|
||||||
|
.plugin-card {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border-secondary);
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: var(--transition-medium);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card:hover {
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Card */
|
||||||
|
.header-card .card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--color-border-secondary);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
color: var(--color-accent);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content p {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.625rem 1rem;
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover {
|
||||||
|
background: var(--color-accent-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.secondary {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.secondary:hover {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.small {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Card */
|
||||||
|
.search-card {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 1rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem 0.75rem 3rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input::placeholder {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-button:hover {
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notice Card */
|
||||||
|
.notice-card {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
border-color: var(--color-warning);
|
||||||
|
background: linear-gradient(135deg, rgba(255, 193, 7, 0.1) 0%, var(--color-surface) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-icon {
|
||||||
|
color: var(--color-warning);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading */
|
||||||
|
.loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
z-index: 100;
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid var(--color-border);
|
||||||
|
border-top: 3px solid var(--color-accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: white;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin Grid */
|
||||||
|
.plugin-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 1.25rem;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin Item Card */
|
||||||
|
.plugin-item-card {
|
||||||
|
position: relative;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: 1.5rem;
|
||||||
|
transition: var(--transition-medium);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 200px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-item-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-item-card.disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin Badges */
|
||||||
|
.cli-badge,
|
||||||
|
.update-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cli-badge {
|
||||||
|
background: var(--color-info);
|
||||||
|
color: var(--color-blue-common);
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-badge {
|
||||||
|
background: var(--color-success);
|
||||||
|
color: white;
|
||||||
|
right: 3.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin Header */
|
||||||
|
.plugin-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-logo {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
object-fit: cover;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 1px solid var(--color-border-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0 0 0.25rem 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-name:hover {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-version {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-author {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin Description */
|
||||||
|
.plugin-description {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-description p {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
min-height: 2.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin Actions */
|
||||||
|
.plugin-actions {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-button {
|
||||||
|
background: var(--color-success);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-button:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.installing-button,
|
||||||
|
.processing-button {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.installed-button {
|
||||||
|
background: var(--color-success-light);
|
||||||
|
color: var(--color-success);
|
||||||
|
border: 1px solid var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button {
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button:hover:not(:disabled) {
|
||||||
|
background: var(--color-accent-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-button {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-button:hover:not(:disabled) {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-color: var(--color-warning);
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Spinner */
|
||||||
|
.button-spinner {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-top: 2px solid currentColor;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty-state {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content h3 {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content p {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
box-shadow: var(--shadow-xl);
|
||||||
|
max-width: 70vw;
|
||||||
|
max-height: 80vh;
|
||||||
|
width: 100%;
|
||||||
|
margin: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1.5rem 1.5rem 0 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content::-webkit-scrollbar-track {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--color-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-top: 1px solid var(--color-border-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background: var(--color-accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover:not(:disabled) {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transitions */
|
||||||
|
.notice-enter-active,
|
||||||
|
.notice-leave-active {
|
||||||
|
transition: all var(--transition-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-enter-from,
|
||||||
|
.notice-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-active,
|
||||||
|
.modal-leave-active {
|
||||||
|
transition: all var(--transition-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-from,
|
||||||
|
.modal-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.plugin-container {
|
||||||
|
padding: 0.75rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-card .card-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
max-width: 95vw;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
min-width: 120px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.plugin-container {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-item-card {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode adjustments */
|
||||||
|
:root.dark .plugin-container,
|
||||||
|
:root.auto.dark .plugin-container {
|
||||||
|
background: var(--color-background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .plugin-card,
|
||||||
|
:root.auto.dark .plugin-card {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-color: var(--color-border-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .search-input,
|
||||||
|
:root.auto.dark .search-input {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .search-input:focus,
|
||||||
|
:root.auto.dark .search-input:focus {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .modal-container,
|
||||||
|
:root.auto.dark .modal-container {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .btn-secondary,
|
||||||
|
:root.auto.dark .btn-secondary {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .btn-secondary:hover,
|
||||||
|
:root.auto.dark .btn-secondary:hover {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accessibility */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
* {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles for keyboard navigation */
|
||||||
|
.action-button:focus-visible,
|
||||||
|
.plugin-button:focus-visible,
|
||||||
|
.btn:focus-visible,
|
||||||
|
.search-input:focus-visible,
|
||||||
|
.clear-button:focus-visible,
|
||||||
|
.modal-close:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-name:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
@@ -1,104 +1,153 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="picbeds-page">
|
<div id="picbeds-page">
|
||||||
<el-row
|
<div class="page-container">
|
||||||
:gutter="20"
|
<div class="page-content">
|
||||||
class="setting-list"
|
<!-- Header Section -->
|
||||||
>
|
<div class="page-header">
|
||||||
<el-col
|
<div class="header-title-section">
|
||||||
:span="22"
|
<h1
|
||||||
:offset="1"
|
class="page-title"
|
||||||
>
|
@click="handleNameClick"
|
||||||
<div class="view-title">
|
>
|
||||||
<span
|
{{ picBedName }} {{ t('pages.picBedConfigs.title') }}
|
||||||
class="view-title-text"
|
</h1>
|
||||||
@click="handleNameClick"
|
<button
|
||||||
> {{ picBedName }} {{ $t('SETTINGS') }}</span>
|
class="link-button"
|
||||||
<el-icon>
|
:title="t('pages.picBedConfigs.viewDoc')"
|
||||||
<Link />
|
@click="handleNameClick"
|
||||||
</el-icon>
|
>
|
||||||
<el-button
|
<ExternalLink :size="20" />
|
||||||
type="primary"
|
</button>
|
||||||
round
|
</div>
|
||||||
size="small"
|
<button
|
||||||
style="margin-left: 6px"
|
class="action-button primary"
|
||||||
@click="handleCopyApi"
|
@click="handleCopyApi"
|
||||||
>
|
>
|
||||||
{{ $t('UPLOAD_PAGE_COPY_UPLOAD_API') }}
|
<Copy :size="20" />
|
||||||
</el-button>
|
{{ t('pages.picBedConfigs.copyAPI') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<config-form
|
|
||||||
|
<!-- Config Form Section -->
|
||||||
|
<div
|
||||||
v-if="config.length > 0"
|
v-if="config.length > 0"
|
||||||
:id="type"
|
class="form-section"
|
||||||
ref="$configForm"
|
|
||||||
:config="config"
|
|
||||||
type="uploader"
|
|
||||||
>
|
>
|
||||||
<el-form-item>
|
<config-form
|
||||||
<el-button-group>
|
:id="type"
|
||||||
<el-button
|
ref="$configForm"
|
||||||
type="info"
|
:config="config"
|
||||||
round
|
type="uploader"
|
||||||
|
color-mode="white"
|
||||||
|
>
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button
|
||||||
|
class="action-button secondary"
|
||||||
|
type="button"
|
||||||
@click="handleReset"
|
@click="handleReset"
|
||||||
>
|
>
|
||||||
{{ $t('RESET_PICBED_CONFIG') }}
|
<RotateCcw :size="18" />
|
||||||
</el-button>
|
{{ t('common.reset') }}
|
||||||
<el-button
|
</button>
|
||||||
type="success"
|
|
||||||
round
|
<button
|
||||||
|
class="action-button success"
|
||||||
|
type="button"
|
||||||
@click="handleConfirm"
|
@click="handleConfirm"
|
||||||
>
|
>
|
||||||
{{ $t('CONFIRM') }}
|
<Check :size="18" />
|
||||||
</el-button>
|
{{ t('common.confirm') }}
|
||||||
<el-button
|
</button>
|
||||||
round
|
|
||||||
type="warning"
|
<div
|
||||||
@mouseenter="handleMouseEnter"
|
v-if="picBedConfigList.length > 0"
|
||||||
@mouseleave="handleMouseLeave"
|
class="dropdown-wrapper"
|
||||||
>
|
>
|
||||||
<el-dropdown
|
<button
|
||||||
ref="$dropdown"
|
class="action-button warning dropdown-trigger"
|
||||||
placement="top"
|
type="button"
|
||||||
style="color: #fff; font-size: 12px; width: 100%"
|
@click="toggleDropdown"
|
||||||
:disabled="picBedConfigList.length === 0"
|
@blur="handleDropdownBlur"
|
||||||
teleported
|
|
||||||
>
|
>
|
||||||
{{ $t('MANAGE_LOGIN_PAGE_PANE_IMPORT') }}
|
<Import :size="18" />
|
||||||
<template #dropdown>
|
{{ t('common.import') }}
|
||||||
<el-dropdown-item
|
<svg
|
||||||
v-for="i in picBedConfigList"
|
class="dropdown-arrow"
|
||||||
:key="i._id"
|
:class="{ 'dropdown-arrow-up': true }"
|
||||||
@click="handleConfigImport(i)"
|
width="12"
|
||||||
|
height="8"
|
||||||
|
viewBox="0 0 12 8"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M1 1.5L6 6.5L11 1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<transition name="dropdown">
|
||||||
|
<div
|
||||||
|
v-show="dropdownVisible"
|
||||||
|
class="dropdown-menu"
|
||||||
|
:class="{ 'dropdown-up': true }"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-for="item in picBedConfigList"
|
||||||
|
:key="item._id"
|
||||||
|
class="dropdown-item"
|
||||||
|
@click="handleConfigImport(item)"
|
||||||
>
|
>
|
||||||
{{ i._configName }}
|
{{ item._configName }}
|
||||||
</el-dropdown-item>
|
</button>
|
||||||
</template>
|
</div>
|
||||||
</el-dropdown>
|
</transition>
|
||||||
</el-button>
|
</div>
|
||||||
</el-button-group>
|
</div>
|
||||||
</el-form-item>
|
</config-form>
|
||||||
</config-form>
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="single"
|
class="empty-state"
|
||||||
>
|
>
|
||||||
<div class="notice">
|
<div class="empty-content">
|
||||||
{{ $t('SETTINGS_NOT_CONFIG_OPTIONS') }}
|
<FolderOpen
|
||||||
|
class="empty-icon"
|
||||||
|
:size="48"
|
||||||
|
/>
|
||||||
|
<h3>{{ t('pages.picBedConfigs.noConfigOptions') }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading Overlay -->
|
||||||
|
<div
|
||||||
|
v-if="loading"
|
||||||
|
class="loading-overlay"
|
||||||
|
>
|
||||||
|
<div class="loading-spinner" />
|
||||||
|
<span class="loading-text">Loading...</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Link } from '@element-plus/icons-vue'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { ElDropdown, ElMessage } from 'element-plus'
|
import { Check, Copy, ExternalLink, FolderOpen, Import, RotateCcw } from 'lucide-vue-next'
|
||||||
import { onBeforeMount, ref } from 'vue'
|
import { onBeforeMount, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
import ConfigForm from '@/components/ConfigForm.vue'
|
import ConfigForm from '@/components/UnifiedConfigForm.vue'
|
||||||
|
import useMessage from '@/hooks/useMessage'
|
||||||
|
import { getRawData } from '@/utils/common'
|
||||||
import { configPaths } from '@/utils/configPaths'
|
import { configPaths } from '@/utils/configPaths'
|
||||||
import { getConfig } from '@/utils/dataSender'
|
import { getConfig } from '@/utils/dataSender'
|
||||||
import { II18nLanguage, IRPCActionType } from '@/utils/enum'
|
import { II18nLanguage, IRPCActionType } from '@/utils/enum'
|
||||||
@@ -106,82 +155,126 @@ import { picBedManualUrlList } from '@/utils/static'
|
|||||||
import type { IPicGoPluginConfig, IStringKeyMap, IUploaderConfigItem, IUploaderConfigListItem } from '#/types/types'
|
import type { IPicGoPluginConfig, IStringKeyMap, IUploaderConfigItem, IUploaderConfigListItem } from '#/types/types'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
const type = ref('')
|
const type = ref('')
|
||||||
const config = ref<IPicGoPluginConfig[]>([])
|
const config = ref<IPicGoPluginConfig[]>([])
|
||||||
const picBedConfigList = ref<IUploaderConfigListItem[]>([])
|
const picBedConfigList = ref<IUploaderConfigListItem[]>([])
|
||||||
const picBedName = ref('')
|
const picBedName = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
const dropdownVisible = ref(false)
|
||||||
const $route = useRoute()
|
const $route = useRoute()
|
||||||
const $router = useRouter()
|
const $router = useRouter()
|
||||||
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
|
const $configForm = ref<InstanceType<typeof ConfigForm> | null>(null)
|
||||||
const $dropdown = ref<InstanceType<typeof ElDropdown> | null>(null)
|
|
||||||
type.value = $route.params.type as string
|
type.value = $route.params.type as string
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
await getPicBeds()
|
loading.value = true
|
||||||
await getPicBedConfigList()
|
try {
|
||||||
|
await getPicBeds()
|
||||||
|
await getPicBedConfigList()
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function toggleDropdown (_: Event) {
|
||||||
|
dropdownVisible.value = !dropdownVisible.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDropdownBlur () {
|
||||||
|
setTimeout(() => {
|
||||||
|
dropdownVisible.value = false
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
const result = (await $configForm.value?.validate()) || false
|
loading.value = true
|
||||||
if (result !== false) {
|
try {
|
||||||
await window.electron.triggerRPC<void>(IRPCActionType.UPLOADER_UPDATE_CONFIG, type.value, result?._id, result)
|
const result = (await $configForm.value?.validate()) || false
|
||||||
const successNotification = new Notification(t('SETTINGS_RESULT'), {
|
if (result !== false) {
|
||||||
body: t('TIPS_SET_SUCCEED')
|
const rawResult = getRawData(result)
|
||||||
})
|
await window.electron.triggerRPC<void>(IRPCActionType.UPLOADER_UPDATE_CONFIG, type.value, rawResult?._id, rawResult)
|
||||||
successNotification.onclick = () => {
|
message.success(t('pages.picBedConfigs.setSuccess'))
|
||||||
return true
|
$router.back()
|
||||||
|
} else {
|
||||||
|
message.error(t('pages.picBedConfigs.setFailedInfo'))
|
||||||
}
|
}
|
||||||
$router.back()
|
} catch (error) {
|
||||||
|
console.error('Failed to save configuration:', error)
|
||||||
|
message.error(t('pages.picBedConfigs.setFailedInfo'))
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseEnter () {
|
|
||||||
$dropdown.value?.handleOpen()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseLeave () {
|
|
||||||
$dropdown.value?.handleClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPicBeds () {
|
async function getPicBeds () {
|
||||||
const result = await window.electron.triggerRPC<any>(IRPCActionType.PICBED_GET_PICBED_CONFIG, $route.params.type)
|
try {
|
||||||
config.value = result.config
|
const result = await window.electron.triggerRPC<any>(IRPCActionType.PICBED_GET_PICBED_CONFIG, $route.params.type)
|
||||||
picBedName.value = result.name
|
config.value = result.config
|
||||||
|
picBedName.value = result.name
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get picbed config:', error)
|
||||||
|
message.error(t('pages.picBedConfigs.loadConfigFailed'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPicBedConfigList () {
|
async function getPicBedConfigList () {
|
||||||
const res = (await window.electron.triggerRPC<IUploaderConfigItem>(IRPCActionType.PICBED_GET_CONFIG_LIST, type.value)) || undefined
|
try {
|
||||||
const configList = res?.configList || []
|
const res = (await window.electron.triggerRPC<IUploaderConfigItem>(IRPCActionType.PICBED_GET_CONFIG_LIST, type.value)) || undefined
|
||||||
picBedConfigList.value = configList.filter(item => item._id !== $route.params.configId)
|
const configList = res?.configList || []
|
||||||
|
picBedConfigList.value = configList.filter(item => item._id !== $route.params.configId)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get config list:', error)
|
||||||
|
message.error(t('pages.picBedConfigs.loadPicBedListFailed'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleConfigImport (configItem: IUploaderConfigListItem) {
|
async function handleConfigImport (configItem: IUploaderConfigListItem) {
|
||||||
const { _id, _configName, _updatedAt, _createdAt, ...rest } = configItem
|
try {
|
||||||
for (const key in rest) {
|
const { _id, _configName, _updatedAt, _createdAt, ...rest } = configItem
|
||||||
if (Object.prototype.hasOwnProperty.call(rest, key)) {
|
for (const key in rest) {
|
||||||
const value = rest[key]
|
if (Object.prototype.hasOwnProperty.call(rest, key)) {
|
||||||
$configForm.value?.updateRuleForm(key, value)
|
const value = rest[key]
|
||||||
|
$configForm.value?.updateRuleForm(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$configForm.value?.updateRuleForm('_configName', dayjs().format('YYYYMMDDHHmmss'))
|
||||||
|
dropdownVisible.value = false
|
||||||
|
message.success(t('pages.picBedConfigs.importConfigSuccess'))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to import configuration:', error)
|
||||||
|
message.error(t('pages.picBedConfigs.importConfigFailed'))
|
||||||
}
|
}
|
||||||
$configForm.value?.updateRuleForm('_configName', dayjs().format('YYYYMMDDHHmmss'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReset = async () => {
|
const handleReset = async () => {
|
||||||
await window.electron.triggerRPC<void>(IRPCActionType.UPLOADER_RESET_CONFIG, type.value, $route.params.configId)
|
loading.value = true
|
||||||
const successNotification = new Notification(t('SETTINGS_RESULT'), {
|
try {
|
||||||
body: t('TIPS_RESET_SUCCEED')
|
await window.electron.triggerRPC<void>(IRPCActionType.UPLOADER_RESET_CONFIG, type.value, $route.params.configId)
|
||||||
})
|
message.success(t('pages.picBedConfigs.resetSuccess'))
|
||||||
successNotification.onclick = () => {
|
setTimeout(() => {
|
||||||
return true
|
$router.back()
|
||||||
|
}, 1000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to reset configuration:', error)
|
||||||
|
message.error(t('pages.picBedConfigs.resetFailed'))
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
$router.back()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleNameClick () {
|
async function handleNameClick () {
|
||||||
const lang = (await getConfig(configPaths.settings.language)) || II18nLanguage.ZH_CN
|
try {
|
||||||
const url = picBedManualUrlList[lang === II18nLanguage.EN ? 'en' : 'zh_cn'][$route.params.type as string]
|
const lang = (await getConfig(configPaths.settings.language)) || II18nLanguage.ZH_CN
|
||||||
if (url) {
|
const url = picBedManualUrlList[lang === II18nLanguage.EN ? 'en' : 'zh_cn'][$route.params.type as string]
|
||||||
window.electron.sendRPC(IRPCActionType.OPEN_URL, url)
|
if (url) {
|
||||||
|
window.electron.sendRPC(IRPCActionType.OPEN_URL, url)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to open documentation:', error)
|
||||||
|
message.error(t('pages.picBedConfigs.viewDocFailed'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,16 +285,24 @@ async function handleCopyApi () {
|
|||||||
const uploader = ((await getConfig(configPaths.uploader)) as IStringKeyMap) || {}
|
const uploader = ((await getConfig(configPaths.uploader)) as IStringKeyMap) || {}
|
||||||
const picBedConfigList = uploader[$route.params.type as string].configList || []
|
const picBedConfigList = uploader[$route.params.type as string].configList || []
|
||||||
const picBedConfig = picBedConfigList.find((item: IUploaderConfigListItem) => item._id === $route.params.configId)
|
const picBedConfig = picBedConfigList.find((item: IUploaderConfigListItem) => item._id === $route.params.configId)
|
||||||
|
|
||||||
if (!picBedConfig) {
|
if (!picBedConfig) {
|
||||||
ElMessage.error('No config found')
|
message.error(t('pages.picBedConfigs.noConfigs'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiUrl = `http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${port}/upload?picbed=${$route.params.type}&configName=${picBedConfig._configName}${serverKey ? `&key=${serverKey}` : ''}`
|
const apiUrl = `http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${port}/upload?picbed=${$route.params.type}&configName=${picBedConfig._configName}${serverKey ? `&key=${serverKey}` : ''}`
|
||||||
window.electron.clipboard.writeText(apiUrl)
|
|
||||||
ElMessage.success(`${t('MANAGE_BUCKET_COPY_SUCCESS')} ${apiUrl}`)
|
try {
|
||||||
|
window.electron.clipboard.writeText(apiUrl)
|
||||||
|
message.success(`${t('pages.picBedConfigs.copyAPISucceed')} ${apiUrl}`)
|
||||||
|
} catch (clipboardError) {
|
||||||
|
console.error('Failed to copy to clipboard:', clipboardError)
|
||||||
|
message.error(t('pages.picBedConfigs.copyAPIFailed'))
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.error('Failed to generate API URL:', error)
|
||||||
ElMessage.error('Copy failed')
|
message.error(t('pages.picBedConfigs.copyAPIFailed'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -212,43 +313,428 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style scoped>
|
||||||
#picbeds-page
|
#picbeds-page {
|
||||||
height 100%
|
min-height: 100vh;
|
||||||
overflow-y auto
|
background: var(--color-background-tertiar);
|
||||||
overflow-x hidden
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
position absolute
|
position: relative;
|
||||||
left 142px
|
}
|
||||||
right 0
|
|
||||||
.setting-list
|
.page-container {
|
||||||
height 100%
|
max-width: 1000px;
|
||||||
overflow-y auto
|
margin: 0 auto;
|
||||||
overflow-x hidden
|
padding: 2rem;
|
||||||
.view-title-text
|
}
|
||||||
&:hover
|
|
||||||
cursor pointer
|
.page-content {
|
||||||
color #409EFF
|
background: rgba(255, 255, 255, 0.95);
|
||||||
.el-form
|
border-radius: 16px;
|
||||||
label
|
backdrop-filter: blur(20px);
|
||||||
line-height 22px
|
box-shadow:
|
||||||
padding-bottom 0
|
0 8px 16px rgba(0, 0, 0, 0.12),
|
||||||
color #eee
|
0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
&-item
|
overflow: auto;
|
||||||
margin-bottom 16px
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
.el-button-group
|
}
|
||||||
width 100%
|
|
||||||
.el-button
|
/* Header Section */
|
||||||
width calc(33.3333% - 10px)
|
.page-header {
|
||||||
.el-radio-group
|
display: flex;
|
||||||
margin-left 25px
|
align-items: center;
|
||||||
.el-switch__label
|
justify-content: space-between;
|
||||||
color #eee
|
padding: 2rem 2rem 1.5rem;
|
||||||
&.is-active
|
border-bottom: 1px solid rgba(229, 231, 235, 0.8);
|
||||||
color #409EFF
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.8) 0%, rgba(248, 250, 252, 0.8) 100%);
|
||||||
.notice
|
}
|
||||||
color #eee
|
|
||||||
text-align center
|
.header-title-section {
|
||||||
margin-bottom 10px
|
display: flex;
|
||||||
.single
|
align-items: center;
|
||||||
text-align center
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
background: linear-gradient(135deg, #1f2937 0%, #4b5563 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
filter: brightness(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
color: #3b82f6;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-button:hover {
|
||||||
|
background: rgba(59, 130, 246, 0.2);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Buttons */
|
||||||
|
.action-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
font-family: inherit;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||||
|
transition: left 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.primary {
|
||||||
|
background: var(--color-blue-common);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 2px 2px rgba(0, 122, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 122, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.secondary {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: #475569;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.secondary:hover {
|
||||||
|
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.success {
|
||||||
|
background: var(--color-success);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 4px rgba(16, 185, 129, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.success:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 6px rgba(16, 185, 129, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.warning {
|
||||||
|
background: var(--color-warning);
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.warning:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 6px rgba(245, 158, 11, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow-up {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger:hover .dropdown-arrow:not(.dropdown-arrow-up) {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger:hover .dropdown-arrow.dropdown-arrow-up {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Section */
|
||||||
|
.form-section {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown */
|
||||||
|
.dropdown-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1000;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu.dropdown-up {
|
||||||
|
top: auto;
|
||||||
|
bottom: 100%;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:hover {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #007aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty-state {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content p {
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading Overlay */
|
||||||
|
.loading-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top: 3px solid white;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: white;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transitions */
|
||||||
|
.dropdown-enter-active,
|
||||||
|
.dropdown-leave-active {
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-enter-from,
|
||||||
|
.dropdown-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-up.dropdown-enter-from,
|
||||||
|
.dropdown-up.dropdown-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode adjustments */
|
||||||
|
:root.dark #picbeds-page,
|
||||||
|
:root.auto.dark #picbeds-page {
|
||||||
|
background: var(--color-background-tertiar);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .page-content,
|
||||||
|
:root.auto.dark .page-content {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border-color: rgba(75, 85, 99, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .page-header,
|
||||||
|
:root.auto.dark .page-header {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
border-color: rgba(75, 85, 99, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .page-title,
|
||||||
|
:root.auto.dark .page-title {
|
||||||
|
background: linear-gradient(135deg, #f9fafb 0%, #d1d5db 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .dropdown-menu,
|
||||||
|
:root.auto.dark .dropdown-menu {
|
||||||
|
background: #374151;
|
||||||
|
border-color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .dropdown-menu.dropdown-up,
|
||||||
|
:root.auto.dark .dropdown-menu.dropdown-up {
|
||||||
|
background: #374151;
|
||||||
|
border-color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .dropdown-item,
|
||||||
|
:root.auto.dark .dropdown-item {
|
||||||
|
color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .dropdown-item:hover,
|
||||||
|
:root.auto.dark .dropdown-item:hover {
|
||||||
|
background: #4b5563;
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.page-container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title-section {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
right: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
top: 1rem;
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.page-container {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles for accessibility */
|
||||||
|
.action-button:focus-visible,
|
||||||
|
.link-button:focus-visible,
|
||||||
|
.dropdown-item:focus-visible {
|
||||||
|
outline: 2px solid #007aff;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
55
yarn.lock
55
yarn.lock
@@ -5809,6 +5809,13 @@ electron-builder@^26.0.12:
|
|||||||
simple-update-notifier "2.0.0"
|
simple-update-notifier "2.0.0"
|
||||||
yargs "^17.6.2"
|
yargs "^17.6.2"
|
||||||
|
|
||||||
|
electron-devtools-installer@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-devtools-installer/-/electron-devtools-installer-4.0.0.tgz#6556a6a326cddea18194cb6d97d85c8ae329dedf"
|
||||||
|
integrity sha512-9Tntu/jtfSn0n6N/ZI6IdvRqXpDyLQiDuuIbsBI+dL+1Ef7C8J2JwByw58P3TJiNeuqyV3ZkphpNWuZK5iSY2w==
|
||||||
|
dependencies:
|
||||||
|
unzip-crx-3 "^0.2.0"
|
||||||
|
|
||||||
electron-publish@26.0.11:
|
electron-publish@26.0.11:
|
||||||
version "26.0.11"
|
version "26.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-26.0.11.tgz#92c9329a101af2836d9d228c82966eca1eee9a7b"
|
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-26.0.11.tgz#92c9329a101af2836d9d228c82966eca1eee9a7b"
|
||||||
@@ -7504,6 +7511,11 @@ image-size@^2.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/image-size/-/image-size-2.0.2.tgz#84a7b43704db5736f364bf0d1b029821299b4bdc"
|
resolved "https://registry.yarnpkg.com/image-size/-/image-size-2.0.2.tgz#84a7b43704db5736f364bf0d1b029821299b4bdc"
|
||||||
integrity sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==
|
integrity sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==
|
||||||
|
|
||||||
|
immediate@~3.0.5:
|
||||||
|
version "3.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||||
|
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
|
||||||
|
|
||||||
import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0:
|
import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||||
@@ -8058,6 +8070,16 @@ jstoxml@^2.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/jstoxml/-/jstoxml-2.2.9.tgz#2eebd5e55383fe66a375022ca0aa88f77bc4fb84"
|
resolved "https://registry.npmmirror.com/jstoxml/-/jstoxml-2.2.9.tgz#2eebd5e55383fe66a375022ca0aa88f77bc4fb84"
|
||||||
integrity sha512-OYWlK0j+roh+eyaMROlNbS5cd5R25Y+IUpdl7cNdB8HNrkgwQzIS7L9MegxOiWNBj9dQhA/yAxiMwCC5mwNoBw==
|
integrity sha512-OYWlK0j+roh+eyaMROlNbS5cd5R25Y+IUpdl7cNdB8HNrkgwQzIS7L9MegxOiWNBj9dQhA/yAxiMwCC5mwNoBw==
|
||||||
|
|
||||||
|
jszip@^3.1.0:
|
||||||
|
version "3.10.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
|
||||||
|
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
|
||||||
|
dependencies:
|
||||||
|
lie "~3.3.0"
|
||||||
|
pako "~1.0.2"
|
||||||
|
readable-stream "~2.3.6"
|
||||||
|
setimmediate "^1.0.5"
|
||||||
|
|
||||||
keycode@2.2.0:
|
keycode@2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
|
resolved "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
|
||||||
@@ -8107,6 +8129,13 @@ libheif-js@^1.19.8:
|
|||||||
resolved "https://registry.yarnpkg.com/libheif-js/-/libheif-js-1.19.8.tgz#fcbf3571ef28b6199dd052bc4d2cb7cce56ddf06"
|
resolved "https://registry.yarnpkg.com/libheif-js/-/libheif-js-1.19.8.tgz#fcbf3571ef28b6199dd052bc4d2cb7cce56ddf06"
|
||||||
integrity sha512-vQJWusIxO7wavpON1dusciL8Go9jsIQ+EUrckauFYAiSTjcmLAsuJh3SszLpvkwPci3JcL41ek2n+LUZGFpPIQ==
|
integrity sha512-vQJWusIxO7wavpON1dusciL8Go9jsIQ+EUrckauFYAiSTjcmLAsuJh3SszLpvkwPci3JcL41ek2n+LUZGFpPIQ==
|
||||||
|
|
||||||
|
lie@~3.3.0:
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
|
||||||
|
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
|
||||||
|
dependencies:
|
||||||
|
immediate "~3.0.5"
|
||||||
|
|
||||||
lines-and-columns@^1.1.6:
|
lines-and-columns@^1.1.6:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
||||||
@@ -9257,6 +9286,11 @@ package-json-from-dist@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
|
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
|
||||||
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
|
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
|
||||||
|
|
||||||
|
pako@~1.0.2:
|
||||||
|
version "1.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||||
|
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||||
|
|
||||||
parent-module@^1.0.0:
|
parent-module@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||||
@@ -9723,7 +9757,7 @@ read-binary-file-arch@^1.0.6:
|
|||||||
dependencies:
|
dependencies:
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
|
||||||
readable-stream@^2.0.0, readable-stream@^2.3.0, readable-stream@^2.3.5:
|
readable-stream@^2.0.0, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6:
|
||||||
version "2.3.8"
|
version "2.3.8"
|
||||||
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
|
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
|
||||||
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
|
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
|
||||||
@@ -10172,6 +10206,11 @@ serialize-error@^7.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
type-fest "^0.13.1"
|
type-fest "^0.13.1"
|
||||||
|
|
||||||
|
setimmediate@^1.0.5:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||||
|
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
|
||||||
|
|
||||||
sharp@^0.34.3:
|
sharp@^0.34.3:
|
||||||
version "0.34.3"
|
version "0.34.3"
|
||||||
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.3.tgz#10a03bcd15fb72f16355461af0b9245ccb8a5da3"
|
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.3.tgz#10a03bcd15fb72f16355461af0b9245ccb8a5da3"
|
||||||
@@ -11156,6 +11195,15 @@ unrs-resolver@^1.9.2:
|
|||||||
"@unrs/resolver-binding-win32-ia32-msvc" "1.11.1"
|
"@unrs/resolver-binding-win32-ia32-msvc" "1.11.1"
|
||||||
"@unrs/resolver-binding-win32-x64-msvc" "1.11.1"
|
"@unrs/resolver-binding-win32-x64-msvc" "1.11.1"
|
||||||
|
|
||||||
|
unzip-crx-3@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unzip-crx-3/-/unzip-crx-3-0.2.0.tgz#d5324147b104a8aed9ae8639c95521f6f7cda292"
|
||||||
|
integrity sha512-0+JiUq/z7faJ6oifVB5nSwt589v1KCduqIJupNVDoWSXZtWDmjDGO3RAEOvwJ07w90aoXoP4enKsR7ecMrJtWQ==
|
||||||
|
dependencies:
|
||||||
|
jszip "^3.1.0"
|
||||||
|
mkdirp "^0.5.1"
|
||||||
|
yaku "^0.16.6"
|
||||||
|
|
||||||
update-browserslist-db@^1.1.3:
|
update-browserslist-db@^1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
|
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
|
||||||
@@ -11657,6 +11705,11 @@ y18n@^5.0.5:
|
|||||||
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||||
|
|
||||||
|
yaku@^0.16.6:
|
||||||
|
version "0.16.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/yaku/-/yaku-0.16.7.tgz#1d195c78aa9b5bf8479c895b9504fd4f0847984e"
|
||||||
|
integrity sha512-Syu3IB3rZvKvYk7yTiyl1bo/jiEFaaStrgv1V2TIJTqYPStSMQVO8EQjg/z+DRzLq/4LIIharNT3iH1hylEIRw==
|
||||||
|
|
||||||
yallist@^3.0.2:
|
yallist@^3.0.2:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||||
|
|||||||
Reference in New Issue
Block a user