feat: support custom theme color and dark mode

perf: replace css variables to theme variables
This commit is contained in:
tiny-craft 2023-07-13 15:46:12 +08:00
parent 73f637a9f8
commit 29f87f75c1
13 changed files with 193 additions and 120 deletions

View File

@ -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>

View File

@ -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 {

View File

@ -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;

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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'

View File

@ -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;

View File

@ -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",

View File

@ -105,6 +105,10 @@
"score": "分值",
"order_no": "序号",
"preferences": "偏好设置",
"theme": "主题",
"theme_light": "浅色",
"theme_dark": "深色",
"theme_auto": "自动",
"language": "语言",
"font": "字体",
"font_size": "字体尺寸",

View File

@ -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
},
},
})

View File

@ -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;
}
}