Files
PicList/src/renderer/pages/PicGoSetting.vue
2025-08-15 13:29:09 +08:00

1869 lines
69 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<div class="piclist-settings">
<!-- Header -->
<div class="settings-header">
<div class="header-content">
<Settings :size="24" class="header-icon" />
<div>
<h1>{{ t('pages.settings.title') }}</h1>
<p>{{ t('pages.settings.description') }}</p>
</div>
</div>
<div class="header-actions">
<button class="btn btn-secondary" @click="goConfigPage">
<BookOpen :size="16" />
{{ t('pages.settings.docs') }}
</button>
</div>
</div>
<!-- Tab Navigation -->
<div class="tab-navigation">
<button
v-for="tab in tabs"
:key="tab.id"
class="tab-button"
:class="{ active: activeName === tab.id }"
@click="activeName = tab.id as 'system' | 'sync' | 'upload' | 'advanced' | 'update'"
>
<component :is="tab.icon" :size="18" />
<span>{{ tab.label }}</span>
</button>
</div>
<!-- Settings Content -->
<div class="settings-content">
<!-- System Settings Tab -->
<div v-if="activeName === 'system'" class="tab-content">
<div class="settings-section">
<h2>{{ t('pages.settings.system.languageAndAppearance') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label>{{ t('pages.settings.system.chooseLanguage') }}</label>
<select v-model="currentLanguage" class="form-select">
<option v-for="item in languageList" :key="item.value" :value="item.value">
{{ item.label }}
</option>
</select>
</div>
<div class="form-group">
<label>{{ t('pages.settings.system.startMode') }}</label>
<select v-model="currentStartMode" class="form-select">
<option value="quiet">
{{ t('pages.settings.system.quietMode') }}
</option>
<option v-if="osGlobal !== 'darwin'" value="mini">
{{ t('pages.settings.system.miniMode') }}
</option>
<option v-if="osGlobal === 'darwin'" value="no-tray">
{{ t('pages.settings.system.noTrayMode') }}
</option>
<option value="main">
{{ t('pages.settings.system.mainMode') }}
</option>
</select>
</div>
</div>
</div>
<div class="settings-section">
<h2>{{ t('pages.settings.system.windowBehavior') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div v-if="osGlobal === 'darwin'" class="form-group">
<label class="switch-label">
<input
v-model="formOfSetting.isHideDock"
type="checkbox"
class="switch-input"
@change="handleHideDockChange(formOfSetting.isHideDock)"
/>
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.system.isHideDock') }}</div>
</div>
</label>
</div>
<div class="form-group">
<label>{{ t('pages.settings.system.mainWindowSize') }}</label>
<button class="btn btn-secondary" @click="mainWindowSizeVisible = true">
<Monitor :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
<div v-if="osGlobal !== 'darwin'" class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.autoCloseMiniWindow" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.system.autoCloseMiniWindow') }}</div>
</div>
</label>
</div>
<div v-if="osGlobal !== 'darwin'" class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.autoCloseMainWindow" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.system.autoCloseMainWindow') }}</div>
</div>
</label>
</div>
<div v-if="osGlobal !== 'darwin'" class="form-group">
<label class="switch-label">
<input
v-model="formOfSetting.miniWindowOntop"
type="checkbox"
class="switch-input"
@change="handleMiniWindowOntop(formOfSetting.miniWindowOntop)"
/>
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.system.miniWindowOnTop') }}</div>
</div>
</label>
</div>
<div v-if="osGlobal !== 'darwin'" class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.isCustomMiniIcon" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.system.isCustomMiniIcon') }}</div>
</div>
</label>
</div>
<div v-if="osGlobal !== 'darwin' && formOfSetting.isCustomMiniIcon" class="form-group">
<label>{{ t('pages.settings.system.customMiniIconPath') }}</label>
<button class="btn btn-secondary" @click="handleMiniIconPath">
<ImageIcon :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
</div>
</div>
<div class="settings-section">
<h2>{{ t('pages.settings.system.startupAndShortcuts') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label class="switch-label">
<input
v-model="formOfSetting.autoStart"
type="checkbox"
class="switch-input"
@change="handleAutoStartChange(formOfSetting.autoStart)"
/>
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.system.autoLaunch') }}</div>
</div>
</label>
</div>
<div class="form-group">
<label>{{ t('pages.settings.system.setShortCuts') }}</label>
<button class="btn btn-secondary" @click="goShortCutPage">
<Keyboard :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
</div>
</div>
</div>
<!-- Sync & Configure Tab -->
<div v-if="activeName === 'sync'" class="tab-content">
<div class="settings-section">
<h2>{{ t('pages.settings.sync.syncConfiguration') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label>{{ t('pages.settings.sync.syncEndpointConfig') }}</label>
<button class="btn btn-primary" @click="syncVisible = true">
<RotateCcw :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.sync.upDownloadSettings') }}</label>
<button class="btn btn-primary" @click="upDownConfigVisible = true">
<Download :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.sync.migrateFromPicGo') }}</label>
<button class="btn btn-secondary" @click="handleMigrateFromPicGo">
<Import :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
</div>
</div>
<div class="settings-section">
<h2>{{ t('pages.settings.sync.fileManagement') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label>{{ t('pages.settings.sync.openConfigFile') }}</label>
<button class="btn btn-secondary" @click="openFile('data.json')">
<FileText :size="16" />
{{ t('pages.settings.clickToOpen') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.sync.openConfigFileDir') }}</label>
<button class="btn btn-secondary" @click="openDirectory()">
<FolderOpen :size="16" />
{{ t('pages.settings.clickToOpen') }}
</button>
</div>
</div>
</div>
</div>
<!-- Upload Settings Tab -->
<div v-if="activeName === 'upload'" class="tab-content">
<div class="settings-section">
<h2>{{ t('pages.settings.upload.uploadBehavior') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.autoImport" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.autoImportInManage') }}</div>
<div class="switch-description">{{ t('pages.settings.upload.autoImportInManageHint') }}</div>
</div>
</label>
</div>
<div v-if="formOfSetting.autoImport" class="form-group">
<label>{{ t('pages.settings.upload.autoImportPicBed') }}</label>
<div class="checkbox-group">
<label v-for="item in picBedGlobal" :key="item.type" class="checkbox-option">
<input
v-model="formOfSetting.autoImportPicBed"
type="checkbox"
:value="item.type"
class="checkbox-input"
/>
<span class="checkbox-indicator" />
<span class="checkbox-label">{{ item.name }}</span>
</label>
</div>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.enableSecondUploader" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.enableSecondPicBed') }}</div>
<div class="switch-description">{{ t('pages.settings.upload.enableSecondPicBedHint') }}</div>
</div>
</label>
</div>
<div class="form-group">
<label>{{ t('pages.settings.upload.setSecondPicBed') }}</label>
<button class="btn btn-secondary" @click="handleChangeSecondPicBed">
<CloudUpload :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
</div>
</div>
<div class="settings-section">
<h2>{{ t('pages.settings.upload.uploadProcessing') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.deleteCloudFile" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.deleteCloud') }}</div>
</div>
</label>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.rename" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.manualRname') }}</div>
</div>
</label>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.autoRename" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.timestampRname') }}</div>
<div class="switch-description">{{ 'YYYYMMDDHHmmssSSS' }}</div>
</div>
</label>
</div>
<div class="form-group">
<label>{{ t('pages.settings.upload.advancedRname') }}</label>
<button class="btn btn-secondary" @click="advancedRenameVisible = true">
<Edit :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.upload.imageProcessing') }}</label>
<button class="btn btn-secondary" @click="imageProcessDialogVisible = true">
<ImageIcon :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.deleteLocalFile" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.deleteLocalFileAfterUpload') }}</div>
</div>
</label>
</div>
</div>
</div>
<div class="settings-section">
<h2>{{ t('pages.settings.upload.clipboardAndNotification') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.uploadNotification" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.enableUploadNotification') }}</div>
</div>
</label>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.uploadResultNotification" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.enableUploadResultNotification') }}</div>
</div>
</label>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.autoCopy" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.autoCopyUrlAfterUpload') }}</div>
</div>
</label>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.useBuiltinClipboard" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.useBuiltInClipboardUpload') }}</div>
<div class="switch-description">
{{ t('pages.settings.upload.useBuiltInClipboardUploadHint') }}
</div>
</div>
</label>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.isAutoListenClipboard" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.isAutoListenClipboard') }}</div>
</div>
</label>
</div>
</div>
</div>
<div class="settings-section">
<h2>{{ t('pages.settings.upload.urlFormatAndLinkType') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label>{{ t('pages.settings.upload.customLinkFormat') }}</label>
<button class="btn btn-secondary" @click="customLinkVisible = true">
<Link :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.useShortUrl" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.enableShortUrl') }}</div>
</div>
</label>
</div>
<div v-if="formOfSetting.useShortUrl" class="form-group">
<label>{{ t('pages.settings.upload.shortUrlServer') }}</label>
<select
v-model="currentShortUrlServer"
class="form-select"
@change="handleShortUrlServerChange(currentShortUrlServer)"
>
<option v-for="item in shortUrlServerList" :key="item.value" :value="item.value">
{{ item.label }}
</option>
</select>
</div>
<div v-if="formOfSetting.useShortUrl && formOfSetting.shortUrlServer === 'c1n'" class="form-group">
<label>{{ t('pages.settings.upload.c1nToken') }}</label>
<input
v-model="formOfSetting.c1nToken"
type="text"
class="form-input"
:placeholder="t('pages.settings.upload.c1nToken')"
/>
</div>
<div v-if="formOfSetting.useShortUrl && formOfSetting.shortUrlServer === 'yourls'" class="form-group">
<label>{{ t('pages.settings.upload.yourlsDomain') }}</label>
<input
v-model="formOfSetting.yourlsDomain"
type="text"
class="form-input"
:placeholder="t('pages.settings.upload.yourlsDomain')"
/>
</div>
<div v-if="formOfSetting.useShortUrl && formOfSetting.shortUrlServer === 'yourls'" class="form-group">
<label>{{ t('pages.settings.upload.yourlsSignature') }}</label>
<input
v-model="formOfSetting.yourlsSignature"
type="text"
class="form-input"
:placeholder="t('pages.settings.upload.yourlsSignature')"
/>
</div>
<div v-if="formOfSetting.useShortUrl && formOfSetting.shortUrlServer === 'cf_worker'" class="form-group">
<label>{{ t('pages.settings.upload.cfWorkerHost') }}</label>
<input
v-model="formOfSetting.cfWorkerHost"
type="text"
class="form-input"
:placeholder="t('pages.settings.upload.cfWorkerHost')"
/>
</div>
<div v-if="formOfSetting.useShortUrl && formOfSetting.shortUrlServer === 'sink'" class="form-group">
<label>{{ t('pages.settings.upload.sinkDomain') }}</label>
<input
v-model="formOfSetting.sinkDomain"
type="text"
class="form-input"
:placeholder="t('pages.settings.upload.sinkDomain')"
/>
</div>
<div v-if="formOfSetting.useShortUrl && formOfSetting.shortUrlServer === 'sink'" class="form-group">
<label>{{ t('pages.settings.upload.sinkToken') }}</label>
<input
v-model="formOfSetting.sinkToken"
type="text"
class="form-input"
:placeholder="t('pages.settings.upload.sinkToken')"
/>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.encodeOutputURL" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.encodeOutputUrl') }}</div>
</div>
</label>
</div>
</div>
</div>
<div class="settings-section">
<h2>{{ t('pages.settings.upload.chooseShowedPicBed') }}</h2>
<div class="checkbox-group">
<label v-for="item in picBedGlobal" :key="item.name" class="checkbox-option">
<input v-model="showPicBedList" type="checkbox" :value="item.name" class="checkbox-input" />
<span class="checkbox-indicator" />
<span class="checkbox-label">{{ item.name }}</span>
</label>
</div>
</div>
</div>
<!-- Advanced Settings Tab -->
<div v-if="activeName === 'advanced'" class="tab-content">
<div class="settings-section">
<h2>{{ t('pages.settings.advanced.logging') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label>{{ t('pages.settings.advanced.logFilePath') }}</label>
<button class="btn btn-secondary" @click="openDirectory()">
<FolderOpen :size="16" />
{{ t('pages.settings.clickToOpen') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.setLog') }}</label>
<button class="btn btn-secondary" @click="openLogSetting">
<FileText :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
</div>
</div>
<div class="settings-section">
<h2>{{ t('pages.settings.advanced.networkAndProxy') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-group">
<label>{{ t('pages.settings.advanced.setProxyAndMirror') }}</label>
<button class="btn btn-secondary" @click="proxyVisible = true">
<Globe :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
</div>
<div class="settings-section">
<h2>{{ t('pages.settings.advanced.serverSettings') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label>{{ t('pages.settings.advanced.webServerSettings') }}</label>
<button class="btn btn-secondary" @click="webServerVisible = true">
<Server :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.uploadServer') }}</label>
<button class="btn btn-secondary" @click="serverVisible = true">
<Settings :size="16" />
{{ t('pages.settings.clickToSet') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.serverEncryptionKey') }}</label>
<input
v-model.trim="formOfSetting.aesPassword"
type="text"
class="form-input"
:placeholder="t('pages.settings.advanced.serverEncryptionKey')"
@change="handleAesPasswordChange(formOfSetting.aesPassword)"
/>
</div>
</div>
</div>
</div>
<!-- Update Settings Tab -->
<div v-if="activeName === 'update'" class="tab-content">
<div class="settings-section">
<h2>{{ t('pages.settings.update.applicationUpdates') }}</h2>
<p>{{ ' ' }}</p>
<div class="form-grid">
<div class="form-group">
<label>{{ t('pages.settings.update.checkUpdate') }}</label>
<button class="btn btn-primary" @click="checkUpdate">
<RefreshCw :size="16" />
{{ t('pages.settings.update.clickToCheck') }}
</button>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.showUpdateTip" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.update.openUpdateHelper') }}</div>
<div class="switch-description">
{{ t('pages.settings.update.openUpdateHelperDesc') }}
</div>
</div>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Dialogs -->
<div v-if="customLinkVisible" class="dialog-overlay" @click="customLinkVisible = false">
<div class="dialog" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.upload.customLinkFormat') }}
</h3>
<button class="dialog-close" @click="customLinkVisible = false">×</button>
</div>
<div class="dialog-content">
<div class="notice-text">
{{ t('pages.settings.upload.urlPlaceholder') }}<br />
{{ t('pages.settings.upload.fileNamePlaceholder') }}<br />
{{ t('pages.settings.upload.extNamePlaceholder') }}
</div>
<div class="form-group">
<input v-model="customLink.value" type="text" class="form-input" :placeholder="'![$fileName]($url)'" />
</div>
<small> ![$fileName]($url)</small>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="cancelCustomLink">
{{ t('common.cancel') }}
</button>
<button class="btn btn-primary" @click="confirmCustomLink">
{{ t('common.confirm') }}
</button>
</div>
</div>
</div>
<!-- Proxy Settings Dialog -->
<div v-if="proxyVisible" class="dialog-overlay" @click="proxyVisible = false">
<div class="dialog" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.advanced.setProxyAndMirror') }}
</h3>
<button class="dialog-close" @click="proxyVisible = false">×</button>
</div>
<div class="dialog-content">
<div class="form-group">
<label>{{ t('pages.settings.advanced.uploadProxy') }}</label>
<input v-model="proxy" type="text" class="form-input" placeholder="http://127.0.0.1:1080" />
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.pluginInstallProxy') }}</label>
<input v-model="formOfSetting.proxy" type="text" class="form-input" placeholder="http://127.0.0.1:1080" />
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.pluginInstallMirror') }}</label>
<input
v-model="formOfSetting.registry"
type="text"
class="form-input"
placeholder="https://registry.npmmirror.com"
/>
</div>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="proxyVisible = false">
{{ t('common.cancel') }}
</button>
<button class="btn btn-primary" @click="proxyVisible = false">
{{ t('common.confirm') }}
</button>
</div>
</div>
</div>
<!-- Main Window Size Dialog -->
<div v-if="mainWindowSizeVisible" class="dialog-overlay" @click="cancelWindowSize">
<div class="dialog" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.system.setMainWindowSize') }}
</h3>
<button class="dialog-close" @click="cancelWindowSize">×</button>
</div>
<div class="dialog-content">
<div class="form-group">
<label>{{ t('pages.settings.system.mainWindowWidth') }}</label>
<input v-model="formOfSetting.mainWindowWidth" type="number" class="form-input" placeholder="1200" />
</div>
<div class="form-group">
<label>{{ t('pages.settings.system.mainWindowHeight') }}</label>
<input v-model="formOfSetting.mainWindowHeight" type="number" class="form-input" placeholder="800" />
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="rawPicGoSize" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.system.rawPicGoSize') }}</div>
<div class="switch-description">
{{ t('pages.settings.system.rawPicGoSizeHint') }}
</div>
</div>
</label>
</div>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="cancelWindowSize">
{{ t('common.cancel') }}
</button>
<button class="btn btn-primary" @click="confirmWindowSize">
{{ t('common.confirm') }}
</button>
</div>
</div>
</div>
<!-- Check Update Dialog -->
<div v-if="checkUpdateVisible" class="dialog-overlay" @click="cancelCheckVersion">
<div class="dialog" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.update.checkUpdate') }}
</h3>
<button class="dialog-close" @click="cancelCheckVersion">×</button>
</div>
<div class="dialog-content">
<div class="update-info">
<div>{{ t('pages.settings.update.currentVersion', { version }) }}</div>
<div>
{{ t('pages.settings.update.newestVersion') }}:
{{ latestVersion ? latestVersion : `${t('pages.settings.update.getting')}` }}
</div>
<div v-if="needUpdate" class="update-notice">
{{ t('pages.settings.update.hasNewVersion') }}
</div>
</div>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="cancelCheckVersion">
{{ t('common.cancel') }}
</button>
<button class="btn btn-primary" @click="confirmCheckVersion">
{{ t('common.confirm') }}
</button>
</div>
</div>
</div>
<!-- Advanced Rename Dialog -->
<div v-if="advancedRenameVisible" class="dialog-overlay" @click="handleCancelAdvancedRename">
<div class="dialog" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.upload.advancedRname') }}
</h3>
<button class="dialog-close" @click="handleCancelAdvancedRename">×</button>
</div>
<div class="dialog-content">
<div class="form-group">
<label class="switch-label">
<input v-model="advancedRename.enable" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.upload.enableAdvancedRname') }}</div>
</div>
</label>
</div>
<div class="form-group">
<label>{{ t('pages.settings.upload.advancedRnameFormat') }}</label>
<input v-model="advancedRename.format" type="text" class="form-input" placeholder="Ex. {Y}-{m}-{uuid}" />
</div>
<div class="form-group">
<label>{{ t('pages.settings.upload.availablePlaceholders') }}</label>
<div class="placeholder-help">
<div class="placeholder-category">
<div class="category-title">
{{ t('pages.settings.upload.placeholder.categoryTime') }}
</div>
<div class="placeholder-grid">
<div
v-for="item in advancedRenameList.categoryTime"
:key="item.value"
class="placeholder-item"
@click="copyPlaceholder(item.value)"
>
<code>{{ item.value }}</code>
<span>{{ item.label }}</span>
</div>
</div>
</div>
<div class="placeholder-category">
<div class="category-title">
{{ t('pages.settings.upload.placeholder.categoryHash') }}
</div>
<div class="placeholder-grid">
<div
v-for="item in advancedRenameList.categoryHash"
:key="item.value"
class="placeholder-item"
@click="copyPlaceholder(item.value)"
>
<code>{{ item.value }}</code>
<span>{{ item.label }}</span>
</div>
</div>
</div>
<div class="placeholder-category">
<div class="category-title">
{{ t('pages.settings.upload.placeholder.categoryFile') }}
</div>
<div class="placeholder-grid">
<div
v-for="item in advancedRenameList.categoryFile"
:key="item.value"
class="placeholder-item"
@click="copyPlaceholder(item.value)"
>
<code>{{ item.value }}</code>
<span>{{ item.label }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="handleCancelAdvancedRename">
{{ t('common.cancel') }}
</button>
<button class="btn btn-primary" @click="handleSaveAdvancedRename">
{{ t('common.confirm') }}
</button>
</div>
</div>
</div>
<!-- Log Settings Dialog -->
<div v-if="logFileVisible" class="dialog-overlay" @click="cancelLogLevelSetting">
<div class="dialog" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.advanced.setLog') }}
</h3>
<button class="dialog-close" @click="cancelLogLevelSetting">×</button>
</div>
<div class="dialog-content">
<div class="form-grid">
<div class="form-group">
<label>{{ t('pages.settings.advanced.logFile') }}</label>
<button class="btn btn-secondary" @click="openFile('piclist.log')">
<FileText :size="16" />
{{ t('pages.settings.clickToOpen') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.guiLogFile') }}</label>
<button class="btn btn-secondary" @click="openFile('piclist-gui-local.log')">
<FileText :size="16" />
{{ t('pages.settings.clickToOpen') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.manageLogFile') }}</label>
<button class="btn btn-secondary" @click="openFile('manage.log')">
<FileText :size="16" />
{{ t('pages.settings.clickToOpen') }}
</button>
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.logLevel') }}</label>
<select v-model="formOfSetting.logLevel" multiple class="form-select">
<option v-for="(value, key) of logLevel" :key="key" :value="key" :disabled="handleLevelDisabled(key)">
{{ value }}
</option>
</select>
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.logFileSize') }} (MB)</label>
<input
v-model="formOfSetting.logFileSizeLimit"
type="number"
class="form-input"
placeholder="10"
min="1"
/>
</div>
</div>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="cancelLogLevelSetting">
{{ t('common.cancel') }}
</button>
<button class="btn btn-primary" @click="confirmLogLevelSetting">
{{ t('common.confirm') }}
</button>
</div>
</div>
</div>
<!-- Server Settings Dialog -->
<div v-if="serverVisible" class="dialog-overlay" @click="cancelServerSetting">
<div class="dialog" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.advanced.uploadServer') }}
</h3>
<button class="dialog-close" @click="cancelServerSetting">×</button>
</div>
<div class="dialog-content">
<div class="notice-text">
{{ t('pages.settings.advanced.serverSettingsNotice') }}
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="server.enable" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.advanced.enableServer') }}</div>
</div>
</label>
</div>
<template v-if="server.enable">
<div class="form-group">
<label>{{ t('pages.settings.advanced.serverHost') }}</label>
<input v-model="server.host" type="text" class="form-input" placeholder="127.0.0.1" />
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.serverPort') }}</label>
<input v-model="server.port" type="number" class="form-input" placeholder="36677" />
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.serverKey') }}</label>
<input
v-model="formOfSetting.serverKey"
type="text"
class="form-input"
:placeholder="t('pages.settings.advanced.serverKeyPlaceholder')"
/>
</div>
</template>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="cancelServerSetting">
{{ t('common.cancel') }}
</button>
<button class="btn btn-primary" @click="confirmServerSetting">
{{ t('common.confirm') }}
</button>
</div>
</div>
</div>
<!-- Web Server Settings Dialog -->
<div v-if="webServerVisible" class="dialog-overlay" @click="confirmWebServerSetting">
<div class="dialog" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.advanced.webServerSettings') }}
</h3>
<button class="dialog-close" @click="confirmWebServerSetting">×</button>
</div>
<div class="dialog-content">
<div class="notice-text">
{{ t('pages.settings.advanced.webServerNotice') }}
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="formOfSetting.enableWebServer" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.advanced.enableWebServer') }}</div>
</div>
</label>
</div>
<template v-if="formOfSetting.enableWebServer">
<div class="form-group">
<label>{{ t('pages.settings.advanced.webServerHost') }}</label>
<input
v-model="formOfSetting.webServerHost"
type="text"
class="form-input"
:placeholder="t('pages.settings.advanced.webServerPlaceholderHost')"
/>
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.webServerPort') }}</label>
<input
v-model.number="formOfSetting.webServerPort"
type="number"
class="form-input"
min="1"
max="65535"
:placeholder="t('pages.settings.advanced.webServerPlaceholderPort')"
/>
</div>
<div class="form-group">
<label>{{ t('pages.settings.advanced.webServerPath') }}</label>
<input v-model="formOfSetting.webServerPath" type="text" class="form-input" />
</div>
</template>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="confirmWebServerSetting">
{{ t('common.close') }}
</button>
</div>
</div>
</div>
<!-- Sync Configuration Dialog -->
<div v-if="syncVisible" class="dialog-overlay" @click="cancelSyncSetting">
<div class="dialog large" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.sync.syncEndpointConfig') }}
</h3>
<button class="dialog-close" @click="cancelSyncSetting">×</button>
</div>
<div class="dialog-content">
<div class="notice-text">
{{ t('pages.settings.sync.syncConfigNote') }}
</div>
<div class="form-group">
<label>{{ t('pages.settings.sync.selectType') }}</label>
<select v-model="sync.type" class="form-select">
<option v-for="typeitem of syncType" :key="typeitem" :value="typeitem">
{{ typeitem.slice(0, 1).toUpperCase() + typeitem.slice(1) }}
</option>
</select>
</div>
<div v-if="sync.type === 'gitea'" class="form-group">
<label>{{ t('pages.settings.sync.giteaHost') }}</label>
<input
v-model.trim="sync.endpoint"
type="text"
class="form-input"
:placeholder="t('pages.settings.sync.giteaHost')"
/>
</div>
<div v-if="sync.type === 'webdav'" class="form-group">
<label>{{ t('pages.settings.sync.webdavEndpoint') }}</label>
<input
v-model.trim="sync.webdavEndpoint"
type="text"
class="form-input"
:placeholder="t('pages.settings.sync.webdavEndpoint')"
/>
</div>
<template v-if="sync.type !== 'webdav'">
<div v-for="inputItem in ['username', 'repo', 'branch', 'token']" :key="inputItem" class="form-group">
<label>{{ t(`pages.settings.sync.${sync.type.toLowerCase()}.${inputItem.toLowerCase()}`) }}</label>
<input
v-model.trim="sync[inputItem as any]"
type="text"
class="form-input"
:placeholder="t(`pages.settings.sync.${sync.type.toLowerCase()}.${inputItem.toLowerCase()}`)"
/>
</div>
</template>
<template v-if="sync.type === 'webdav'">
<div class="form-group">
<label>{{ t('pages.settings.sync.webdav.username') }}</label>
<input
v-model.trim="sync.webdavUsername"
type="text"
class="form-input"
:placeholder="t('pages.settings.sync.webdav.username')"
/>
</div>
<div class="form-group">
<label>{{ t('pages.settings.sync.webdav.password') }}</label>
<input
v-model.trim="sync.webdavPassword"
class="form-input"
:placeholder="t('pages.settings.sync.webdav.password')"
/>
</div>
<div class="form-group">
<label>{{ t('pages.settings.sync.webdav.savePath') }}</label>
<input
v-model.trim="sync.webdavSavePath"
type="text"
class="form-input"
:placeholder="t('pages.settings.sync.webdav.savePath')"
/>
</div>
<div class="form-group">
<label>{{ t('pages.settings.sync.webdav.authType') }}</label>
<select v-model="sync.webdavAuthType" class="form-select">
<option value="basic">Basic</option>
<option value="digest">Digest</option>
</select>
</div>
<div class="form-group">
<label class="switch-label">
<input v-model="sync.webdavSslEnabled" type="checkbox" class="switch-input" />
<span class="switch-slider" />
<div class="switch-content">
<div class="switch-title">{{ t('pages.settings.sync.webdav.enableSSL') }}</div>
</div>
</label>
</div>
</template>
<div v-if="sync.type === 'github'" class="form-group">
<label>{{ t('pages.settings.sync.syncConfigProxy') }}</label>
<input
v-model.trim="sync.proxy"
type="text"
class="form-input"
:placeholder="t('pages.settings.sync.syncConfigProxy')"
/>
</div>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="cancelSyncSetting">
{{ t('common.cancel') }}
</button>
<button class="btn btn-primary" @click="confirmSyncSetting">
{{ t('common.confirm') }}
</button>
</div>
</div>
</div>
<!-- Upload/Download Config Dialog -->
<div v-if="upDownConfigVisible" class="dialog-overlay" @click="upDownConfigVisible = false">
<div class="dialog" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.settings.sync.upDownloadSettings') }}
</h3>
<button class="dialog-close" @click="upDownConfigVisible = false">×</button>
</div>
<div class="dialog-content">
<div class="form-group">
<label>{{ t('pages.settings.sync.uploadSettings') }}</label>
<div class="button-group">
<button
v-for="item in syncTaskList.slice(0, 3)"
:key="item.task"
class="btn btn-primary"
@click="syncTaskFn(item.task, item.number)"
>
{{ item.label }}
</button>
</div>
</div>
<div class="form-group">
<label>{{ t('pages.settings.sync.downloadSettings') }}</label>
<div class="button-group">
<button
v-for="item in syncTaskList.slice(3)"
:key="item.task"
class="btn btn-primary"
@click="syncTaskFn(item.task, item.number)"
>
{{ item.label }}
</button>
</div>
</div>
</div>
<div class="dialog-footer">
<button class="btn btn-secondary" @click="upDownConfigVisible = false">
{{ t('common.close') }}
</button>
</div>
</div>
</div>
<!-- Image Process Dialog -->
<div v-if="imageProcessDialogVisible" class="dialog-overlay" @click="imageProcessDialogVisible = false">
<div class="dialog large" @click.stop>
<div class="dialog-header">
<h3 class="dialog-title">
{{ t('pages.imageProcess.title') }}
</h3>
<button class="dialog-close" @click="imageProcessDialogVisible = false">×</button>
</div>
<div class="dialog-content">
<ImageProcessSetting v-model="imageProcessDialogVisible" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { compare } from 'compare-versions'
import {
BookOpen,
CloudUpload,
Download,
Edit,
FileText,
FolderOpen,
Globe,
Image as ImageIcon,
Import,
Keyboard,
Link,
Monitor,
RefreshCw,
RotateCcw,
Server,
Settings
} from 'lucide-vue-next'
import type { IConfig } from 'piclist'
import pkg from 'root/package.json'
import { ISettingForm } from 'root/src/universal/types/view'
import { computed, onBeforeMount, reactive, ref, toRaw, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import ImageProcessSetting from '@/components/ImageProcessSetting.vue'
import useConfirm from '@/hooks/useConfirm'
import useMessage from '@/hooks/useMessage'
import { setCurrentLanguage } from '@/i18n'
import { SHORTKEY_PAGE } from '@/router/config'
import { enforceNumber } from '@/utils/common'
import { configPaths } from '@/utils/configPaths'
import { getConfig, saveConfig } from '@/utils/dataSender'
import { II18nLanguage, IRPCActionType, ISartMode } from '@/utils/enum'
import { getLatestVersion } from '@/utils/getLatestVersion'
import { osGlobal, picBedGlobal, updatePicBedGlobal } from '@/utils/global'
import type { ICheckBoxValueType } from '#/types/types'
const { t, locale } = useI18n()
const $router = useRouter()
const { confirm } = useConfirm()
const message = useMessage()
const activeName = ref<'system' | 'sync' | 'upload' | 'advanced' | 'update'>('system')
const showPicBedList = ref<string[]>([])
// Tab configuration
const tabs = computed(() => [
{ id: 'system', label: t('pages.settings.system.title'), icon: Settings },
{ id: 'sync', label: t('pages.settings.sync.title'), icon: RotateCcw },
{ id: 'upload', label: t('pages.settings.upload.title'), icon: CloudUpload },
{ id: 'advanced', label: t('pages.settings.advanced.title'), icon: Server },
{ id: 'update', label: t('pages.settings.update.title'), icon: RefreshCw }
])
const shortUrlServerList = [
{ label: 'c1n', value: 'c1n' },
{ label: 'yourls', value: 'yourls' },
{ label: 'xyTom/Url-Shorten-Worker', value: 'cf_worker' },
{ label: 'ccbikai/Sink', value: 'sink' }
]
const languageList = [
{ label: '简体中文', value: 'zh-CN' },
{ label: '繁體中文', value: 'zh-TW' },
{ label: 'English', value: 'en' }
]
const formOfSetting = ref<ISettingForm>({
showUpdateTip: true,
autoStart: false,
rename: false,
autoRename: false,
uploadNotification: false,
uploadResultNotification: true,
miniWindowOntop: false,
autoCloseMiniWindow: false,
autoCloseMainWindow: false,
logLevel: ['all'],
autoCopy: true,
useBuiltinClipboard: true,
logFileSizeLimit: 10,
deleteCloudFile: false,
isCustomMiniIcon: false,
customMiniIcon: '',
isHideDock: false,
autoImport: false,
autoImportPicBed: [],
encodeOutputURL: false,
isAutoListenClipboard: false,
useShortUrl: false,
shortUrlServer: 'c1n',
c1nToken: '',
yourlsDomain: '',
yourlsSignature: '',
cfWorkerHost: '',
sinkDomain: '',
sinkToken: '',
deleteLocalFile: false,
serverKey: '',
aesPassword: 'PicList-aesPassword',
enableWebServer: false,
webServerHost: '0.0.0.0',
webServerPort: 37777,
webServerPath: '',
registry: '',
proxy: '',
mainWindowWidth: 1200,
mainWindowHeight: 800,
enableSecondUploader: false
})
const proxy = ref('')
const formKeys = Object.keys(formOfSetting.value) as (keyof ISettingForm)[]
const autoWatchKeys = [
'showUpdateTip',
'autoImport',
'autoImportPicBed',
'useBuiltinClipboard',
'isAutoListenClipboard',
'deleteCloudFile',
'deleteLocalFile',
'rename',
'autoRename',
'enableWebServer',
'webServerHost',
'webServerPort',
'webServerPath',
'serverKey',
'uploadNotification',
'uploadResultNotification',
'autoCloseMainWindow',
'autoCloseMiniWindow',
'isCustomMiniIcon',
'c1nToken',
'yourlsDomain',
'yourlsSignature',
'cfWorkerHost',
'sinkDomain',
'sinkToken',
'registry',
'proxy',
'autoCopy',
'encodeOutputURL',
'useShortUrl',
'enableSecondUploader'
]
const addWatch = () => {
autoWatchKeys.forEach(key => {
watch(
() => formOfSetting.value[key as keyof ISettingForm],
value => {
saveConfig({ [`settings.${key}`]: value })
}
)
})
watch(currentLanguage, newVal => {
if (newVal) {
handleLanguageChange(newVal)
}
})
watch(currentStartMode, newVal => {
if (newVal) {
handleStartModeChange(newVal)
}
})
watch(currentShortUrlServer, newVal => {
if (newVal) {
handleShortUrlServerChange(newVal)
}
})
}
const addProxyWatch = () => {
watch(proxy, value => {
saveConfig({ 'picBed.proxy': value })
})
}
const advancedRenameList = {
categoryTime: [
{ label: t('pages.settings.upload.placeholder.year4'), value: '{Y}' },
{ label: t('pages.settings.upload.placeholder.year2'), value: '{y}' },
{ label: t('pages.settings.upload.placeholder.month'), value: '{m}' },
{ label: t('pages.settings.upload.placeholder.date'), value: '{d}' },
{ label: t('pages.settings.upload.placeholder.hour'), value: '{h}' },
{ label: t('pages.settings.upload.placeholder.minute'), value: '{i}' },
{ label: t('pages.settings.upload.placeholder.second'), value: '{s}' },
{ label: t('pages.settings.upload.placeholder.millisecond'), value: '{ms}' },
{ label: t('pages.settings.upload.placeholder.timestamp'), value: '{timestamp}' }
],
categoryHash: [
{ label: t('pages.settings.upload.placeholder.md5'), value: '{md5}' },
{ label: t('pages.settings.upload.placeholder.md5-16'), value: '{md5-16}' },
{ label: t('pages.settings.upload.placeholder.uuid'), value: '{uuid}' }
],
categoryFile: [
{ label: t('pages.settings.upload.placeholder.filename'), value: '{filename}' },
{ label: t('pages.settings.upload.placeholder.localFolder'), value: '{localFolder:n}' },
{ label: t('pages.settings.upload.placeholder.randomString'), value: '{str-n}' }
]
}
function copyPlaceholder(placeholder: string) {
window.electron.clipboard.writeText(placeholder)
message.success(t('pages.settings.upload.copySuccess', { content: placeholder }))
}
const currentLanguage = ref()
const currentStartMode = ref()
const currentShortUrlServer = ref()
const logFileVisible = ref(false)
const customLinkVisible = ref(false)
const checkUpdateVisible = ref(false)
const serverVisible = ref(false)
const webServerVisible = ref(false)
const syncVisible = ref(false)
const upDownConfigVisible = ref(false)
const proxyVisible = ref(false)
const mainWindowSizeVisible = ref(false)
const advancedRenameVisible = ref(false)
const imageProcessDialogVisible = ref(false)
const rawPicGoSize = ref(false)
const customLink = reactive({ value: '![$fileName]($url)' })
const logLevel = {
all: t('pages.settings.advanced.logLevelList.all'),
success: t('pages.settings.advanced.logLevelList.success'),
error: t('pages.settings.advanced.logLevelList.error'),
info: t('pages.settings.advanced.logLevelList.info'),
warn: t('pages.settings.advanced.logLevelList.warn'),
none: t('pages.settings.advanced.logLevelList.none')
}
const server = ref({ port: 36677, host: '0.0.0.0', enable: true })
const advancedRename = ref({ enable: false, format: '{filename}' })
const sync = ref<any>({
type: 'github',
username: '',
repo: '',
branch: '',
token: '',
endpoint: '',
proxy: '',
interval: 60,
// WebDAV-specific fields
password: '',
authType: 'basic',
sslEnabled: true,
webdavSavePath: ''
})
const syncType = ['github', 'gitee', 'gitea', 'webdav']
async function cancelSyncSetting() {
syncVisible.value = false
sync.value = (await getConfig(configPaths.settings.sync)) || {
type: 'github',
username: '',
repo: '',
branch: '',
token: '',
endpoint: '',
proxy: '',
interval: 60,
// WebDAV-specific fields
webdavEndpoint: '',
webdavUsername: '',
webdavPassword: '',
webdavAuthType: 'basic',
webdavSslEnabled: true,
webdavSavePath: ''
}
}
function confirmSyncSetting() {
saveConfig({ [configPaths.settings.sync]: sync.value })
syncVisible.value = false
}
const version = pkg.version
const latestVersion = ref('')
const needUpdate = computed(() => {
if (latestVersion.value) {
return compareVersion2Update(version, latestVersion.value)
}
return false
})
onBeforeMount(() => {
initData()
})
async function initData() {
const config = (await getConfig<IConfig>()) || ({} as IConfig)
const settings = config.settings || {}
const picBed = config.picBed
showPicBedList.value = picBedGlobal.value.filter(item => item.visible).map(item => item.name)
formKeys.forEach(key => {
;(formOfSetting.value as any)[key] = settings[key] ?? formOfSetting.value[key]
})
formOfSetting.value.logLevel = initArray(settings.logLevel || [], ['all'])
formOfSetting.value.autoImportPicBed = initArray(settings.autoImportPicBed || [], [])
currentLanguage.value = settings.language || 'zh-CN'
currentStartMode.value = settings.startMode || ISartMode.QUIET
if (osGlobal.value === 'darwin' && currentStartMode.value === ISartMode.MINI) {
currentStartMode.value = ISartMode.QUIET
saveConfig(configPaths.settings.startMode, ISartMode.QUIET)
}
currentShortUrlServer.value = settings.shortUrlServer || 'c1n'
customLink.value = settings.customLink || '![$fileName]($url)'
proxy.value = picBed.proxy || ''
server.value = settings.server || { port: 36677, host: '0.0.0.0', enable: true }
advancedRename.value = config.buildIn?.rename || { enable: false, format: '{filename}' }
if (advancedRename.value.enable) {
formOfSetting.value.autoRename = false
saveConfig({ [configPaths.settings.autoRename]: false })
}
sync.value = settings.sync || {
type: 'github',
username: '',
repo: '',
branch: '',
token: '',
endpoint: '',
proxy: '',
interval: 60,
// WebDAV-specific fields
webdavEndpoint: '',
webdavUsername: '',
webdavPassword: '',
webdavAuthType: 'basic',
webdavSslEnabled: true,
webdavSavePath: ''
}
formOfSetting.value.logFileSizeLimit = enforceNumber(settings.logFileSizeLimit) || 10
addProxyWatch()
addWatch()
}
function initArray(arrayT: string | string[], defaultValue: string[]) {
if (!Array.isArray(arrayT)) {
if (arrayT && arrayT.length > 0) {
arrayT = [arrayT]
} else {
arrayT = defaultValue
}
}
return arrayT
}
async function handleChangeSecondPicBed() {
window.electron.sendRPC(IRPCActionType.SHOW_SECOND_UPLOADER_MENU)
}
function openFile(file: string) {
window.electron.sendRPC(IRPCActionType.PICLIST_OPEN_FILE, file)
}
function openDirectory(directory?: string, inStorePath = true) {
window.electron.sendRPC(IRPCActionType.PICLIST_OPEN_DIRECTORY, directory, inStorePath)
}
function openLogSetting() {
logFileVisible.value = true
}
async function cancelCustomLink() {
customLinkVisible.value = false
customLink.value = (await getConfig<string>(configPaths.settings.customLink)) || '![$fileName]($url)'
}
function confirmCustomLink() {
saveConfig(configPaths.settings.customLink, customLink.value)
customLinkVisible.value = false
}
async function handleCancelAdvancedRename() {
advancedRenameVisible.value = false
advancedRename.value = toRaw(
(await getConfig<any>(configPaths.buildIn.rename)) || { enable: false, format: '{filename}' }
)
}
function handleSaveAdvancedRename() {
saveConfig(configPaths.buildIn.rename, toRaw(advancedRename.value))
if (advancedRename.value.enable) {
formOfSetting.value.autoRename = false
saveConfig(configPaths.settings.autoRename, false)
}
advancedRenameVisible.value = false
}
function handleMigrateFromPicGo() {
confirm({
title: t('pages.settings.sync.mirgrateTitle'),
message: t('pages.settings.sync.mirgrateContent'),
type: 'warning',
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
center: true
}).then(result => {
if (result) {
window.electron
.triggerRPC<boolean>(IRPCActionType.CONFIGURE_MIGRATE_FROM_PICGO)
.then(() => {
message.success(t('pages.settings.sync.mirgrateSuccess'))
})
.catch(() => {
message.error(t('pages.settings.sync.mirgrateFailed'))
})
}
})
}
function handleHideDockChange(val: ICheckBoxValueType) {
if (val && currentStartMode.value === ISartMode.NO_TRAY) {
message.warning(t('pages.settings.system.hideDockHint'))
formOfSetting.value.isHideDock = false
return
}
saveConfig(configPaths.settings.isHideDock, val)
window.electron.sendRPC(IRPCActionType.HIDE_DOCK, val)
}
watch(showPicBedList, val => {
handleShowPicBedListChange(val)
})
function handleShowPicBedListChange(val: ICheckBoxValueType[]) {
const list = picBedGlobal.value.map(item => ({ ...item, visible: val.includes(item.name) }))
saveConfig({ [configPaths.picBed.list]: list })
updatePicBedGlobal()
}
function handleAutoStartChange(val: ICheckBoxValueType) {
saveConfig(configPaths.settings.autoStart, val)
window.electron.sendRPC(IRPCActionType.PICLIST_AUTO_START, val)
}
function compareVersion2Update(current: string, latest: string): boolean {
return compare(current, latest, '<')
}
async function checkUpdate() {
checkUpdateVisible.value = true
latestVersion.value = (await getLatestVersion()) || t('pages.settings.update.networkError')
}
function confirmCheckVersion() {
if (needUpdate.value) {
window.electron.sendRPC(IRPCActionType.RELOAD_APP)
}
checkUpdateVisible.value = false
}
function cancelCheckVersion() {
checkUpdateVisible.value = false
}
function confirmWebServerSetting() {
if (formOfSetting.value.enableWebServer) {
window.electron.sendRPC(IRPCActionType.ADVANCED_RESTART_WEB_SERVER)
} else {
window.electron.sendRPC(IRPCActionType.ADVANCED_STOP_WEB_SERVER)
}
webServerVisible.value = false
}
async function getMainWindowSize() {
formOfSetting.value.mainWindowWidth = (await getConfig<number>(configPaths.settings.mainWindowWidth)) || 1200
formOfSetting.value.mainWindowHeight = (await getConfig<number>(configPaths.settings.mainWindowHeight)) || 800
}
async function cancelWindowSize() {
mainWindowSizeVisible.value = false
await getMainWindowSize()
}
async function confirmWindowSize() {
mainWindowSizeVisible.value = false
const width = enforceNumber(formOfSetting.value.mainWindowWidth)
const height = enforceNumber(formOfSetting.value.mainWindowHeight)
saveConfig({
[configPaths.settings.mainWindowWidth]: rawPicGoSize.value ? 800 : width < 100 ? 100 : width,
[configPaths.settings.mainWindowHeight]: rawPicGoSize.value ? 450 : height < 100 ? 100 : height
})
await getMainWindowSize()
}
function handleMiniWindowOntop(val: ICheckBoxValueType) {
saveConfig(configPaths.settings.miniWindowOntop, val)
window.electron.sendRPC(IRPCActionType.MINI_WINDOW_ON_TOP, val)
}
async function handleMiniIconPath(_: Event) {
const result = await window.electron.triggerRPC<string[]>(IRPCActionType.MANAGE_OPEN_FILE_SELECT_DIALOG)
if (result && result[0]) {
formOfSetting.value.customMiniIcon = result[0]
saveConfig(configPaths.settings.customMiniIcon, formOfSetting.value.customMiniIcon)
window.electron.sendRPC(IRPCActionType.UPDATE_MINI_WINDOW_ICON, formOfSetting.value.customMiniIcon)
}
}
function handleShortUrlServerChange(val: string) {
formOfSetting.value.shortUrlServer = val
saveConfig(configPaths.settings.shortUrlServer, val)
}
function handleAesPasswordChange(val: string) {
saveConfig(configPaths.settings.aesPassword, val || 'PicList-aesPassword')
}
function confirmLogLevelSetting() {
if (formOfSetting.value.logLevel.length === 0) {
message.error(t('pages.settings.advanced.chooseLogLevel'))
return
}
saveConfig({
[configPaths.settings.logLevel]: formOfSetting.value.logLevel,
[configPaths.settings.logFileSizeLimit]: formOfSetting.value.logFileSizeLimit
})
logFileVisible.value = false
}
async function cancelLogLevelSetting() {
logFileVisible.value = false
let logLevel = await getConfig<string | string[]>(configPaths.settings.logLevel)
const logFileSizeLimit = (await getConfig<number>(configPaths.settings.logFileSizeLimit)) || 10
if (!Array.isArray(logLevel)) {
if (logLevel && logLevel.length > 0) {
logLevel = [logLevel]
} else {
logLevel = ['all']
}
}
formOfSetting.value.logLevel = logLevel
formOfSetting.value.logFileSizeLimit = logFileSizeLimit
}
function syncMessage(failed: number) {
if (failed) {
message.error(t('pages.settings.sync.syncResult.failed'))
} else {
message.success(t('pages.settings.sync.syncResult.success'))
}
}
const syncTaskList = [
{ task: IRPCActionType.CONFIGURE_UPLOAD_COMMON_CONFIG, label: t('pages.settings.sync.commonConfig'), number: 2 },
{ task: IRPCActionType.CONFIGURE_UPLOAD_MANAGE_CONFIG, label: t('pages.settings.sync.manageConfig'), number: 2 },
{ task: IRPCActionType.CONFIGURE_UPLOAD_ALL_CONFIG, label: t('pages.settings.sync.allConfig'), number: 4 },
{ task: IRPCActionType.CONFIGURE_DOWNLOAD_COMMON_CONFIG, label: t('pages.settings.sync.commonConfig'), number: 2 },
{ task: IRPCActionType.CONFIGURE_DOWNLOAD_MANAGE_CONFIG, label: t('pages.settings.sync.manageConfig'), number: 2 },
{ task: IRPCActionType.CONFIGURE_DOWNLOAD_ALL_CONFIG, label: t('pages.settings.sync.allConfig'), number: 4 }
]
async function syncTaskFn(task: string, number: number) {
const failed = number - ((await window.electron.triggerRPC<number>(task)) || 0)
syncMessage(failed)
}
function confirmServerSetting() {
server.value.port = parseInt(server.value.port as unknown as string, 10)
saveConfig({ [configPaths.settings.server]: server.value })
serverVisible.value = false
window.electron.sendRPC(IRPCActionType.ADVANCED_UPDATE_SERVER)
}
async function cancelServerSetting() {
serverVisible.value = false
server.value = (await getConfig(configPaths.settings.server)) || { port: 36677, host: '0.0.0.0', enable: true }
}
function handleLevelDisabled(val: string) {
const currentLevel = val
let flagLevel
const result = formOfSetting.value.logLevel.some((item: string) => {
if (item === 'all' || item === 'none') {
flagLevel = item
}
return item === 'all' || item === 'none'
})
if (result) {
if (currentLevel !== flagLevel) {
return true
}
} else if (formOfSetting.value.logLevel.length > 0) {
if (val === 'all' || val === 'none') {
return true
}
}
return false
}
function handleLanguageChange(val: string) {
locale.value = val
setCurrentLanguage(val)
saveConfig({ [configPaths.settings.language]: val })
localStorage.setItem('currentLanguage', val)
// updatePicBedGlobal()
}
function handleStartModeChange(val: string) {
if (val === ISartMode.NO_TRAY) {
if (formOfSetting.value.isHideDock) {
message.warning(t('pages.settings.system.hideDockHint'))
currentStartMode.value = ISartMode.QUIET
return
}
message.info(t('pages.settings.system.needRestart'))
}
saveConfig({ [configPaths.settings.startMode]: val })
}
async function goConfigPage() {
const lang = (await getConfig(configPaths.settings.language)) || II18nLanguage.ZH_CN
const url = `https://piclist.cn/${lang === II18nLanguage.EN ? 'en/' : ''}configure.html`
window.electron.sendRPC(IRPCActionType.OPEN_URL, url)
}
function goShortCutPage() {
$router.push({ name: SHORTKEY_PAGE })
}
</script>
<script lang="ts">
export default { name: 'SettingPage' }
</script>
<style scoped src="./css/PicgoSetting.css"></style>