Compare commits

..

7 Commits

Author SHA1 Message Date
tiny-craft faad24d1d5 perf: force change icon button color when then state is loading 2023-10-28 22:42:15 +08:00
tiny-craft 6fb0eadfbd style: adjusted the light and dark themes
style: add window shadow on macOS #62
2023-10-28 22:31:02 +08:00
tiny-craft 124627d724 refactor: wrapping element with drag and resize behavior as a standalone component 2023-10-27 21:53:05 +08:00
tiny-craft b6eebda177 style: update style of browser tabs
style: update style of connection dialog
2023-10-27 21:53:05 +08:00
tiny-craft 33051f4e46 fix: last preferences did not clone correctly 2023-10-27 13:01:18 +08:00
tiny-craft 85ab6e4377 style: formatted code 2023-10-27 11:34:40 +08:00
tiny-craft 297286f150 perf: create "content-pane" component for each tab to maintain data and status easily 2023-10-27 11:34:40 +08:00
34 changed files with 557 additions and 496 deletions

View File

@ -18,7 +18,7 @@ import { useI18n } from 'vue-i18n'
import { darkTheme } from 'naive-ui'
import KeyFilterDialog from './components/dialogs/KeyFilterDialog.vue'
import { WindowSetDarkTheme, WindowSetLightTheme } from 'wailsjs/runtime/runtime.js'
import { themeOverrides } from '@/utils/theme.js'
import { darkThemeOverrides, themeOverrides } from '@/utils/theme.js'
import AboutDialog from '@/components/dialogs/AboutDialog.vue'
hljs.registerLanguage('json', json)
@ -60,7 +60,7 @@ watch(
:inline-theme-disabled="true"
:locale="prefStore.themeLocale"
:theme="prefStore.isDark ? darkTheme : undefined"
:theme-overrides="themeOverrides"
:theme-overrides="prefStore.isDark ? darkThemeOverrides : themeOverrides"
class="fill-height">
<n-dialog-provider>
<app-content :loading="initializing" />

View File

@ -16,6 +16,8 @@ import ToolbarControlWidget from '@/components/common/ToolbarControlWidget.vue'
import { EventsOn, WindowIsFullscreen, WindowIsMaximised, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js'
import { isMacOS } from '@/utils/platform.js'
import iconUrl from '@/assets/images/icon.png'
import ResizeableWrapper from '@/components/common/ResizeableWrapper.vue'
import { extraTheme } from '@/utils/extra_theme.js'
const themeVars = useThemeVars()
@ -25,8 +27,6 @@ const props = defineProps({
const data = reactive({
navMenuWidth: 60,
hoverResize: false,
resizing: false,
toolbarHeight: 45,
})
@ -34,37 +34,17 @@ const tabStore = useTabStore()
const prefStore = usePreferencesStore()
const connectionStore = useConnectionStore()
const logPaneRef = ref(null)
const exThemeVars = computed(() => {
return extraTheme(prefStore.isDark)
})
// const preferences = ref({})
// provide('preferences', preferences)
const saveSidebarWidth = debounce(prefStore.savePreferences, 1000, { trailing: true })
const handleResize = (evt) => {
if (data.resizing) {
prefStore.setAsideWidth(Math.max(evt.clientX - data.navMenuWidth, 300))
saveSidebarWidth()
}
const handleResize = () => {
saveSidebarWidth()
}
const stopResize = () => {
data.resizing = false
document.removeEventListener('mousemove', handleResize)
document.removeEventListener('mouseup', stopResize)
}
const startResize = () => {
data.resizing = true
document.addEventListener('mousemove', handleResize)
document.addEventListener('mouseup', stopResize)
}
const asideWidthVal = computed(() => {
return prefStore.behavior.asideWidth + 'px'
})
const dragging = computed(() => {
return data.hoverResize || data.resizing
})
watch(
() => tabStore.nav,
(nav) => {
@ -75,8 +55,11 @@ watch(
)
const border = computed(() => {
const color = isMacOS() && false ? '#0000' : themeVars.value.borderColor
return `1px solid ${color}`
return isMacOS() ? 'none' : `1px solid ${themeVars.value.borderColor}`
})
const logoWrapperWidth = computed(() => {
return `${data.navMenuWidth + prefStore.behavior.asideWidth - 4}px`
})
const borderRadius = ref(10)
@ -128,15 +111,9 @@ onMounted(async () => {
<!-- app content-->
<n-spin
:show="props.loading"
:style="{ backgroundColor: themeVars.bodyColor, borderRadius: `${borderRadius}px`, border }"
:style="{ borderRadius: `${borderRadius}px`, border }"
:theme-overrides="{ opacitySpinning: 0 }">
<div
id="app-content-wrapper"
:style="{
backgroundColor: themeVars.bodyColor,
color: themeVars.textColorBase,
}"
class="flex-box-v">
<div id="app-content-wrapper" :style="{ color: themeVars.textColorBase }" class="flex-box-v">
<!-- title bar -->
<div
id="app-toolbar"
@ -148,7 +125,8 @@ onMounted(async () => {
<div
id="app-toolbar-title"
:style="{
width: `${data.navMenuWidth + prefStore.behavior.asideWidth - 4}px`,
width: logoWrapperWidth,
minWidth: logoWrapperWidth,
paddingLeft: `${logoPaddingLeft}px`,
}">
<n-space :size="3" :wrap="false" :wrap-item="false" align="center">
@ -161,15 +139,6 @@ onMounted(async () => {
</transition>
</n-space>
</div>
<div
:class="{
'resize-divider-hover': data.hoverResize,
'resize-divider-drag': data.resizing,
}"
class="resize-divider resize-divider-hide"
@mousedown="startResize"
@mouseout="data.hoverResize = false"
@mouseover="data.hoverResize = true" />
<!-- browser tabs -->
<div v-show="tabStore.nav === 'browser'" class="app-toolbar-tab flex-item-expand">
<content-value-tab />
@ -191,40 +160,37 @@ onMounted(async () => {
style="--wails-draggable: none">
<nav-menu v-model:value="tabStore.nav" :width="data.navMenuWidth" />
<!-- browser page -->
<div v-show="tabStore.nav === 'browser'" :class="{ dragging }" class="flex-box-h flex-item-expand">
<div id="app-side" :style="{ width: asideWidthVal }" class="flex-box-h flex-item">
<div v-show="tabStore.nav === 'browser'" class="flex-box-h flex-item-expand">
<resizeable-wrapper
v-model:size="prefStore.behavior.asideWidth"
:min-size="300"
:offset="data.navMenuWidth"
class="flex-item"
@update:size="handleResize">
<browser-pane
v-for="t in tabStore.tabs"
v-show="get(tabStore.currentTab, 'name') === t.name"
:key="t.name"
class="flex-item-expand" />
<div
:class="{
'resize-divider-hover': data.hoverResize,
'resize-divider-drag': data.resizing,
}"
class="resize-divider"
@mousedown="startResize"
@mouseout="data.hoverResize = false"
@mouseover="data.hoverResize = true" />
</div>
<content-pane class="flex-item-expand" />
class="app-side flex-item-expand" />
</resizeable-wrapper>
<content-pane
v-for="t in tabStore.tabs"
v-show="get(tabStore.currentTab, 'name') === t.name"
:key="t.name"
:server="t.name"
class="flex-item-expand" />
</div>
<!-- server list page -->
<div v-show="tabStore.nav === 'server'" :class="{ dragging }" class="flex-box-h flex-item-expand">
<div id="app-side" :style="{ width: asideWidthVal }" class="flex-box-h flex-item">
<connection-pane class="flex-item-expand" />
<div
:class="{
'resize-divider-hover': data.hoverResize,
'resize-divider-drag': data.resizing,
}"
class="resize-divider"
@mousedown="startResize"
@mouseout="data.hoverResize = false"
@mouseover="data.hoverResize = true" />
</div>
<div v-show="tabStore.nav === 'server'" class="flex-box-h flex-item-expand">
<resizeable-wrapper
v-model:size="prefStore.behavior.asideWidth"
:min-size="300"
:offset="data.navMenuWidth"
class="flex-item"
@update:size="handleResize">
<connection-pane class="app-side flex-item-expand" />
</resizeable-wrapper>
<content-server-pane class="flex-item-expand" />
</div>
@ -239,15 +205,16 @@ onMounted(async () => {
<style lang="scss" scoped>
#app-content-wrapper {
width: calc(100vw - 2px);
height: calc(100vh - 2px);
width: 100vw;
height: 100vh;
overflow: hidden;
box-sizing: border-box;
border-radius: 10px;
border-radius: 8px;
background-color: v-bind('themeVars.bodyColor');
#app-toolbar {
background-color: v-bind('themeVars.tabColor');
border-bottom: 1px solid v-bind('themeVars.borderColor');
background-color: v-bind('exThemeVars.titleColor');
border-bottom: 1px solid v-bind('exThemeVars.splitColor');
&-title {
padding-left: 10px;
@ -268,37 +235,14 @@ onMounted(async () => {
height: calc(100% - 60px);
}
#app-side {
.app-side {
//overflow: hidden;
height: 100%;
background-color: v-bind('themeVars.tabColor');
background-color: v-bind('exThemeVars.sidebarColor');
border-right: 1px solid v-bind('exThemeVars.splitColor');
}
}
.resize-divider {
width: 3px;
border-right: 1px solid v-bind('themeVars.borderColor');
}
.resize-divider-hide {
background-color: #0000;
border-right-color: #0000;
}
.resize-divider-hover {
background-color: v-bind('themeVars.borderColor');
border-right-color: v-bind('themeVars.borderColor');
}
.resize-divider-drag {
background-color: v-bind('themeVars.primaryColor');
border-right-color: v-bind('themeVars.primaryColor');
}
.dragging {
cursor: col-resize !important;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;

View File

@ -28,4 +28,4 @@ const handleSelectFile = async () => {
</n-input-group>
</template>
<style scoped lang="scss"></style>
<style lang="scss" scoped></style>

View File

@ -14,7 +14,7 @@ const props = defineProps({
},
color: {
type: String,
default: 'currentColor',
default: '',
},
strokeWidth: {
type: [Number, String],
@ -38,9 +38,10 @@ const hasTooltip = computed(() => {
:focusable="false"
:loading="loading"
:text="!border"
:color="props.color"
@click.prevent="emit('click')">
<template #icon>
<n-icon :color="props.color" :size="props.size">
<n-icon :color="props.color || 'currentColor'" :size="props.size">
<component :is="props.icon" :stroke-width="props.strokeWidth" />
</n-icon>
</template>
@ -54,9 +55,10 @@ const hasTooltip = computed(() => {
:focusable="false"
:loading="loading"
:text="!border"
:color="props.color"
@click.prevent="emit('click')">
<template #icon>
<n-icon :color="props.color" :size="props.size">
<n-icon :color="props.color || 'currentColor'" :size="props.size">
<component :is="props.icon" :stroke-width="props.strokeWidth" />
</n-icon>
</template>

View File

@ -0,0 +1,122 @@
<script setup>
import { useThemeVars } from 'naive-ui'
import { ref } from 'vue'
/**
* Resizeable component wrapper
*/
const themeVars = useThemeVars()
const props = defineProps({
size: {
type: Number,
default: 100,
},
minSize: {
type: Number,
default: 300,
},
maxSize: {
type: Number,
default: 0,
},
offset: {
type: Number,
default: 0,
},
disabled: {
type: Boolean,
default: false,
},
borderWidth: {
type: Number,
default: 4,
},
})
const emit = defineEmits(['update:size'])
const resizing = ref(false)
const hover = ref(false)
const handleResize = (evt) => {
if (resizing.value) {
let size = evt.clientX - props.offset
if (size < props.minSize) {
size = props.minSize
}
if (props.maxSize > 0 && size > props.maxSize) {
size = props.maxSize
}
emit('update:size', size)
}
}
const stopResize = () => {
resizing.value = false
document.removeEventListener('mousemove', handleResize)
document.removeEventListener('mouseup', stopResize)
}
const startResize = () => {
if (props.disabled) {
return
}
resizing.value = true
document.addEventListener('mousemove', handleResize)
document.addEventListener('mouseup', stopResize)
}
const handleMouseOver = () => {
if (props.disabled) {
return
}
hover.value = true
}
</script>
<template>
<div :style="{ width: props.size + 'px' }" class="resize-wrapper flex-box-h">
<slot></slot>
<div
:class="{
'resize-divider-hover': hover,
'resize-divider-drag': resizing,
dragging: hover || resizing,
}"
:style="{ width: props.borderWidth + 'px', right: Math.floor(-props.borderWidth / 2) + 'px' }"
class="resize-divider"
@mousedown="startResize"
@mouseout="hover = false"
@mouseover="handleMouseOver" />
</div>
</template>
<style lang="scss" scoped>
.resize-wrapper {
position: relative;
.resize-divider {
position: absolute;
top: 0;
bottom: 0;
transition: background-color 0.3s ease-in;
}
.resize-divider-hide {
background-color: #0000;
}
.resize-divider-hover {
background-color: v-bind('themeVars.borderColor');
}
.resize-divider-drag {
background-color: v-bind('themeVars.primaryColor');
}
.dragging {
cursor: col-resize !important;
}
}
</style>

View File

@ -42,12 +42,12 @@ const handleSwitch = (idx) => {
<template>
<n-button-group>
<n-tooltip
:show-arrow="false"
v-for="(icon, i) in props.icons"
:key="i"
:disabled="!(props.tTooltips && props.tTooltips[i])">
:disabled="!(props.tTooltips && props.tTooltips[i])"
:show-arrow="false">
<template #trigger>
<n-button :tertiary="i !== props.value" :focusable="false" :size="props.size" @click="handleSwitch(i)">
<n-button :focusable="false" :size="props.size" :tertiary="i !== props.value" @click="handleSwitch(i)">
<template #icon>
<n-icon :size="props.iconSize">
<component
@ -62,4 +62,4 @@ const handleSwitch = (idx) => {
</n-button-group>
</template>
<style scoped lang="scss"></style>
<style lang="scss" scoped></style>

View File

@ -73,10 +73,10 @@ defineExpose({
<template>
<n-card
:bordered="false"
:theme-overrides="{ borderRadius: '0px' }"
:title="$t('log.launch_log')"
class="content-container flex-box-v"
content-style="display: flex;flex-direction: column; overflow: hidden; backgroundColor: gray"
:theme-overrides="{ borderRadius: '0px' }">
content-style="display: flex;flex-direction: column; overflow: hidden; backgroundColor: gray">
<n-form :disabled="data.loading" class="flex-item" inline>
<n-form-item :label="$t('log.filter_server')">
<n-select
@ -162,6 +162,5 @@ defineExpose({
.content-container {
padding: 5px;
box-sizing: border-box;
border-left: 1px solid v-bind('themeVars.borderColor');
}
</style>

View File

@ -1,8 +1,7 @@
<script setup>
import { computed, onMounted, onUnmounted, reactive, watch } from 'vue'
import { get, isEmpty, keyBy, map, size, toUpper } from 'lodash'
import { computed, nextTick, ref, watch } from 'vue'
import { find, map, toUpper } from 'lodash'
import useTabStore from 'stores/tab.js'
import useConnectionStore from 'stores/connections.js'
import ContentServerStatus from '@/components/content_value/ContentServerStatus.vue'
import Status from '@/components/icons/Status.vue'
import { useThemeVars } from 'naive-ui'
@ -24,98 +23,10 @@ const themeVars = useThemeVars()
* @property {boolean} autoLoading loading status for auto refresh
*/
/**
*
* @type {UnwrapNestedRefs<Object.<string, ServerStatusItem>>}
*/
const serverStatusTab = reactive({})
/**
*
* @param {string} serverName
* @return {UnwrapRef<ServerStatusItem>}
*/
const getServerInfo = (serverName) => {
if (isEmpty(serverName)) {
return {
name: serverName,
info: {},
autoRefresh: false,
autoLoading: false,
loading: false,
}
}
if (!serverStatusTab.hasOwnProperty(serverName)) {
serverStatusTab[serverName] = {
name: serverName,
info: {},
autoRefresh: false,
autoLoading: false,
loading: false,
}
}
return serverStatusTab[serverName]
}
const serverName = computed(() => {
if (tabContent.value != null) {
return tabContent.value.name
}
return ''
})
/**
*
* @type {ComputedRef<ServerStatusItem>}
*/
const currentServer = computed(() => {
return getServerInfo(serverName.value)
const props = defineProps({
server: String,
})
/**
* refresh server status info
* @param {string} serverName
* @param {boolean} [force] force refresh will show loading indicator
* @returns {Promise<void>}
*/
const refreshInfo = async (serverName, force) => {
const info = getServerInfo(serverName)
if (force) {
info.loading = true
} else {
info.autoLoading = true
}
if (!isEmpty(serverName) && connectionStore.isConnected(serverName)) {
try {
info.info = await connectionStore.getServerInfo(serverName)
} finally {
info.loading = false
info.autoLoading = false
}
}
}
const refreshAllInfo = async (force) => {
for (const serverName in serverStatusTab) {
await refreshInfo(serverName, force)
}
}
let intervalId
onMounted(() => {
refreshAllInfo(true)
intervalId = setInterval(() => {
for (const serverName in serverStatusTab) {
if (get(serverStatusTab, [serverName, 'autoRefresh'])) {
refreshInfo(serverName)
}
}
}, 5000)
})
onUnmounted(() => {
clearInterval(intervalId)
})
const connectionStore = useConnectionStore()
const tabStore = useTabStore()
const tab = computed(() =>
map(tabStore.tabs, (item) => ({
@ -124,35 +35,10 @@ const tab = computed(() =>
})),
)
watch(
() => tabStore.nav,
(nav) => {
if (nav === 'browser') {
refreshInfo(serverName.value)
}
},
)
watch(
() => tabStore.tabList,
(tabs) => {
if (size(tabs) < size(serverStatusTab)) {
const tabMap = keyBy(tabs, 'name')
// remove unused server status tab
for (const t in serverStatusTab) {
if (!tabMap.hasOwnProperty(t)) {
delete serverStatusTab[t]
}
}
}
},
{ deep: true },
)
const tabContent = computed(() => {
const tab = tabStore.currentTab
const tab = find(tabStore.tabs, { name: props.server })
if (tab == null) {
return null
return {}
}
return {
name: tab.name,
@ -168,39 +54,36 @@ const tabContent = computed(() => {
}
})
const showServerStatus = computed(() => {
return tabContent.value == null || isEmpty(tabContent.value.keyPath)
})
const isBlankValue = computed(() => {
return tabContent.value.value == null
})
/**
* reload current selection key
* @returns {Promise<null>}
*/
const onReloadKey = async () => {
const tab = tabStore.currentTab
if (tab == null || isEmpty(tab.key)) {
return null
}
await connectionStore.loadKeyValue(tab.name, tab.db, tab.key, tab.viewAs)
}
const selectedSubTab = computed(() => {
const { subTab = 'status' } = tabStore.currentTab || {}
return subTab
})
const onSwitchSubTab = (name) => {
tabStore.switchSubTab(name)
}
// BUG: naive-ui tabs will set the bottom line to '0px' after switch to another page and back again
// watch parent tabs' changing and call 'syncBarPosition' manually
const tabsRef = ref(null)
const cliRef = ref(null)
watch(
() => tabContent.value?.name,
(name) => {
if (name === props.server) {
nextTick().then(() => {
tabsRef.value?.syncBarPosition()
cliRef.value?.resizeTerm()
})
}
},
)
</script>
<template>
<div class="content-container flex-box-v">
<n-tabs
ref="tabsRef"
:tabs-padding="5"
:theme-overrides="{
tabFontWeightActive: 'normal',
@ -215,9 +98,9 @@ const onSwitchSubTab = (name) => {
placement="top"
tab-style="padding-left: 10px; padding-right: 10px;"
type="line"
@update:value="onSwitchSubTab">
@update:value="tabStore.switchSubTab">
<!-- server status pane -->
<n-tab-pane :name="BrowserTabType.Status.toString()">
<n-tab-pane :name="BrowserTabType.Status.toString()" display-directive="show:lazy">
<template #tab>
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
<n-icon size="16">
@ -226,17 +109,11 @@ const onSwitchSubTab = (name) => {
<span>{{ $t('interface.sub_tab.status') }}</span>
</n-space>
</template>
<content-server-status
v-model:auto-refresh="currentServer.autoRefresh"
:auto-loading="currentServer.autoLoading"
:info="currentServer.info"
:loading="currentServer.loading"
:server="currentServer.name"
@refresh="refreshInfo(currentServer.name, true)" />
<content-server-status :server="props.server" />
</n-tab-pane>
<!-- key detail pane -->
<n-tab-pane :name="BrowserTabType.KeyDetail.toString()">
<n-tab-pane :name="BrowserTabType.KeyDetail.toString()" display-directive="show:lazy">
<template #tab>
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
<n-icon size="16">
@ -249,20 +126,19 @@ const onSwitchSubTab = (name) => {
</template>
<content-value-wrapper
:blank="isBlankValue"
:type="tabContent.type"
:db="tabContent.db"
:key-code="tabContent.keyCode"
:key-path="tabContent.keyPath"
:name="tabContent.name"
:size="tabContent.size"
:ttl="tabContent.ttl"
:type="tabContent.type"
:value="tabContent.value"
:view-as="tabContent.viewAs"
@reload="onReloadKey" />
:view-as="tabContent.viewAs" />
</n-tab-pane>
<!-- cli pane -->
<n-tab-pane :name="BrowserTabType.Cli.toString()">
<n-tab-pane :name="BrowserTabType.Cli.toString()" display-directive="show:lazy">
<template #tab>
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
<n-icon size="16">
@ -271,7 +147,7 @@ const onSwitchSubTab = (name) => {
<span>{{ $t('interface.sub_tab.cli') }}</span>
</n-space>
</template>
<content-cli :name="currentServer.name" />
<content-cli ref="cliRef" :name="props.server" />
</n-tab-pane>
<!-- slow log pane -->
@ -302,14 +178,18 @@ const onSwitchSubTab = (name) => {
<style lang="scss">
.content-sub-tab {
background-color: v-bind('themeVars.bodyColor');
background-color: v-bind('themeVars.tabColor');
height: 100%;
}
.content-sub-tab-pane {
padding: 0 !important;
height: 100%;
background-color: v-bind('themeVars.tabColor');
background-color: v-bind('themeVars.bodyColor');
overflow: hidden;
}
.n-tabs .n-tabs-bar {
transition: none !important;
}
</style>

View File

@ -6,33 +6,37 @@ import { useI18n } from 'vue-i18n'
import { get, map } from 'lodash'
import { useThemeVars } from 'naive-ui'
import useConnectionStore from 'stores/connections.js'
import { extraTheme } from '@/utils/extra_theme.js'
import usePreferencesStore from 'stores/preferences.js'
/**
* Value content tab on head
*/
const themeVars = useThemeVars()
const i18n = useI18n()
const tabStore = useTabStore()
const connectionStore = useConnectionStore()
const prefStore = usePreferencesStore()
const onCloseTab = (tabIndex) => {
$dialog.warning(i18n.t('dialogue.close_confirm'), () => {
const tab = get(tabStore.tabs, tabIndex)
if (tab != null) {
const tab = get(tabStore.tabs, tabIndex)
if (tab != null) {
$dialog.warning(i18n.t('dialogue.close_confirm', { name: tab.name }), () => {
connectionStore.closeConnection(tab.name)
}
})
})
}
}
const activeTabStyle = computed(() => {
const { name } = tabStore.currentTab
const tabMarkColor = computed(() => {
const { name } = tabStore?.currentTab || {}
const { markColor = '' } = connectionStore.serverProfile[name] || {}
return {
borderTopWidth: markColor ? '3px' : '1px',
borderTopColor: markColor || themeVars.value.borderColor,
}
return markColor
})
const tabClass = (idx) => {
if (tabStore.activatedIndex === idx) {
return ['value-tab', 'value-tab-active']
return ['value-tab', 'value-tab-active', tabMarkColor.value ? 'value-tab-active_mark' : '']
} else if (tabStore.activatedIndex - 1 === idx) {
return ['value-tab', 'value-tab-inactive']
} else {
@ -46,26 +50,23 @@ const tab = computed(() =>
label: item.title,
})),
)
const exThemeVars = computed(() => {
return extraTheme(prefStore.isDark)
})
</script>
<template>
<n-tabs
v-model:value="tabStore.activatedIndex"
:closable="true"
:tabs-padding="0"
:tab-style="{
borderStyle: 'solid',
borderWidth: '1px',
borderLeftColor: themeVars.borderColor,
borderRightColor: themeVars.borderColor,
}"
:tabs-padding="3"
:theme-overrides="{
tabFontWeightActive: 800,
tabGapSmallCard: 0,
tabGapMediumCard: 0,
tabGapLargeCard: 0,
tabColor: '#0000',
// tabBorderColor: themeVars.borderColor,
tabBorderColor: '#0000',
tabTextColorCard: themeVars.closeIconColor,
}"
@ -73,14 +74,7 @@ const tab = computed(() =>
type="card"
@close="onCloseTab"
@update:value="(tabIndex) => tabStore.switchTab(tabIndex)">
<n-tab
v-for="(t, i) in tab"
:key="i"
:closable="true"
:name="i"
:style="tabStore.activatedIndex === i ? activeTabStyle : undefined"
:class="tabClass(i)"
@dblclick.stop="() => {}">
<n-tab v-for="(t, i) in tab" :key="i" :class="tabClass(i)" :closable="true" :name="i" @dblclick.stop="() => {}">
<n-space :size="5" :wrap-item="false" align="center" inline justify="center">
<n-icon size="18">
<server stroke-width="4" />
@ -95,18 +89,23 @@ const tab = computed(() =>
.value-tab {
--wails-draggable: none;
position: relative;
border: 1px solid v-bind('exThemeVars.splitColor') !important;
}
.value-tab-active {
background-color: v-bind('themeVars.bodyColor') !important;
border-bottom-color: v-bind('themeVars.bodyColor') !important;
background-color: v-bind('themeVars.tabColor') !important;
border-bottom-color: v-bind('themeVars.tabColor') !important;
&_mark {
border-top: 3px solid v-bind('tabMarkColor') !important;
}
}
.value-tab-inactive {
border-color: #0000 !important;
&:hover {
background-color: v-bind('themeVars.borderColor') !important;
background-color: v-bind('exThemeVars.splitColor') !important;
}
}

View File

@ -1,7 +1,7 @@
<script setup>
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { computed, defineExpose, onMounted, onUnmounted, ref, watch } from 'vue'
import 'xterm/css/xterm.css'
import { EventsEmit, EventsOff, EventsOn } from 'wailsjs/runtime/runtime.js'
import { get, isEmpty, set, size } from 'lodash'
@ -54,7 +54,6 @@ const newTerm = () => {
return { term, fitAddon }
}
let intervalID
onMounted(() => {
const { term, fitAddon } = newTerm()
termInst = term
@ -69,16 +68,9 @@ onMounted(() => {
EventsOn(`cmd:output:${props.name}`, receiveTermOutput)
fitAddon.fit()
term.focus()
intervalID = setInterval(() => {
if (props.activated) {
resizeTerm()
}
}, 1000)
})
onUnmounted(() => {
clearInterval(intervalID)
// window.removeEventListener('resize', resizeTerm)
EventsOff(`cmd:output:${props.name}`)
termInst.dispose()
@ -92,6 +84,10 @@ const resizeTerm = () => {
}
}
defineExpose({
resizeTerm,
})
watch(
() => prefStore.general.fontSize,
(fontSize) => {
@ -125,7 +121,6 @@ const onTermData = (data) => {
case 13: // enter
// try to process local command first
console.log('enter con', getCurrentInput())
switch (getCurrentInput()) {
case 'clear':
case 'clr':
@ -366,7 +361,7 @@ const receiveTermOutput = (data) => {
<div ref="termRef" class="xterm" />
</template>
<style scoped lang="scss">
<style lang="scss" scoped>
.xterm {
width: 100%;
min-height: 100%;

View File

@ -1,36 +1,72 @@
<script setup>
import { get, isEmpty, map, mapValues, pickBy, split, sum, toArray, toNumber } from 'lodash'
import { computed, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import IconButton from '@/components/common/IconButton.vue'
import Filter from '@/components/icons/Filter.vue'
import Refresh from '@/components/icons/Refresh.vue'
import useConnectionStore from 'stores/connections.js'
const props = defineProps({
server: String,
info: Object,
autoRefresh: false,
loading: false,
autoLoading: false,
})
const emit = defineEmits(['update:autoRefresh', 'refresh'])
const connectionStore = useConnectionStore()
const serverInfo = ref({})
const autoRefresh = ref(false)
const loading = ref(false) // loading status for refresh
const autoLoading = ref(false) // loading status for auto refresh
/**
* refresh server status info
* @param {boolean} [force] force refresh will show loading indicator
* @returns {Promise<void>}
*/
const refreshInfo = async (force) => {
if (force) {
loading.value = true
} else {
autoLoading.value = true
}
if (!isEmpty(props.server) && connectionStore.isConnected(props.server)) {
try {
serverInfo.value = await connectionStore.getServerInfo(props.server)
} finally {
loading.value = false
autoLoading.value = false
}
}
}
let intervalID
onMounted(() => {
refreshInfo()
intervalID = setInterval(() => {
if (autoRefresh.value === true) {
refreshInfo()
}
}, 5000)
})
onUnmounted(() => {
clearInterval(intervalID)
})
const scrollRef = ref(null)
const redisVersion = computed(() => {
return get(props.info, 'Server.redis_version', '')
return get(serverInfo.value, 'Server.redis_version', '')
})
const redisMode = computed(() => {
return get(props.info, 'Server.redis_mode', '')
return get(serverInfo.value, 'Server.redis_mode', '')
})
const role = computed(() => {
return get(props.info, 'Replication.role', '')
return get(serverInfo.value, 'Replication.role', '')
})
const timeUnit = ['common.unit_minute', 'common.unit_hour', 'common.unit_day']
const uptime = computed(() => {
let seconds = get(props.info, 'Server.uptime_in_seconds', 0)
let seconds = get(serverInfo.value, 'Server.uptime_in_seconds', 0)
seconds /= 60
if (seconds < 60) {
// minutes
@ -46,7 +82,7 @@ const uptime = computed(() => {
const units = ['B', 'KB', 'MB', 'GB', 'TB']
const usedMemory = computed(() => {
let size = get(props.info, 'Memory.used_memory', 0)
let size = get(serverInfo.value, 'Memory.used_memory', 0)
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
@ -59,7 +95,7 @@ const usedMemory = computed(() => {
const totalKeys = computed(() => {
const regex = /^db\d+$/
const result = pickBy(props.info['Keyspace'], (value, key) => {
const result = pickBy(serverInfo.value['Keyspace'], (value, key) => {
return regex.test(key)
})
const nums = mapValues(result, (v) => {
@ -75,8 +111,8 @@ const infoFilter = ref('')
<template>
<n-scrollbar ref="scrollRef">
<n-back-top :listen-to="scrollRef" />
<n-space vertical :wrap-item="false" :size="5" style="padding: 5px">
<n-card>
<n-space :size="5" :wrap-item="false" style="padding: 5px" vertical>
<n-card embedded>
<template #header>
<n-space :wrap-item="false" align="center" inline size="small">
{{ props.server }}
@ -103,19 +139,16 @@ const infoFilter = ref('')
<template #header-extra>
<n-space align="center" inline>
{{ $t('status.auto_refresh') }}
<n-switch
:loading="props.autoLoading"
:value="props.autoRefresh"
@update:value="(v) => emit('update:autoRefresh', v)" />
<n-switch v-model:value="autoRefresh" :loading="autoLoading" />
<n-tooltip>
{{ $t('status.refresh') }}
<template #trigger>
<n-button
:loading="props.autoLoading"
:loading="autoLoading"
circle
size="small"
tertiary
@click="emit('refresh')">
@click="refreshInfo(true)">
<template #icon>
<n-icon :component="Refresh" />
</template>
@ -124,7 +157,7 @@ const infoFilter = ref('')
</n-tooltip>
</n-space>
</template>
<n-spin :show="props.loading">
<n-spin :show="loading">
<n-grid style="min-width: 500px" x-gap="5">
<n-gi :span="6">
<n-statistic :label="$t('status.uptime')" :value="uptime[0]">
@ -134,7 +167,7 @@ const infoFilter = ref('')
<n-gi :span="6">
<n-statistic
:label="$t('status.connected_clients')"
:value="get(props.info, 'Clients.connected_clients', 0)" />
:value="get(serverInfo, 'Clients.connected_clients', 0)" />
</n-gi>
<n-gi :span="6">
<n-statistic :value="totalKeys">
@ -151,7 +184,7 @@ const infoFilter = ref('')
</n-grid>
</n-spin>
</n-card>
<n-card :title="$t('status.all_info')">
<n-card :title="$t('status.all_info')" embedded>
<template #header-extra>
<n-input v-model:value="infoFilter" clearable placeholder="">
<template #prefix>
@ -159,9 +192,9 @@ const infoFilter = ref('')
</template>
</n-input>
</template>
<n-spin :show="props.loading">
<n-spin :show="loading">
<n-tabs default-value="CPU" placement="left" type="line">
<n-tab-pane v-for="(v, k) in props.info" :key="k" :disabled="isEmpty(v)" :name="k">
<n-tab-pane v-for="(v, k) in serverInfo" :key="k" :disabled="isEmpty(v)" :name="k">
<n-data-table
:columns="[
{

View File

@ -8,8 +8,10 @@ import ContentValueSet from '@/components/content_value/ContentValueSet.vue'
import ContentValueZset from '@/components/content_value/ContentValueZSet.vue'
import ContentValueStream from '@/components/content_value/ContentValueStream.vue'
import { useThemeVars } from 'naive-ui'
import useConnectionStore from 'stores/connections.js'
const themeVars = useThemeVars()
const connectionStore = useConnectionStore()
const props = defineProps({
blank: Boolean,
@ -33,8 +35,6 @@ const props = defineProps({
},
})
const emit = defineEmits(['reload'])
const valueComponents = {
[redisTypes.STRING]: ContentValueString,
[redisTypes.HASH]: ContentValueHash,
@ -43,30 +43,34 @@ const valueComponents = {
[redisTypes.ZSET]: ContentValueZset,
[redisTypes.STREAM]: ContentValueStream,
}
/**
* reload current selection key
* @returns {Promise<null>}
*/
const onReloadKey = async () => {
await connectionStore.loadKeyValue(props.name, props.db, props.key, props.viewAs)
}
</script>
<template>
<n-empty v-if="props.blank" :description="$t('interface.nonexist_tab_content')" class="empty-content">
<template #extra>
<n-button :focusable="false" @click="emit('reload')">{{ $t('interface.reload') }}</n-button>
<n-button :focusable="false" @click="onReloadKey">{{ $t('interface.reload') }}</n-button>
</template>
</n-empty>
<component
class="content-value-wrapper"
:is="valueComponents[props.type]"
v-else
:db="props.db"
:key-code="props.keyCode"
:key-path="props.keyPath"
:name="props.name"
:size="props.size"
:ttl="props.ttl"
:value="props.value"
:view-as="props.viewAs" />
<keep-alive v-else>
<component
:is="valueComponents[props.type]"
:db="props.db"
:key-code="props.keyCode"
:key-path="props.keyPath"
:name="props.name"
:size="props.size"
:ttl="props.ttl"
:value="props.value"
:view-as="props.viewAs" />
</keep-alive>
</template>
<style scoped lang="scss">
.content-value-wrapper {
background-color: v-bind('themeVars.bodyColor');
}
</style>
<style lang="scss" scoped></style>

View File

@ -8,11 +8,13 @@ import Close from '@/components/icons/Close.vue'
import useConnectionStore from 'stores/connections.js'
import FileOpenInput from '@/components/common/FileOpenInput.vue'
import { KeyViewType } from '@/consts/key_view_type.js'
import { useThemeVars } from 'naive-ui'
/**
* Dialog for new or edit connection
*/
const themeVars = useThemeVars()
const dialogStore = useDialog()
const connectionStore = useConnectionStore()
const i18n = useI18n()
@ -396,10 +398,13 @@ const onClose = () => {
<div
v-for="color in predefineColors"
:key="color"
:class="{
'color-preset-item_selected': generalForm.markColor === color,
:style="{
backgroundColor: color,
borderColor:
generalForm.markColor === color
? themeVars.textColorBase
: themeVars.borderColor,
}"
:style="{ backgroundColor: color }"
class="color-preset-item"
@click="generalForm.markColor = color">
<n-icon v-if="isEmpty(color)" :component="Close" size="24" />
@ -591,13 +596,9 @@ const onClose = () => {
width: 24px;
height: 24px;
margin-right: 2px;
border: white 3px solid;
border-width: 3px;
border-style: solid;
cursor: pointer;
border-radius: 50%;
&_selected,
&:hover {
border-color: #cdd0d6;
}
}
</style>

View File

@ -16,52 +16,52 @@ const props = defineProps({
<template>
<svg v-if="props.inverse" fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect
fill="currentColor"
stroke="currentColor"
:stroke-width="props.strokeWidth"
fill="currentColor"
height="8"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
width="8"
x="4"
y="34" />
<rect
fill="currentColor"
stroke="currentColor"
:stroke-width="props.strokeWidth"
fill="currentColor"
height="12"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
width="32"
x="8"
y="6" />
<path
stroke="currentColor"
:stroke-width="props.strokeWidth"
d="M24 34V18"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round" />
<path
stroke="currentColor"
:stroke-width="props.strokeWidth"
d="M8 34V26H40V34"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round" />
<rect
fill="currentColor"
stroke="currentColor"
:stroke-width="props.strokeWidth"
fill="currentColor"
height="8"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
width="8"
x="36"
y="34" />
<rect
fill="currentColor"
stroke="currentColor"
:stroke-width="props.strokeWidth"
fill="currentColor"
height="8"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
width="8"

View File

@ -16,31 +16,31 @@ const props = defineProps({
<template>
<svg v-if="props.inverse" fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path
stroke="currentColor"
:stroke-width="props.strokeWidth"
d="M44.0001 11C44.0001 11 44 36.0623 44 38C44 41.3137 35.0457 44 24 44C12.9543 44 4.00003 41.3137 4.00003 38C4.00003 36.1423 4 11 4 11"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round" />
<path
stroke="currentColor"
:stroke-width="props.strokeWidth"
d="M44 29C44 32.3137 35.0457 35 24 35C12.9543 35 4 32.3137 4 29"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round" />
<path
stroke="currentColor"
:stroke-width="props.strokeWidth"
d="M44 20C44 23.3137 35.0457 26 24 26C12.9543 26 4 23.3137 4 20"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round" />
<ellipse
stroke="currentColor"
:stroke-width="props.strokeWidth"
cx="24"
cy="10"
fill="currentColor"
rx="20"
ry="6"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>

View File

@ -14,93 +14,93 @@ const props = defineProps({
</script>
<template>
<svg v-if="props.inverse" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg v-if="props.inverse" fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect
x="6"
y="6"
width="36"
:stroke-width="props.strokeWidth"
fill="currentColor"
height="36"
rx="3"
fill="currentColor"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
stroke-linejoin="round"
width="36"
x="6"
y="6" />
<rect
x="13"
y="13"
width="8"
height="8"
fill="#FFF"
stroke="#FFF"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
fill="#FFF"
height="8"
stroke="#FFF"
stroke-linejoin="round"
width="8"
x="13"
y="13" />
<path
:stroke-width="props.strokeWidth"
d="M27 13L35 13"
stroke="#FFF"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M27 20L35 20"
stroke="#FFF"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M13 28L35 28"
stroke="#FFF"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M13 35H35"
stroke="#FFF"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg v-else fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect
x="6"
y="6"
width="36"
:stroke-width="props.strokeWidth"
fill="none"
height="36"
rx="3"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
stroke-linejoin="round"
width="36"
x="6"
y="6" />
<rect
x="13"
y="13"
width="8"
height="8"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
fill="none"
height="8"
stroke="currentColor"
stroke-linejoin="round"
width="8"
x="13"
y="13" />
<path
:stroke-width="props.strokeWidth"
d="M27 13L35 13"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M27 20L35 20"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M13 28L35 28"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M13 35H35"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>

View File

@ -8,38 +8,38 @@ const props = defineProps({
</script>
<template>
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path
:stroke-width="props.strokeWidth"
d="M9 42C11.2091 42 13 40.2091 13 38C13 35.7909 11.2091 34 9 34C6.79086 34 5 35.7909 5 38C5 40.2091 6.79086 42 9 42Z"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M9 14C11.2091 14 13 12.2092 13 10C13 7.79086 11.2091 6 9 6C6.79086 6 5 7.79086 5 10C5 12.2092 6.79086 14 9 14Z"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M9 28C11.2091 28 13 26.2092 13 24C13 21.7908 11.2091 20 9 20C6.79086 20 5 21.7908 5 24C5 26.2092 6.79086 28 9 28Z"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M21 24H43"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M21 38H43"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M21 10H43"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>

View File

@ -12,24 +12,24 @@ const props = defineProps({
</script>
<template>
<svg v-if="props.inverse" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg v-if="props.inverse" fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect
x="13"
y="10"
width="28"
height="34"
fill="currentColor"
height="34"
stroke="currentColor"
stroke-linejoin="round"
stroke-width="3"
stroke-linejoin="round" />
width="28"
x="13"
y="10" />
<path
d="M35 10V4H8C7.44772 4 7 4.44772 7 5V38H13"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round" />
<path d="M21 22H33" stroke="#FFF" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
<path d="M21 30H33" stroke="#FFF" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
stroke-linejoin="round"
stroke-width="3" />
<path d="M21 22H33" stroke="#FFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" />
<path d="M21 30H33" stroke="#FFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" />
</svg>
<svg v-else fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect

View File

@ -16,10 +16,10 @@ const props = defineProps({
<template>
<svg v-if="props.inverse" fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path
fill="currentColor"
stroke="currentColor"
:stroke-width="props.strokeWidth"
d="M41 4H7C5.34315 4 4 5.34315 4 7V41C4 42.6569 5.34315 44 7 44H41C42.6569 44 44 42.6569 44 41V7C44 5.34315 42.6569 4 41 4Z"
fill="currentColor"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round" />
<path :stroke-width="props.strokeWidth" d="M4 32H44" stroke="#FFF" stroke-linecap="round" />
@ -36,15 +36,15 @@ const props = defineProps({
stroke-linecap="round"
stroke-linejoin="round" />
<path
stroke="currentColor"
:stroke-width="props.strokeWidth"
d="M44 37V27"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round" />
<path
stroke="currentColor"
:stroke-width="props.strokeWidth"
d="M4 37V27"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>

View File

@ -12,15 +12,15 @@ const props = defineProps({
</script>
<template>
<svg v-if="props.inverse" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg v-if="props.inverse" fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path
d="M42 8H6C4.89543 8 4 8.89543 4 10V38C4 39.1046 4.89543 40 6 40H42C43.1046 40 44 39.1046 44 38V10C44 8.89543 43.1046 8 42 8Z"
fill="currentColor"
stroke="currentColor"
stroke-width="3" />
<path d="M24 17V31" stroke="#FFF" :stroke-width="props.strokeWidth" stroke-linecap="round" />
<path d="M32 24V31" stroke="#FFF" :stroke-width="props.strokeWidth" stroke-linecap="round" />
<path d="M16 22V31" stroke="#FFF" :stroke-width="props.strokeWidth" stroke-linecap="round" />
<path :stroke-width="props.strokeWidth" d="M24 17V31" stroke="#FFF" stroke-linecap="round" />
<path :stroke-width="props.strokeWidth" d="M32 24V31" stroke="#FFF" stroke-linecap="round" />
<path :stroke-width="props.strokeWidth" d="M16 22V31" stroke="#FFF" stroke-linecap="round" />
</svg>
<svg v-else fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect

View File

@ -14,51 +14,51 @@ const props = defineProps({
</script>
<template>
<svg v-if="props.inverse" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg v-if="props.inverse" fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect
x="4"
y="8"
width="40"
:stroke-width="props.strokeWidth"
fill="currentColor"
height="32"
rx="2"
fill="currentColor"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
stroke-linejoin="round"
width="40"
x="4"
y="8" />
<path
:stroke-width="props.strokeWidth"
d="M12 18L19 24L12 30"
stroke="#FFF"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M23 32H36"
stroke="#FFF"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
<svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg v-else fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<rect
x="4"
y="8"
width="40"
:stroke-width="props.strokeWidth"
fill="none"
height="32"
rx="2"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
stroke-linejoin="round"
width="40"
x="4"
y="8" />
<path
:stroke-width="props.strokeWidth"
d="M12 18L19 24L12 30"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M23 32H36"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>

View File

@ -8,47 +8,47 @@ const props = defineProps({
</script>
<template>
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path
:stroke-width="props.strokeWidth"
d="M38 20H18V28H38V20Z"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M32 6H18V14H32V6Z"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M44 34H18V42H44V34Z"
fill="none"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M17 10H5"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M17 24H5"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M17 38H5"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
<path
:stroke-width="props.strokeWidth"
d="M5 44V4"
stroke="currentColor"
:stroke-width="props.strokeWidth"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>

View File

@ -12,24 +12,12 @@ import useConnectionStore from 'stores/connections.js'
import { types } from '@/consts/support_redis_type.js'
import Search from '@/components/icons/Search.vue'
import Unlink from '@/components/icons/Unlink.vue'
import Status from '@/components/icons/Status.vue'
import SwitchButton from '@/components/common/SwitchButton.vue'
import ListView from '@/components/icons/ListView.vue'
import TreeView from '@/components/icons/TreeView.vue'
const themeVars = useThemeVars()
const dialogStore = useDialogStore()
const tabStore = useTabStore()
const currentName = computed(() => get(tabStore.currentTab, 'name', ''))
const browserTreeRef = ref(null)
/**
*
* @type {ComputedRef<{server: string, db: number, key: string}>}
*/
const currentSelect = computed(() => {
const { server, db, key } = tabStore.currentTab || {}
return { server, db, key }
})
const onInfo = () => {
browserTreeRef.value?.handleSelectContextMenu('server_info')
@ -99,7 +87,7 @@ const filterTypeOptions = computed(() => {
<!-- stroke-width="4"-->
<!-- unselect-stroke-width="3"-->
<!-- @update:value="onSwitchView" />-->
<icon-button :icon="Status" size="20" stroke-width="4" t-tooltip="interface.status" @click="onInfo" />
<!-- <icon-button :icon="Status" size="20" stroke-width="4" t-tooltip="interface.status" @click="onInfo" />-->
<icon-button :icon="Refresh" size="20" stroke-width="4" t-tooltip="interface.reload" @click="onRefresh" />
<div class="flex-item-expand" />
<icon-button

View File

@ -1,7 +1,7 @@
<script setup>
import { computed, h, nextTick, onMounted, reactive, ref } from 'vue'
import { ConnectionType } from '@/consts/connection_type.js'
import { NIcon, NSpace, NTag } from 'naive-ui'
import { NIcon, NSpace, NTag, useThemeVars } from 'naive-ui'
import Key from '@/components/icons/Key.vue'
import Binary from '@/components/icons/Binary.vue'
import Database from '@/components/icons/Database.vue'
@ -31,6 +31,7 @@ const props = defineProps({
keyView: String,
})
const themeVars = useThemeVars()
const i18n = useI18n()
const loading = ref(false)
const loadingConnections = ref(false)
@ -532,6 +533,7 @@ const getDatabaseMenu = (opened, loading, end) => {
icon: LoadList,
disabled: end === true,
loading: loading === true,
color: loading === true ? themeVars.value.primaryColor : '',
onClick: () => handleSelectContextMenu('db_loadmore'),
}),
h(IconButton, {
@ -539,6 +541,7 @@ const getDatabaseMenu = (opened, loading, end) => {
icon: LoadAll,
disabled: end === true,
loading: loading === true,
color: loading === true ? themeVars.value.primaryColor : '',
onClick: () => handleSelectContextMenu('db_loadall'),
}),
h(IconButton, {

View File

@ -530,10 +530,10 @@ const onCancelOpen = () => {
<!-- context menu -->
<n-dropdown
:keyboard="true"
:options="contextMenuParam.options"
:render-label="renderContextLabel"
:show="contextMenuParam.show"
:keyboard="true"
:x="contextMenuParam.x"
:y="contextMenuParam.y"
placement="bottom-start"

View File

@ -12,6 +12,7 @@ import { BrowserOpenURL } from 'wailsjs/runtime/runtime.js'
import useConnectionStore from 'stores/connections.js'
import usePreferencesStore from 'stores/preferences.js'
import Record from '@/components/icons/Record.vue'
import { extraTheme } from '@/utils/extra_theme.js'
const themeVars = useThemeVars()
@ -88,14 +89,14 @@ const renderContextLabel = (option) => {
}
const dialogStore = useDialogStore()
const preferencesStore = usePreferencesStore()
const prefStore = usePreferencesStore()
const onSelectPreferenceMenu = (key) => {
switch (key) {
case 'preferences':
dialogStore.openPreferencesDialog()
break
case 'update':
preferencesStore.checkForUpdate(true)
prefStore.checkForUpdate(true)
break
case 'about':
dialogStore.openAboutDialog()
@ -106,6 +107,10 @@ const onSelectPreferenceMenu = (key) => {
const openGithub = () => {
BrowserOpenURL('https://github.com/tiny-craft/tiny-rdm')
}
const exThemeVars = computed(() => {
return extraTheme(prefStore.isDark)
})
</script>
<template>
@ -139,7 +144,9 @@ const openGithub = () => {
<style lang="scss">
#app-nav-menu {
//height: 100vh;
//border-right: v-bind('themeVars.borderColor') solid 1px;
border-right: v-bind('exThemeVars.splitColor') solid 1px;
background-color: v-bind('exThemeVars.sidebarColor');
box-sizing: border-box;
.nav-menu-item {
align-items: center;

View File

@ -108,7 +108,7 @@
"log": "Log"
},
"dialogue": {
"close_confirm": "Confirm close this tab and connection",
"close_confirm": "Confirm close this tab and connection ({name})",
"edit_close_confirm": "Please close the relevant connections before editing. Do you want to continue?",
"opening_connection": "Opening Connection...",
"interrupt_connection": "Cancel",

View File

@ -108,7 +108,7 @@
"log": "日志"
},
"dialogue": {
"close_confirm": "是否关闭当前连接",
"close_confirm": "是否关闭此连接({name}",
"edit_close_confirm": "编辑前需要关闭相关连接,是否继续",
"opening_connection": "正在打开连接...",
"interrupt_connection": "中断连接",

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { lang } from '@/langs/index.js'
import { clone, find, get, isEmpty, map, pick, set, split } from 'lodash'
import { cloneDeep, find, get, isEmpty, map, pick, set, split } from 'lodash'
import {
CheckForUpdate,
GetFontList,
@ -178,7 +178,7 @@ const usePreferencesStore = defineStore('preferences', {
async loadPreferences() {
const { success, data } = await GetPreferences()
if (success) {
this.lastPref = clone(data)
this.lastPref = cloneDeep(data)
this._applyPreferences(data)
i18nGlobal.locale.value = this.currentLanguage
}

View File

@ -234,7 +234,7 @@ const useTabStore = defineStore('tab', {
/**
* set selected keys of current display browser tree
* @param {string} server
* @param {string|string[]} keys
* @param {string|string[]} [keys]
*/
setSelectedKeys(server, keys = null) {
let tab = find(this.tabList, { name: server })

View File

@ -0,0 +1,35 @@
/**
* @typedef ExtraTheme
* @property {string} titleColor
* @property {string} sidebarColor
* @property {string} splitColor
*/
/**
*
* @type ExtraTheme
*/
export const extraLightTheme = {
titleColor: '#F0F0F4',
sidebarColor: '#F6F6F6',
splitColor: '#E0E0E6',
}
/**
*
* @type ExtraTheme
*/
export const extraDarkTheme = {
titleColor: '#202020',
sidebarColor: '#202124',
splitColor: '#323138',
}
/**
*
* @param {boolean} dark
* @return ExtraTheme
*/
export const extraTheme = (dark) => {
return dark ? extraDarkTheme : extraLightTheme
}

View File

@ -5,6 +5,7 @@ import { padStart, size, startsWith } from 'lodash'
* @property {number} r
* @property {number} g
* @property {number} b
* @property {number} [a]
*/
/**
@ -43,6 +44,28 @@ export function hexGammaCorrection(rgb, gamma) {
}
}
/**
* mix two colors
* @param rgba1
* @param rgba2
* @param weight
* @return {{a: number, r: number, b: number, g: number}}
*/
export function mixColors(rgba1, rgba2, weight = 0.5) {
if (rgba1.a === undefined) {
rgba1.a = 255
}
if (rgba2.a === undefined) {
rgba2.a = 255
}
return {
r: Math.floor(rgba1.r * (1 - weight) + rgba2.r * weight),
g: Math.floor(rgba1.g * (1 - weight) + rgba2.g * weight),
b: Math.floor(rgba1.b * (1 - weight) + rgba2.b * weight),
a: Math.floor(rgba1.a * (1 - weight) + rgba2.a * weight),
}
}
/**
* RGB object to hex color string
* @param {RGB} rgb

View File

@ -1,3 +1,5 @@
import { merge } from 'lodash'
/**
*
* @type import('naive-ui').GlobalThemeOverrides
@ -12,6 +14,7 @@ export const themeOverrides = {
borderRadiusSmall: '3px',
lineHeight: 1.5,
scrollbarWidth: '8px',
tabColor: '#FFFFFF',
},
Tag: {
// borderRadius: '3px'
@ -39,4 +42,27 @@ export const themeOverrides = {
labelTextColor: 'rgb(113,120,128)',
labelFontWeight: '450',
},
Radio: {
buttonColorActive: '#D13B37',
buttonTextColorActive: '#FFF',
},
}
/**
*
* @type import('naive-ui').GlobalThemeOverrides
*/
const _darkThemeOverrides = {
common: {
bodyColor: '#1A1A1A',
tabColor: '#18181C',
},
Tree: {
nodeTextColor: '#ceced0',
},
Card: {
colorEmbedded: '#18181C',
},
}
export const darkThemeOverrides = merge({}, themeOverrides, _darkThemeOverrides)

View File

@ -53,7 +53,7 @@ func main() {
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 0},
BackgroundColour: options.NewRGBA(27, 38, 54, 0),
OnStartup: func(ctx context.Context) {
sysSvc.Start(ctx)
connSvc.Start(ctx)
@ -80,7 +80,7 @@ func main() {
Icon: icon,
},
WebviewIsTransparent: false,
WindowIsTranslucent: false,
WindowIsTranslucent: true,
},
Windows: &windows.Options{
WebviewIsTransparent: true,