refactor: wrapping element with drag and resize behavior as a standalone component
This commit is contained in:
parent
b6eebda177
commit
124627d724
|
@ -16,6 +16,7 @@ import ToolbarControlWidget from '@/components/common/ToolbarControlWidget.vue'
|
||||||
import { EventsOn, WindowIsFullscreen, WindowIsMaximised, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js'
|
import { EventsOn, WindowIsFullscreen, WindowIsMaximised, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js'
|
||||||
import { isMacOS } from '@/utils/platform.js'
|
import { isMacOS } from '@/utils/platform.js'
|
||||||
import iconUrl from '@/assets/images/icon.png'
|
import iconUrl from '@/assets/images/icon.png'
|
||||||
|
import ResizeableWrapper from '@/components/common/ResizeableWrapper.vue'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
|
@ -25,8 +26,6 @@ const props = defineProps({
|
||||||
|
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
navMenuWidth: 60,
|
navMenuWidth: 60,
|
||||||
hoverResize: false,
|
|
||||||
resizing: false,
|
|
||||||
toolbarHeight: 45,
|
toolbarHeight: 45,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -38,32 +37,9 @@ const logPaneRef = ref(null)
|
||||||
// provide('preferences', preferences)
|
// provide('preferences', preferences)
|
||||||
|
|
||||||
const saveSidebarWidth = debounce(prefStore.savePreferences, 1000, { trailing: true })
|
const saveSidebarWidth = debounce(prefStore.savePreferences, 1000, { trailing: true })
|
||||||
const handleResize = (evt) => {
|
const handleResize = () => {
|
||||||
if (data.resizing) {
|
|
||||||
prefStore.setAsideWidth(Math.max(evt.clientX - data.navMenuWidth, 300))
|
|
||||||
saveSidebarWidth()
|
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(
|
watch(
|
||||||
() => tabStore.nav,
|
() => tabStore.nav,
|
||||||
|
@ -166,15 +142,6 @@ onMounted(async () => {
|
||||||
</transition>
|
</transition>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</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 -->
|
<!-- browser tabs -->
|
||||||
<div v-show="tabStore.nav === 'browser'" class="app-toolbar-tab flex-item-expand">
|
<div v-show="tabStore.nav === 'browser'" class="app-toolbar-tab flex-item-expand">
|
||||||
<content-value-tab />
|
<content-value-tab />
|
||||||
|
@ -196,23 +163,19 @@ onMounted(async () => {
|
||||||
style="--wails-draggable: none">
|
style="--wails-draggable: none">
|
||||||
<nav-menu v-model:value="tabStore.nav" :width="data.navMenuWidth" />
|
<nav-menu v-model:value="tabStore.nav" :width="data.navMenuWidth" />
|
||||||
<!-- browser page -->
|
<!-- browser page -->
|
||||||
<div v-show="tabStore.nav === 'browser'" :class="{ dragging }" class="flex-box-h flex-item-expand">
|
<div v-show="tabStore.nav === 'browser'" class="flex-box-h flex-item-expand">
|
||||||
<div :style="{ width: asideWidthVal }" class="app-side flex-box-h flex-item">
|
<resizeable-wrapper
|
||||||
|
v-model:size="prefStore.behavior.asideWidth"
|
||||||
|
:min-size="300"
|
||||||
|
:offset="data.navMenuWidth"
|
||||||
|
class="flex-item"
|
||||||
|
@update:size="handleResize">
|
||||||
<browser-pane
|
<browser-pane
|
||||||
v-for="t in tabStore.tabs"
|
v-for="t in tabStore.tabs"
|
||||||
v-show="get(tabStore.currentTab, 'name') === t.name"
|
v-show="get(tabStore.currentTab, 'name') === t.name"
|
||||||
:key="t.name"
|
:key="t.name"
|
||||||
class="flex-item-expand" />
|
class="app-side flex-item-expand" />
|
||||||
<div
|
</resizeable-wrapper>
|
||||||
: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
|
<content-pane
|
||||||
v-for="t in tabStore.tabs"
|
v-for="t in tabStore.tabs"
|
||||||
v-show="get(tabStore.currentTab, 'name') === t.name"
|
v-show="get(tabStore.currentTab, 'name') === t.name"
|
||||||
|
@ -222,19 +185,15 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- server list page -->
|
<!-- server list page -->
|
||||||
<div v-show="tabStore.nav === 'server'" :class="{ dragging }" class="flex-box-h flex-item-expand">
|
<div v-show="tabStore.nav === 'server'" class="flex-box-h flex-item-expand">
|
||||||
<div :style="{ width: asideWidthVal }" class="app-side flex-box-h flex-item">
|
<resizeable-wrapper
|
||||||
<connection-pane class="flex-item-expand" />
|
v-model:size="prefStore.behavior.asideWidth"
|
||||||
<div
|
:min-size="300"
|
||||||
:class="{
|
:offset="data.navMenuWidth"
|
||||||
'resize-divider-hover': data.hoverResize,
|
class="flex-item"
|
||||||
'resize-divider-drag': data.resizing,
|
@update:size="handleResize">
|
||||||
}"
|
<connection-pane class="app-side flex-item-expand" />
|
||||||
class="resize-divider"
|
</resizeable-wrapper>
|
||||||
@mousedown="startResize"
|
|
||||||
@mouseout="data.hoverResize = false"
|
|
||||||
@mouseover="data.hoverResize = true" />
|
|
||||||
</div>
|
|
||||||
<content-server-pane class="flex-item-expand" />
|
<content-server-pane class="flex-item-expand" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -282,6 +241,7 @@ onMounted(async () => {
|
||||||
//overflow: hidden;
|
//overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: v-bind('themeVars.tabColor');
|
background-color: v-bind('themeVars.tabColor');
|
||||||
|
border-right: 1px solid v-bind('themeVars.borderColor');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,10 +265,6 @@ onMounted(async () => {
|
||||||
border-right-color: v-bind('themeVars.primaryColor');
|
border-right-color: v-bind('themeVars.primaryColor');
|
||||||
}
|
}
|
||||||
|
|
||||||
.dragging {
|
|
||||||
cursor: col-resize !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-from,
|
.fade-enter-from,
|
||||||
.fade-leave-to {
|
.fade-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
|
@ -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>
|
|
@ -17,13 +17,13 @@ const tabStore = useTabStore()
|
||||||
const connectionStore = useConnectionStore()
|
const connectionStore = useConnectionStore()
|
||||||
|
|
||||||
const onCloseTab = (tabIndex) => {
|
const onCloseTab = (tabIndex) => {
|
||||||
$dialog.warning(i18n.t('dialogue.close_confirm'), () => {
|
|
||||||
const tab = get(tabStore.tabs, tabIndex)
|
const tab = get(tabStore.tabs, tabIndex)
|
||||||
if (tab != null) {
|
if (tab != null) {
|
||||||
|
$dialog.warning(i18n.t('dialogue.close_confirm', { name: tab.name }), () => {
|
||||||
connectionStore.closeConnection(tab.name)
|
connectionStore.closeConnection(tab.name)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tabMarkColor = computed(() => {
|
const tabMarkColor = computed(() => {
|
||||||
const { name } = tabStore?.currentTab || {}
|
const { name } = tabStore?.currentTab || {}
|
||||||
|
@ -53,7 +53,7 @@ const tab = computed(() =>
|
||||||
<n-tabs
|
<n-tabs
|
||||||
v-model:value="tabStore.activatedIndex"
|
v-model:value="tabStore.activatedIndex"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:tabs-padding="0"
|
:tabs-padding="3"
|
||||||
:theme-overrides="{
|
:theme-overrides="{
|
||||||
tabFontWeightActive: 800,
|
tabFontWeightActive: 800,
|
||||||
tabGapSmallCard: 0,
|
tabGapSmallCard: 0,
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
"log": "Log"
|
"log": "Log"
|
||||||
},
|
},
|
||||||
"dialogue": {
|
"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?",
|
"edit_close_confirm": "Please close the relevant connections before editing. Do you want to continue?",
|
||||||
"opening_connection": "Opening Connection...",
|
"opening_connection": "Opening Connection...",
|
||||||
"interrupt_connection": "Cancel",
|
"interrupt_connection": "Cancel",
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
"log": "日志"
|
"log": "日志"
|
||||||
},
|
},
|
||||||
"dialogue": {
|
"dialogue": {
|
||||||
"close_confirm": "是否关闭当前连接",
|
"close_confirm": "是否关闭此连接({name})",
|
||||||
"edit_close_confirm": "编辑前需要关闭相关连接,是否继续",
|
"edit_close_confirm": "编辑前需要关闭相关连接,是否继续",
|
||||||
"opening_connection": "正在打开连接...",
|
"opening_connection": "正在打开连接...",
|
||||||
"interrupt_connection": "中断连接",
|
"interrupt_connection": "中断连接",
|
||||||
|
|
Loading…
Reference in New Issue