mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
feat(records): make chat record drawer resizable
This commit is contained in:
1 parent
9009edfeb9
commit
997598259c
7 files changed
+123
-6
No files matched your search
@@ -3,7 +3,7 @@
|
||||
* Chat record viewer drawer.
|
||||
* Keeps the quick-view shell while ChatRecordWorkspace owns shared orchestration.
|
||||
*/
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onBeforeUnmount, ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import ChatRecordWorkspace from './ChatRecordWorkspace.vue'
|
||||
import { useLayoutStore } from '@/stores/layout'
|
||||
@@ -13,9 +13,59 @@ const layoutStore = useLayoutStore()
|
||||
|
||||
// 平台检测
|
||||
const isWindows = ref(false)
|
||||
const MIN_DRAWER_WIDTH = 480
|
||||
const VIEWPORT_MARGIN = 32
|
||||
let dragStartX = 0
|
||||
let dragStartWidth = 0
|
||||
|
||||
const maxDrawerWidth = ref(1280 - VIEWPORT_MARGIN)
|
||||
|
||||
function updateViewportLimit() {
|
||||
maxDrawerWidth.value = Math.max(MIN_DRAWER_WIDTH, window.innerWidth - VIEWPORT_MARGIN)
|
||||
}
|
||||
|
||||
function clampDrawerWidth(width: number) {
|
||||
return Math.min(maxDrawerWidth.value, Math.max(MIN_DRAWER_WIDTH, width))
|
||||
}
|
||||
|
||||
function stopResize() {
|
||||
window.removeEventListener('pointermove', handleResize)
|
||||
window.removeEventListener('pointerup', stopResize)
|
||||
document.body.style.cursor = ''
|
||||
document.body.style.userSelect = ''
|
||||
}
|
||||
|
||||
function handleResize(event: PointerEvent) {
|
||||
layoutStore.chatRecordDrawerWidth = clampDrawerWidth(dragStartWidth + dragStartX - event.clientX)
|
||||
}
|
||||
|
||||
function startResize(event: PointerEvent) {
|
||||
if (event.button !== 0) return
|
||||
dragStartX = event.clientX
|
||||
dragStartWidth = layoutStore.chatRecordDrawerWidth
|
||||
window.addEventListener('pointermove', handleResize)
|
||||
window.addEventListener('pointerup', stopResize)
|
||||
document.body.style.cursor = 'col-resize'
|
||||
document.body.style.userSelect = 'none'
|
||||
}
|
||||
|
||||
function resizeWithKeyboard(event: KeyboardEvent) {
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
|
||||
event.preventDefault()
|
||||
const delta = event.key === 'ArrowLeft' ? 24 : -24
|
||||
layoutStore.chatRecordDrawerWidth = clampDrawerWidth(layoutStore.chatRecordDrawerWidth + delta)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
isWindows.value = navigator.platform.toLowerCase().includes('win')
|
||||
updateViewportLimit()
|
||||
layoutStore.chatRecordDrawerWidth = clampDrawerWidth(layoutStore.chatRecordDrawerWidth)
|
||||
window.addEventListener('resize', updateViewportLimit)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopResize()
|
||||
window.removeEventListener('resize', updateViewportLimit)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -30,9 +80,26 @@ onMounted(() => {
|
||||
<template #content>
|
||||
<div
|
||||
data-vaul-no-drag
|
||||
class="chat-record-drawer-content flex h-full w-[750px] flex-col bg-white dark:bg-page-dark"
|
||||
class="chat-record-drawer-content relative flex h-full flex-col bg-white dark:bg-page-dark"
|
||||
:style="{ width: `${layoutStore.chatRecordDrawerWidth}px`, maxWidth: `calc(100vw - ${VIEWPORT_MARGIN}px)` }"
|
||||
style="-webkit-app-region: no-drag"
|
||||
>
|
||||
<div
|
||||
class="group absolute inset-y-0 left-0 z-10 w-2 -translate-x-1/2 cursor-col-resize touch-none"
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
:aria-label="t('records.drawer.resize')"
|
||||
:aria-valuemin="MIN_DRAWER_WIDTH"
|
||||
:aria-valuemax="maxDrawerWidth"
|
||||
:aria-valuenow="layoutStore.chatRecordDrawerWidth"
|
||||
tabindex="0"
|
||||
@pointerdown.prevent="startResize"
|
||||
@keydown="resizeWithKeyboard"
|
||||
>
|
||||
<div
|
||||
class="mx-auto h-full w-px bg-transparent transition-colors group-hover:bg-primary-400 group-focus:bg-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<!-- 头部 -->
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-gray-200 px-4 dark:border-gray-800"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"drawer": {
|
||||
"title": "Chat Record Viewer"
|
||||
"title": "Chat Record Viewer",
|
||||
"resize": "Resize chat record panel"
|
||||
},
|
||||
"workspace": {
|
||||
"indexMissingTitle": "No conversation summary index yet",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"drawer": {
|
||||
"title": "チャット履歴ビューア"
|
||||
"title": "チャット履歴ビューア",
|
||||
"resize": "チャット履歴パネルの幅を変更"
|
||||
},
|
||||
"workspace": {
|
||||
"indexMissingTitle": "会話要約インデックスがまだありません",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"drawer": {
|
||||
"title": "聊天记录查看器"
|
||||
"title": "聊天记录查看器",
|
||||
"resize": "调整聊天记录面板宽度"
|
||||
},
|
||||
"workspace": {
|
||||
"indexMissingTitle": "还没有可用的会话摘要索引",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"drawer": {
|
||||
"title": "聊天紀錄檢視器"
|
||||
"title": "聊天紀錄檢視器",
|
||||
"resize": "調整聊天紀錄面板寬度"
|
||||
},
|
||||
"workspace": {
|
||||
"indexMissingTitle": "尚未建立可用的會話摘要索引",
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
function createMemoryStorage(initial: Record<string, string> = {}): Storage {
|
||||
const values = new Map(Object.entries(initial))
|
||||
return {
|
||||
get length() {
|
||||
return values.size
|
||||
},
|
||||
clear: () => values.clear(),
|
||||
getItem: (key) => values.get(key) ?? null,
|
||||
key: (index) => [...values.keys()][index] ?? null,
|
||||
removeItem: (key) => values.delete(key),
|
||||
setItem: (key, value) => void values.set(key, value),
|
||||
}
|
||||
}
|
||||
|
||||
function createPersistedPinia(storage: Storage) {
|
||||
Object.defineProperty(globalThis, 'localStorage', { configurable: true, value: storage })
|
||||
const pinia = createPinia().use(piniaPluginPersistedstate)
|
||||
createApp({}).use(pinia)
|
||||
setActivePinia(pinia)
|
||||
return pinia
|
||||
}
|
||||
|
||||
test('layout persistence upgrades older state and restores a saved drawer width', async () => {
|
||||
const storage = createMemoryStorage({ layout: JSON.stringify({ isSidebarCollapsed: true }) })
|
||||
const legacyPinia = createPersistedPinia(storage)
|
||||
const { useLayoutStore } = await import('./layout')
|
||||
const legacyStore = useLayoutStore(legacyPinia)
|
||||
|
||||
assert.equal(legacyStore.isSidebarCollapsed, true)
|
||||
assert.equal(legacyStore.chatRecordDrawerWidth, 750)
|
||||
|
||||
storage.setItem('layout', JSON.stringify({ chatRecordDrawerWidth: 936 }))
|
||||
const restoredPinia = createPersistedPinia(storage)
|
||||
const restoredStore = useLayoutStore(restoredPinia)
|
||||
|
||||
assert.equal(restoredStore.chatRecordDrawerWidth, 936)
|
||||
})
|
||||
@@ -16,6 +16,7 @@ export const useLayoutStore = defineStore(
|
||||
const screenCaptureImage = ref<string | null>(null)
|
||||
const showChatRecordDrawer = ref(false)
|
||||
const chatRecordQuery = ref<ChatRecordQuery | null>(null)
|
||||
const chatRecordDrawerWidth = ref(750)
|
||||
|
||||
const isToolsPanelLocked = ref(false)
|
||||
const isToolsPanelMini = ref(false)
|
||||
@@ -117,6 +118,7 @@ export const useLayoutStore = defineStore(
|
||||
screenCaptureImage,
|
||||
showChatRecordDrawer,
|
||||
chatRecordQuery,
|
||||
chatRecordDrawerWidth,
|
||||
showSettings,
|
||||
settingsTab,
|
||||
settingsSubTab,
|
||||
@@ -142,6 +144,7 @@ export const useLayoutStore = defineStore(
|
||||
'isToolsPanelLocked',
|
||||
'isToolsPanelMini',
|
||||
'toolsPanelPosition',
|
||||
'chatRecordDrawerWidth',
|
||||
],
|
||||
storage: localStorage,
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user