refactor: wrapping element with drag and resize behavior as a standalone component

This commit is contained in:
tiny-craft 2023-10-27 18:26:35 +08:00
parent b6eebda177
commit 124627d724
5 changed files with 152 additions and 74 deletions

View File

@ -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,33 +37,10 @@ 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,
(nav) => { (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;

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

@ -17,12 +17,12 @@ 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(() => {
@ -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,

View File

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

View File

@ -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": "中断连接",