feat: support custom theme color and dark mode
perf: replace css variables to theme variables
This commit is contained in:
parent
73f637a9f8
commit
29f87f75c1
|
@ -11,18 +11,30 @@ import AddFieldsDialog from './components/dialogs/AddFieldsDialog.vue'
|
|||
import AppContent from './AppContent.vue'
|
||||
import GroupDialog from './components/dialogs/GroupDialog.vue'
|
||||
import DeleteKeyDialog from './components/dialogs/DeleteKeyDialog.vue'
|
||||
import { computed, onBeforeMount, ref } from 'vue'
|
||||
import { get } from 'lodash'
|
||||
import usePreferencesStore from './stores/preferences.js'
|
||||
import useConnectionStore from './stores/connections.js'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { darkTheme, lightTheme, useOsTheme } from 'naive-ui'
|
||||
|
||||
hljs.registerLanguage('json', json)
|
||||
hljs.registerLanguage('plaintext', plaintext)
|
||||
|
||||
/**
|
||||
*
|
||||
* @type import('naive-ui').GlobalThemeOverrides
|
||||
*/
|
||||
const themeOverrides = {
|
||||
common: {
|
||||
// primaryColor: '#409EFF',
|
||||
primaryColor: '#D33A31',
|
||||
primaryColorHover: '#FF6B6B',
|
||||
primaryColorPressed: '#D5271C',
|
||||
primaryColorSuppl: '#FF6B6B',
|
||||
borderRadius: '4px',
|
||||
borderRadiusSmall: '3px',
|
||||
fontFamily: `"Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue"`,
|
||||
lineHeight: 1.5,
|
||||
scrollbarWidth: '8px',
|
||||
},
|
||||
Tag: {
|
||||
// borderRadius: '3px'
|
||||
|
@ -33,13 +45,52 @@ const themeOverrides = {
|
|||
tabGapLargeCard: '1px',
|
||||
},
|
||||
}
|
||||
|
||||
const prefStore = usePreferencesStore()
|
||||
const connectionStore = useConnectionStore()
|
||||
const i18n = useI18n()
|
||||
const initializing = ref(false)
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
initializing.value = true
|
||||
await prefStore.loadPreferences()
|
||||
i18n.locale.value = get(prefStore.general, 'language', 'en')
|
||||
await prefStore.loadFontList()
|
||||
await connectionStore.initConnections()
|
||||
} finally {
|
||||
initializing.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const osTheme = useOsTheme()
|
||||
const theme = computed(() => {
|
||||
if (prefStore.general.theme === 'auto') {
|
||||
if (osTheme.value === 'dark') {
|
||||
return darkTheme
|
||||
}
|
||||
} else if (prefStore.general.theme === 'dark') {
|
||||
return darkTheme
|
||||
}
|
||||
return lightTheme
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider :hljs="hljs" :inline-theme-disabled="true" :theme-overrides="themeOverrides" class="fill-height">
|
||||
<n-config-provider
|
||||
:hljs="hljs"
|
||||
:inline-theme-disabled="true"
|
||||
:theme="theme"
|
||||
:theme-overrides="themeOverrides"
|
||||
class="fill-height"
|
||||
>
|
||||
<n-global-style />
|
||||
<n-message-provider>
|
||||
<n-dialog-provider>
|
||||
<app-content />
|
||||
<n-spin v-show="initializing" :theme-overrides="{ opacitySpinning: 0 }">
|
||||
<template #description>{{ $t('launching') }}</template>
|
||||
<div id="launch-container" />
|
||||
</n-spin>
|
||||
<app-content v-if="!initializing" />
|
||||
|
||||
<!-- top modal dialogs -->
|
||||
<connection-dialog />
|
||||
|
@ -55,4 +106,9 @@ const themeOverrides = {
|
|||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<style lang="scss"></style>
|
||||
<style lang="scss">
|
||||
#launch-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<script setup>
|
||||
import ContentPane from './components/content/ContentPane.vue'
|
||||
import BrowserPane from './components/sidebar/BrowserPane.vue'
|
||||
import { computed, nextTick, onBeforeMount, onMounted, reactive, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed, reactive } from 'vue'
|
||||
import { debounce, get } from 'lodash'
|
||||
import { useThemeVars } from 'naive-ui'
|
||||
import NavMenu from './components/sidebar/NavMenu.vue'
|
||||
|
@ -26,24 +25,11 @@ const prefStore = usePreferencesStore()
|
|||
const connectionStore = useConnectionStore()
|
||||
// const preferences = ref({})
|
||||
// provide('preferences', preferences)
|
||||
const i18n = useI18n()
|
||||
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
data.initializing = true
|
||||
await prefStore.loadPreferences()
|
||||
i18n.locale.value = get(prefStore.general, 'language', 'en')
|
||||
await prefStore.loadFontList()
|
||||
await connectionStore.initConnections()
|
||||
} finally {
|
||||
data.initializing = false
|
||||
}
|
||||
})
|
||||
|
||||
const saveWidth = debounce(prefStore.savePreferences, 1000, { trailing: true })
|
||||
const handleResize = (evt) => {
|
||||
if (data.resizing) {
|
||||
prefStore.setNavWidth(Math.max(evt.clientX - data.navMenuWidth, 300))
|
||||
prefStore.setAsideWidth(Math.max(evt.clientX - data.navMenuWidth, 300))
|
||||
saveWidth()
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +48,7 @@ const startResize = () => {
|
|||
}
|
||||
|
||||
const asideWidthVal = computed(() => {
|
||||
return prefStore.general.navMenuWidth + 'px'
|
||||
return prefStore.general.asideWidth + 'px'
|
||||
})
|
||||
|
||||
const dragging = computed(() => {
|
||||
|
@ -72,9 +58,6 @@ const dragging = computed(() => {
|
|||
|
||||
<template>
|
||||
<!-- app content-->
|
||||
<!-- <div id="app-container"></div>-->
|
||||
<n-spin :show="data.initializing" :theme-overrides="{ opacitySpinning: 0 }">
|
||||
<template #description> {{ $t('launching') }} </template>
|
||||
<div id="app-container" :class="{ dragging }" class="flex-box-h" :style="prefStore.generalFont">
|
||||
<nav-menu v-model:value="tabStore.nav" :width="data.navMenuWidth" />
|
||||
<!-- structure page-->
|
||||
|
@ -121,19 +104,18 @@ const dragging = computed(() => {
|
|||
<!-- log page -->
|
||||
<div v-show="tabStore.nav === 'log'">display log</div>
|
||||
</div>
|
||||
</n-spin>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
<style scoped lang="scss">
|
||||
#app-container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-top: var(--border-color) 1px solid;
|
||||
border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||
box-sizing: border-box;
|
||||
|
||||
#app-toolbar {
|
||||
height: 40px;
|
||||
border-bottom: var(--border-color) 1px solid;
|
||||
border-bottom: v-bind('themeVars.borderColor') 1px solid;
|
||||
}
|
||||
|
||||
#app-side {
|
||||
|
@ -144,7 +126,7 @@ const dragging = computed(() => {
|
|||
//height: 100%;
|
||||
width: 2px;
|
||||
border-left-width: 5px;
|
||||
background-color: var(--border-color);
|
||||
background-color: v-bind('themeVars.dividerColor');
|
||||
}
|
||||
|
||||
.resize-divider-hover {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.content-container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
//background-color: var(--bg-color);
|
||||
padding-top: 2px;
|
||||
padding-bottom: 5px;
|
||||
box-sizing: border-box;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'
|
|||
import ContentToolbar from './ContentToolbar.vue'
|
||||
import Copy from '../icons/Copy.vue'
|
||||
import Save from '../icons/Save.vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { useMessage, useThemeVars } from 'naive-ui'
|
||||
import { types } from '../../consts/value_view_type.js'
|
||||
import Close from '../icons/Close.vue'
|
||||
import Edit from '../icons/Edit.vue'
|
||||
|
@ -13,6 +13,7 @@ import { types as redisTypes } from '../../consts/support_redis_type.js'
|
|||
import { ClipboardSetText } from '../../../wailsjs/runtime/runtime.js'
|
||||
import { toLower } from 'lodash'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
const themeVars = useThemeVars()
|
||||
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
|
@ -215,5 +216,6 @@ const onSaveValue = async () => {
|
|||
<style lang="scss" scoped>
|
||||
.value-wrapper {
|
||||
overflow: hidden;
|
||||
border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -76,6 +76,13 @@ const onClose = () => {
|
|||
label-align="right"
|
||||
label-placement="left"
|
||||
>
|
||||
<n-form-item :label="$t('theme')" required>
|
||||
<n-radio-group v-model:value="prefStore.general.theme" name="theme" size="medium">
|
||||
<n-radio-button v-for="opt in prefStore.themeOption" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</n-radio-button>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('language')" required>
|
||||
<n-select
|
||||
v-model:value="prefStore.general.language"
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<script setup>
|
||||
import { NIcon, useMessage } from 'naive-ui'
|
||||
import { useMessage, useThemeVars } from 'naive-ui'
|
||||
import AddLink from '../icons/AddLink.vue'
|
||||
import BrowserTree from './BrowserTree.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
import Filter from '../icons/Filter.vue'
|
||||
import useTabStore from '../../stores/tab.js'
|
||||
import { computed } from 'vue'
|
||||
import { get } from 'lodash'
|
||||
|
@ -14,6 +13,7 @@ import { useConfirmDialog } from '../../utils/confirm_dialog.js'
|
|||
import { useI18n } from 'vue-i18n'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
const themeVars = useThemeVars()
|
||||
const dialogStore = useDialogStore()
|
||||
const tabStore = useTabStore()
|
||||
const currentName = computed(() => get(tabStore.currentTab, 'name', ''))
|
||||
|
@ -59,27 +59,12 @@ const onRefresh = () => {
|
|||
|
||||
<!-- bottom function bar -->
|
||||
<div class="nav-pane-bottom flex-box-h">
|
||||
<icon-button
|
||||
:icon="AddLink"
|
||||
color="#555"
|
||||
size="20"
|
||||
stroke-width="4"
|
||||
t-tooltip="new_key"
|
||||
@click="onNewKey"
|
||||
/>
|
||||
<icon-button
|
||||
:icon="Refresh"
|
||||
color="#555"
|
||||
size="20"
|
||||
stroke-width="4"
|
||||
t-tooltip="reload"
|
||||
@click="onRefresh"
|
||||
/>
|
||||
<icon-button :icon="AddLink" size="20" stroke-width="4" t-tooltip="new_key" @click="onNewKey" />
|
||||
<icon-button :icon="Refresh" size="20" stroke-width="4" t-tooltip="reload" @click="onRefresh" />
|
||||
<div class="flex-item-expand"></div>
|
||||
<icon-button
|
||||
:disabled="currentSelect.key == null"
|
||||
:icon="Delete"
|
||||
color="#555"
|
||||
size="20"
|
||||
stroke-width="4"
|
||||
t-tooltip="remove_key"
|
||||
|
@ -94,4 +79,9 @@ const onRefresh = () => {
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style scoped lang="scss">
|
||||
.nav-pane-bottom {
|
||||
color: v-bind('themeVars.iconColor');
|
||||
border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import useDialogStore from '../../stores/dialog.js'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import { NIcon, useThemeVars } from 'naive-ui'
|
||||
import AddGroup from '../icons/AddGroup.vue'
|
||||
import AddLink from '../icons/AddLink.vue'
|
||||
import IconButton from '../common/IconButton.vue'
|
||||
|
@ -10,6 +10,7 @@ import Unlink from '../icons/Unlink.vue'
|
|||
import useConnectionStore from '../../stores/connections.js'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const themeVars = useThemeVars()
|
||||
const dialogStore = useDialogStore()
|
||||
const connectionStore = useConnectionStore()
|
||||
const filterPattern = ref('')
|
||||
|
@ -27,7 +28,6 @@ const onDisconnectAll = () => {
|
|||
<div class="nav-pane-bottom flex-box-h">
|
||||
<icon-button
|
||||
:icon="AddLink"
|
||||
color="#555"
|
||||
size="20"
|
||||
stroke-width="4"
|
||||
t-tooltip="new_conn"
|
||||
|
@ -35,7 +35,6 @@ const onDisconnectAll = () => {
|
|||
/>
|
||||
<icon-button
|
||||
:icon="AddGroup"
|
||||
color="#555"
|
||||
size="20"
|
||||
stroke-width="4"
|
||||
t-tooltip="new_group"
|
||||
|
@ -44,7 +43,6 @@ const onDisconnectAll = () => {
|
|||
<icon-button
|
||||
:disabled="!connectionStore.anyConnectionOpened"
|
||||
:icon="Unlink"
|
||||
color="#555"
|
||||
size="20"
|
||||
stroke-width="4"
|
||||
t-tooltip="disconnect_all"
|
||||
|
@ -59,4 +57,9 @@ const onDisconnectAll = () => {
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style scoped lang="scss">
|
||||
.nav-pane-bottom {
|
||||
color: v-bind('themeVars.iconColor');
|
||||
border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<script setup>
|
||||
import useDialogStore from '../../stores/dialog.js'
|
||||
import { h, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { h, nextTick, reactive, ref } from 'vue'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
import { NIcon, useDialog, useMessage } from 'naive-ui'
|
||||
import { ConnectionType } from '../../consts/connection_type.js'
|
||||
import ToggleFolder from '../icons/ToggleFolder.vue'
|
||||
import ToggleServer from '../icons/ToggleServer.vue'
|
||||
import { debounce, indexOf, throttle } from 'lodash'
|
||||
import { debounce, indexOf } from 'lodash'
|
||||
import Config from '../icons/Config.vue'
|
||||
import Delete from '../icons/Delete.vue'
|
||||
import Unlink from '../icons/Unlink.vue'
|
||||
|
|
|
@ -114,7 +114,7 @@ const openGithub = () => {
|
|||
:collapsed-icon-size="iconSize"
|
||||
@update:value="(val) => emit('update:value', val)"
|
||||
:options="menuOptions"
|
||||
></n-menu>
|
||||
/>
|
||||
<div class="flex-item-expand"></div>
|
||||
<div class="nav-menu-item flex-box-v">
|
||||
<n-dropdown
|
||||
|
@ -127,7 +127,7 @@ const openGithub = () => {
|
|||
>
|
||||
<icon-button :icon="Config" :size="iconSize" class="nav-menu-button" />
|
||||
</n-dropdown>
|
||||
<icon-button :icon="Github" :size="iconSize" class="nav-menu-button" @click="openGithub"></icon-button>
|
||||
<icon-button :icon="Github" :size="iconSize" class="nav-menu-button" @click="openGithub" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -136,7 +136,7 @@ const openGithub = () => {
|
|||
#app-nav-menu {
|
||||
//width: 60px;
|
||||
height: 100vh;
|
||||
border-right: var(--border-color) solid 1px;
|
||||
border-right: v-bind('themeVars.borderColor') solid 1px;
|
||||
|
||||
.nav-menu-item {
|
||||
align-items: center;
|
||||
|
|
|
@ -102,6 +102,10 @@
|
|||
"score": "Score",
|
||||
"order_no": "Order",
|
||||
"preferences": "Preferences",
|
||||
"theme": "Theme",
|
||||
"theme_light": "Light",
|
||||
"theme_dark": "Dark",
|
||||
"theme_auto": "Auto",
|
||||
"language": "Language",
|
||||
"font": "Font",
|
||||
"font_size": "Font Size",
|
||||
|
|
|
@ -105,6 +105,10 @@
|
|||
"score": "分值",
|
||||
"order_no": "序号",
|
||||
"preferences": "偏好设置",
|
||||
"theme": "主题",
|
||||
"theme_light": "浅色",
|
||||
"theme_dark": "深色",
|
||||
"theme_auto": "自动",
|
||||
"language": "语言",
|
||||
"font": "字体",
|
||||
"font_size": "字体尺寸",
|
||||
|
|
|
@ -27,13 +27,14 @@ const usePreferencesStore = defineStore('preferences', {
|
|||
*/
|
||||
state: () => ({
|
||||
general: {
|
||||
theme: 'light',
|
||||
language: 'en',
|
||||
font: '',
|
||||
fontSize: 14,
|
||||
useSysProxy: false,
|
||||
useSysProxyHttp: false,
|
||||
checkUpdate: false,
|
||||
navMenuWidth: 300,
|
||||
asideWidth: 300,
|
||||
},
|
||||
editor: {
|
||||
font: '',
|
||||
|
@ -47,6 +48,24 @@ const usePreferencesStore = defineStore('preferences', {
|
|||
return ':'
|
||||
},
|
||||
|
||||
themeOption() {
|
||||
const i18n = useI18n()
|
||||
return [
|
||||
{
|
||||
value: 'light',
|
||||
label: i18n.t('theme_light'),
|
||||
},
|
||||
{
|
||||
value: 'dark',
|
||||
label: i18n.t('theme_dark'),
|
||||
},
|
||||
{
|
||||
value: 'auto',
|
||||
label: i18n.t('theme_auto'),
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
/**
|
||||
* all available language
|
||||
* @returns {{label: string, value: string}[]}
|
||||
|
@ -58,6 +77,10 @@ const usePreferencesStore = defineStore('preferences', {
|
|||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* all system font list
|
||||
* @returns {{path: string, label: string, value: string}[]}
|
||||
*/
|
||||
fontOption() {
|
||||
const i18n = useI18n()
|
||||
const option = map(this.fontList, (font) => ({
|
||||
|
@ -73,6 +96,10 @@ const usePreferencesStore = defineStore('preferences', {
|
|||
return option
|
||||
},
|
||||
|
||||
/**
|
||||
* current font selection
|
||||
* @returns {{fontSize: string}}
|
||||
*/
|
||||
generalFont() {
|
||||
const fontStyle = {
|
||||
fontSize: this.general.fontSize + 'px',
|
||||
|
@ -167,8 +194,8 @@ const usePreferencesStore = defineStore('preferences', {
|
|||
return false
|
||||
},
|
||||
|
||||
setNavWidth(width) {
|
||||
this.general.navMenuWidth = width
|
||||
setAsideWidth(width) {
|
||||
this.general.asideWidth = width
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
:root {
|
||||
--bg-color: #ffffff;
|
||||
--bg-color-page: #f2f3f5;
|
||||
--text-color-regular: #606266;
|
||||
--border-color: #dcdfe6;
|
||||
//--bg-color: #ffffff;
|
||||
//--bg-color-page: #f2f3f5;
|
||||
//--text-color-regular: #606266;
|
||||
//--border-color: #dcdfe6;
|
||||
--transition-duration-fast: 0.2s;
|
||||
--transition-function-ease-in-out-bezier: cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #ffffff;
|
||||
//text-align: center;
|
||||
cursor: default;
|
||||
-webkit-user-select: none; /* Chrome, Safari */
|
||||
|
@ -82,8 +81,8 @@ body {
|
|||
}
|
||||
|
||||
.content-wrapper {
|
||||
background-color: var(--bg-color);
|
||||
height: 100%;
|
||||
//height: 100%;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
gap: 5px;
|
||||
padding: 5px;
|
||||
|
@ -96,7 +95,7 @@ body {
|
|||
}
|
||||
|
||||
.value-wrapper {
|
||||
border-top: var(--border-color) 1px solid;
|
||||
//border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||
padding: 5px;
|
||||
user-select: text;
|
||||
}
|
||||
|
@ -118,13 +117,12 @@ body {
|
|||
|
||||
.nav-pane-container {
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
|
||||
.nav-pane-bottom {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 3px 5px 5px 5px;
|
||||
min-height: 35px;
|
||||
border-top: var(--border-color) 1px solid;
|
||||
//border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue