write(ui): 三栏宽度可拖拽 + localStorage 持久化
deploy articulate / build-and-deploy (push) Successful in 1m15s
deploy cube / build-and-deploy (push) Successful in 1m40s
deploy karaoke / build-and-deploy (push) Successful in 1m6s
deploy llm-proxy / build-and-deploy (push) Successful in 2m3s
deploy music / build-and-deploy (push) Successful in 2m16s
deploy notes / build-and-deploy (push) Successful in 2m2s
deploy simpleasm / build-and-deploy (push) Successful in 1m30s
deploy werewolf / build-and-deploy (push) Successful in 1m12s
deploy write / build-and-deploy (push) Successful in 1m54s

- sidebar | workspace 拖拽条:调整侧栏宽(180-500px)
- editor | preview 拖拽条:调整源码/预览比例(15%-85%)
- CSS var --sidebar-w / --editor-fr / --preview-fr 驱动 grid-template-columns
- 鼠标 down 开始 drag,move 实时算 px/dx,up 落盘 localStorage
- 移动端(<768px)自动隐藏拖拽条,回到 100% 切 tab 模式
This commit is contained in:
Fam Zheng
2026-05-24 17:18:37 +01:00
parent 9328c01c1b
commit 7b868852d2
4 changed files with 270 additions and 9 deletions
Generated
+194 -7
View File
@@ -56,6 +56,7 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core", "axum-core",
"base64",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@@ -75,8 +76,10 @@ dependencies = [
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
"serde_urlencoded", "serde_urlencoded",
"sha1",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-tungstenite",
"tower", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@@ -116,12 +119,27 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.20.2" version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.1" version = "1.11.1"
@@ -150,6 +168,25 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "cube" name = "cube"
version = "0.1.0" version = "0.1.0"
@@ -174,6 +211,22 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
] ]
[[package]]
name = "data-encoding"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.5"
@@ -319,6 +372,16 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@@ -882,7 +945,7 @@ dependencies = [
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"socket2", "socket2",
"thiserror", "thiserror 2.0.18",
"tokio", "tokio",
"tracing", "tracing",
"web-time", "web-time",
@@ -897,13 +960,13 @@ dependencies = [
"bytes", "bytes",
"getrandom 0.3.4", "getrandom 0.3.4",
"lru-slab", "lru-slab",
"rand", "rand 0.9.4",
"ring", "ring",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"slab", "slab",
"thiserror", "thiserror 2.0.18",
"tinyvec", "tinyvec",
"tracing", "tracing",
"web-time", "web-time",
@@ -938,14 +1001,35 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.4" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
dependencies = [ dependencies = [
"rand_chacha", "rand_chacha 0.9.0",
"rand_core", "rand_core 0.9.5",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
] ]
[[package]] [[package]]
@@ -955,7 +1039,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.9.5",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
] ]
[[package]] [[package]]
@@ -1188,6 +1281,17 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@@ -1297,13 +1401,33 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.18" version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl 2.0.18",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
] ]
[[package]] [[package]]
@@ -1400,6 +1524,18 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-tungstenite"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.18" version = "0.7.18"
@@ -1550,6 +1686,30 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand 0.8.6",
"sha1",
"thiserror 1.0.69",
"utf-8",
]
[[package]]
name = "typenum"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.9.0" version = "2.9.0"
@@ -1580,6 +1740,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8_iter" name = "utf8_iter"
version = "1.0.4" version = "1.0.4"
@@ -1901,6 +2067,27 @@ version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "write"
version = "0.1.0"
dependencies = [
"axum",
"cube-core",
"futures",
"futures-util",
"reqwest",
"rusqlite",
"serde",
"serde_json",
"tokio",
"tokio-stream",
"tokio-tungstenite",
"tower",
"tower-http",
"tracing",
"url",
]
[[package]] [[package]]
name = "writeable" name = "writeable"
version = "0.6.3" version = "0.6.3"
+1
View File
@@ -10,6 +10,7 @@ members = [
"apps/karaoke", "apps/karaoke",
"apps/notes", "apps/notes",
"apps/llm-proxy", "apps/llm-proxy",
"apps/write",
] ]
[workspace.package] [workspace.package]
+59
View File
@@ -14,6 +14,62 @@ const statusErr = ref(false)
const busy = ref(false) const busy = ref(false)
const recording = ref(false) const recording = ref(false)
onMounted(() => applyLayoutVars())
// ====== 拖拽布局:sidebar 宽(px + editor/preview 比例(0-1======
const sidebarW = ref(parseInt(localStorage.getItem('write.sidebarW') || '260'))
const editorRatio = ref(parseFloat(localStorage.getItem('write.editorRatio') || '0.5'))
function applyLayoutVars(): void {
const r = document.documentElement
r.style.setProperty('--sidebar-w', sidebarW.value + 'px')
r.style.setProperty('--editor-fr', editorRatio.value + 'fr')
r.style.setProperty('--preview-fr', (1 - editorRatio.value) + 'fr')
}
let dragKind: 'sidebar' | 'editor' | null = null
let dragStartX = 0
let dragStartVal = 0
let dragPaneW = 0
function startDrag(e: MouseEvent, kind: 'sidebar' | 'editor'): void {
dragKind = kind
dragStartX = e.clientX
if (kind === 'sidebar') {
dragStartVal = sidebarW.value
} else {
dragStartVal = editorRatio.value
const pane = (e.currentTarget as HTMLElement).parentElement
dragPaneW = pane ? pane.offsetWidth : window.innerWidth
}
document.body.style.cursor = 'col-resize'
document.body.style.userSelect = 'none'
window.addEventListener('mousemove', onDrag)
window.addEventListener('mouseup', endDrag)
e.preventDefault()
}
function onDrag(e: MouseEvent): void {
if (!dragKind) return
const dx = e.clientX - dragStartX
if (dragKind === 'sidebar') {
sidebarW.value = Math.max(180, Math.min(500, dragStartVal + dx))
} else {
const newR = dragStartVal + dx / Math.max(1, dragPaneW)
editorRatio.value = Math.max(0.15, Math.min(0.85, newR))
}
applyLayoutVars()
}
function endDrag(): void {
if (!dragKind) return
localStorage.setItem('write.sidebarW', String(sidebarW.value))
localStorage.setItem('write.editorRatio', String(editorRatio.value))
dragKind = null
document.body.style.cursor = ''
document.body.style.userSelect = ''
window.removeEventListener('mousemove', onDrag)
window.removeEventListener('mouseup', endDrag)
}
const needToken = ref(!getToken()) const needToken = ref(!getToken())
const tokenInput = ref('') const tokenInput = ref('')
@@ -218,6 +274,8 @@ onMounted(() => {
<div class="sidebar-backdrop" @click="drawerOpen = false"></div> <div class="sidebar-backdrop" @click="drawerOpen = false"></div>
<div class="splitter splitter-main" @mousedown="startDrag($event, 'sidebar')" title="拖动调整侧栏宽度"></div>
<aside class="sidebar"> <aside class="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<h1> write</h1> <h1> write</h1>
@@ -256,6 +314,7 @@ onMounted(() => {
:disabled="activeId === null" :disabled="activeId === null"
></textarea> ></textarea>
</div> </div>
<div class="splitter splitter-editor" @mousedown="startDrag($event, 'editor')" title="拖动调整源码/预览比例"></div>
<div class="preview" v-html="renderedPreview"></div> <div class="preview" v-html="renderedPreview"></div>
</div> </div>
+16 -2
View File
@@ -16,7 +16,7 @@ textarea, input { font: inherit; }
.app { .app {
display: grid; display: grid;
grid-template-columns: 260px 1fr; grid-template-columns: var(--sidebar-w, 260px) 6px 1fr;
height: 100vh; height: 100vh;
} }
@@ -92,11 +92,25 @@ textarea, input { font: inherit; }
.editor-pane { .editor-pane {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: var(--editor-fr, 1fr) 6px var(--preview-fr, 1fr);
min-height: 0; min-height: 0;
border-bottom: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;
} }
/* 拖拽分隔条 */
.splitter {
background: #e5e5e5;
cursor: col-resize;
transition: background 0.12s;
user-select: none;
}
.splitter:hover { background: #b0c5ff; }
.splitter-main { /* sidebar | workspace */ }
.splitter-editor { /* editor | preview */ }
@media (max-width: 768px) {
.splitter { display: none; }
}
.title-row { .title-row {
grid-column: 1 / -1; grid-column: 1 / -1;
padding: 10px 16px; padding: 10px 16px;