From e20447dbc4f857ab5d1c773e6991507ef9e79f3b Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sat, 11 Jul 2026 12:57:31 +0200 Subject: [PATCH 01/10] fix clippy: remove redundant api:_ wildcard in CloseRequested pattern --- src/commands.rs | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 7ca16a3..f15d893 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -501,7 +501,7 @@ pub async fn new_window( let app_handle = app_handle.clone(); let label_clone = label.clone(); window.on_window_event(move |event| { - if let tauri::WindowEvent::CloseRequested { api: _, .. } = event + if let tauri::WindowEvent::CloseRequested { .. } = event && let Some(win) = app_handle.get_webview_window(&label_clone) { save_window_state_internal(&win, &db); diff --git a/src/lib.rs b/src/lib.rs index b070e1e..e9af10f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,7 +99,7 @@ pub fn run() { let handle2 = handle.clone(); let label2 = label.clone(); window.on_window_event(move |event| { - if let tauri::WindowEvent::CloseRequested { api: _, .. } = event + if let tauri::WindowEvent::CloseRequested { .. } = event && let Some(win) = handle2.get_webview_window(&label2) { commands::save_window_state_internal(&win, &db2); From 96a4598e4736a20dd4337e87b02d3693922382cc Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 14 Jul 2026 13:51:55 +0200 Subject: [PATCH 02/10] fix: resolve LICENSE merge conflicts, update AUR PKGBUILD --- LICENSE | 22 ---------------------- dist/.SRCINFO | 2 -- dist/PKGBUILD | 3 +-- 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/LICENSE b/LICENSE index 162ee6c..f288702 100644 --- a/LICENSE +++ b/LICENSE @@ -392,13 +392,8 @@ governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms -<<<<<<< HEAD of that license document, provided that the further restriction does not survive such relicensing or conveying. -======= -of that license document, provided that the further restriction does not -survive such relicensing or conveying. ->>>>>>> 049e4f1 (Initial commit: LoFlum FTP/SFTP client) If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the @@ -516,20 +511,12 @@ in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or -<<<<<<< HEAD arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. -======= -arrangement, you convey, or procure by conveying, a covered work, and -grant a patent license to some of the parties receiving the covered work -authorizing them to use, propagate, modify or convey a specific copy of -the covered work, then the patent license you grant is automatically -extended to all recipients of the covered work and works based on it. ->>>>>>> 049e4f1 (Initial commit: LoFlum FTP/SFTP client) A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is @@ -547,13 +534,8 @@ contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting -<<<<<<< HEAD any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. -======= -any implied license or other defenses to infringement that may be -otherwise available to you under applicable patent law. ->>>>>>> 049e4f1 (Initial commit: LoFlum FTP/SFTP client) 12. No Surrender of Others' Freedom. @@ -594,11 +576,7 @@ Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. -<<<<<<< HEAD If the Program specifies that a proxy can decide which future -======= - If the Program specifies that a proxy can decide whether future ->>>>>>> 049e4f1 (Initial commit: LoFlum FTP/SFTP client) versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. diff --git a/dist/.SRCINFO b/dist/.SRCINFO index 534f91d..02874e3 100644 --- a/dist/.SRCINFO +++ b/dist/.SRCINFO @@ -7,7 +7,6 @@ pkgbase = wherry license = GPL3 makedepends = cargo makedepends = pkg-config - makedepends = patchelf depends = gcc-libs depends = gtk3 depends = webkit2gtk-4.1 @@ -18,7 +17,6 @@ pkgbase = wherry depends = librsvg depends = openssl depends = libssh2 - depends = libappindicator-gtk3 depends = dbus source = https://github.com/loki5512344/Wherry/archive/refs/tags/v0.1.0.tar.gz sha256sums = SKIP diff --git a/dist/PKGBUILD b/dist/PKGBUILD index 2b94a3d..3952426 100644 --- a/dist/PKGBUILD +++ b/dist/PKGBUILD @@ -18,10 +18,9 @@ depends=( 'librsvg' 'openssl' 'libssh2' - 'libappindicator-gtk3' 'dbus' ) -makedepends=('cargo' 'pkg-config' 'patchelf') +makedepends=('cargo' 'pkg-config') source=("https://github.com/loki5512344/Wherry/archive/refs/tags/v$pkgver.tar.gz") sha256sums=('SKIP') From 4a4adb1ae5c6e3b743b9d6c00471ad636b1127b7 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 14 Jul 2026 13:53:57 +0200 Subject: [PATCH 03/10] update sha256sum for v0.1.0 --- dist/.SRCINFO | 2 +- dist/PKGBUILD | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/.SRCINFO b/dist/.SRCINFO index 02874e3..a9ffb59 100644 --- a/dist/.SRCINFO +++ b/dist/.SRCINFO @@ -19,6 +19,6 @@ pkgbase = wherry depends = libssh2 depends = dbus source = https://github.com/loki5512344/Wherry/archive/refs/tags/v0.1.0.tar.gz - sha256sums = SKIP + sha256sums = 2c9ee91948fdad352ba797ce8652bf626797769a74e2707504184f541eecf0ab pkgname = wherry diff --git a/dist/PKGBUILD b/dist/PKGBUILD index 3952426..a53568f 100644 --- a/dist/PKGBUILD +++ b/dist/PKGBUILD @@ -22,7 +22,7 @@ depends=( ) makedepends=('cargo' 'pkg-config') source=("https://github.com/loki5512344/Wherry/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('SKIP') +sha256sums=('2c9ee91948fdad352ba797ce8652bf626797769a74e2707504184f541eecf0ab') _repodir="Wherry-$pkgver" From 88260d7ea2bb5f075011b4de228f7b680622b2f4 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 14 Jul 2026 14:06:44 +0200 Subject: [PATCH 04/10] fix AUR: remove jobs limit, drop explicit linker --- .cargo/config.toml | 3 +-- dist/PKGBUILD | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 39dc687..8b13789 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1 @@ -[build] -jobs = 6 + diff --git a/dist/PKGBUILD b/dist/PKGBUILD index a53568f..7e4aee7 100644 --- a/dist/PKGBUILD +++ b/dist/PKGBUILD @@ -2,7 +2,7 @@ pkgname=wherry pkgver=0.1.0 -pkgrel=1 +pkgrel=2 pkgdesc="A modern dual-pane file manager with SFTP/FTP/FTPS support" arch=('x86_64') url='https://github.com/loki5512344/Wherry' @@ -35,7 +35,6 @@ prepare() { build() { cd "$srcdir/$_repodir" export CARGO_TARGET_DIR="$srcdir/target" - export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="/usr/bin/cc" cargo build --release --frozen } From c32c3a2e2d7805a44747df78433388017b2ca3a5 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 14 Jul 2026 14:09:47 +0200 Subject: [PATCH 05/10] feat: add release workflow, wherry-bin PKGBUILD, mold optdepends --- .github/workflows/rust.yml | 27 +++++++++++++++++++++++++++ dist/.SRCINFO | 5 +++-- dist/PKGBUILD | 3 ++- dist/wherry-bin/.SRCINFO | 22 ++++++++++++++++++++++ dist/wherry-bin/PKGBUILD | 28 ++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 dist/wherry-bin/.SRCINFO create mode 100644 dist/wherry-bin/PKGBUILD diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 64560bb..ec6ad31 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -3,6 +3,7 @@ name: Rust on: push: branches: [ "main" ] + tags: [ "v*" ] pull_request: branches: [ "main" ] @@ -58,6 +59,10 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Build (release) run: cargo build --release + - uses: actions/upload-artifact@v4 + with: + name: wherry-x86_64 + path: target/release/wherry test: runs-on: ubuntu-latest @@ -81,3 +86,25 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run tests run: cargo test + + release: + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + needs: [build, test] + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: wherry-x86_64 + - name: Package binary + run: | + mkdir -p pkg/usr/bin pkg/usr/share/licenses/wherry pkg/usr/share/pixmaps pkg/usr/share/applications + install -Dm755 wherry pkg/usr/bin/wherry + install -Dm644 LICENSE pkg/usr/share/licenses/wherry/LICENSE + install -Dm644 src/icons/128x128.png pkg/usr/share/pixmaps/wherry.png + install -Dm644 dist/wherry.desktop pkg/usr/share/applications/wherry.desktop + tar czf wherry-v${{ github.ref_name }}-x86_64-linux.tar.gz -C pkg . + - uses: softprops/action-gh-release@v2 + with: + files: wherry-v${{ github.ref_name }}-x86_64-linux.tar.gz + generate_release_notes: true diff --git a/dist/.SRCINFO b/dist/.SRCINFO index a9ffb59..0022b37 100644 --- a/dist/.SRCINFO +++ b/dist/.SRCINFO @@ -1,7 +1,7 @@ pkgbase = wherry pkgdesc = A modern dual-pane file manager with SFTP/FTP/FTPS support pkgver = 0.1.0 - pkgrel = 1 + pkgrel = 2 url = https://github.com/loki5512344/Wherry arch = x86_64 license = GPL3 @@ -18,7 +18,8 @@ pkgbase = wherry depends = openssl depends = libssh2 depends = dbus + optdepends = mold: faster linking source = https://github.com/loki5512344/Wherry/archive/refs/tags/v0.1.0.tar.gz - sha256sums = 2c9ee91948fdad352ba797ce8652bf626797769a74e2707504184f541eecf0ab + sha256sums = 9974b7477dc3c051a74812d4be721da360a1498a737ceebad7650014da2b6d20 pkgname = wherry diff --git a/dist/PKGBUILD b/dist/PKGBUILD index 7e4aee7..1e15527 100644 --- a/dist/PKGBUILD +++ b/dist/PKGBUILD @@ -21,8 +21,9 @@ depends=( 'dbus' ) makedepends=('cargo' 'pkg-config') +optdepends=('mold: faster linking') source=("https://github.com/loki5512344/Wherry/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('2c9ee91948fdad352ba797ce8652bf626797769a74e2707504184f541eecf0ab') +sha256sums=('9974b7477dc3c051a74812d4be721da360a1498a737ceebad7650014da2b6d20') _repodir="Wherry-$pkgver" diff --git a/dist/wherry-bin/.SRCINFO b/dist/wherry-bin/.SRCINFO new file mode 100644 index 0000000..b692117 --- /dev/null +++ b/dist/wherry-bin/.SRCINFO @@ -0,0 +1,22 @@ +pkgbase = wherry-bin + pkgdesc = A modern dual-pane file manager with SFTP/FTP/FTPS support (pre-compiled) + pkgver = 0.1.0 + pkgrel = 1 + url = https://github.com/loki5512344/Wherry + arch = x86_64 + license = GPL3 + depends = gcc-libs + depends = gtk3 + depends = webkit2gtk-4.1 + depends = libsoup3 + depends = cairo + depends = gdk-pixbuf2 + depends = glib2 + depends = librsvg + depends = openssl + depends = libssh2 + depends = dbus + source = https://github.com/loki5512344/Wherry/releases/download/v0.1.0/wherry-v0.1.0-x86_64-linux.tar.gz + sha256sums = SKIP + +pkgname = wherry-bin diff --git a/dist/wherry-bin/PKGBUILD b/dist/wherry-bin/PKGBUILD new file mode 100644 index 0000000..6116964 --- /dev/null +++ b/dist/wherry-bin/PKGBUILD @@ -0,0 +1,28 @@ +# Maintainer: loki5512344 + +pkgname=wherry-bin +pkgver=0.1.0 +pkgrel=1 +pkgdesc="A modern dual-pane file manager with SFTP/FTP/FTPS support (pre-compiled)" +arch=('x86_64') +url='https://github.com/loki5512344/Wherry' +license=('GPL3') +depends=( + 'gcc-libs' + 'gtk3' + 'webkit2gtk-4.1' + 'libsoup3' + 'cairo' + 'gdk-pixbuf2' + 'glib2' + 'librsvg' + 'openssl' + 'libssh2' + 'dbus' +) +source=("https://github.com/loki5512344/Wherry/releases/download/v$pkgver/wherry-v$pkgver-x86_64-linux.tar.gz") +sha256sums=('SKIP') + +package() { + cp -r "$srcdir"/* "$pkgdir"/ +} From f22e0cb7019803fe579b697df02e81ebdc0737c9 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 14 Jul 2026 14:16:20 +0200 Subject: [PATCH 06/10] fix CI: add contents:write permission for release --- .github/workflows/rust.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ec6ad31..759ea97 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -91,6 +91,8 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest needs: [build, test] + permissions: + contents: write steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 From 0276e5692ea9b3c3f83d9ba56fe00eb9981d0d4b Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 14 Jul 2026 14:23:56 +0200 Subject: [PATCH 07/10] fix CI: remove double v in release filename --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 759ea97..a767691 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -105,8 +105,8 @@ jobs: install -Dm644 LICENSE pkg/usr/share/licenses/wherry/LICENSE install -Dm644 src/icons/128x128.png pkg/usr/share/pixmaps/wherry.png install -Dm644 dist/wherry.desktop pkg/usr/share/applications/wherry.desktop - tar czf wherry-v${{ github.ref_name }}-x86_64-linux.tar.gz -C pkg . + tar czf wherry-${{ github.ref_name }}-x86_64-linux.tar.gz -C pkg . - uses: softprops/action-gh-release@v2 with: - files: wherry-v${{ github.ref_name }}-x86_64-linux.tar.gz + files: wherry-${{ github.ref_name }}-x86_64-linux.tar.gz generate_release_notes: true From 7ced3bc24f49fbf2644cd543bb61719b54b546d9 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 14 Jul 2026 14:30:30 +0200 Subject: [PATCH 08/10] fix: update maintainer email --- dist/.SRCINFO | 2 +- dist/PKGBUILD | 4 ++-- dist/wherry-bin/.SRCINFO | 2 +- dist/wherry-bin/PKGBUILD | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/.SRCINFO b/dist/.SRCINFO index 0022b37..f29096d 100644 --- a/dist/.SRCINFO +++ b/dist/.SRCINFO @@ -20,6 +20,6 @@ pkgbase = wherry depends = dbus optdepends = mold: faster linking source = https://github.com/loki5512344/Wherry/archive/refs/tags/v0.1.0.tar.gz - sha256sums = 9974b7477dc3c051a74812d4be721da360a1498a737ceebad7650014da2b6d20 + sha256sums = 7f0ad6e308cf20439820b19136cb9113cb9b76e7cc81275248d5dd8fed812ea8 pkgname = wherry diff --git a/dist/PKGBUILD b/dist/PKGBUILD index 1e15527..e8a6134 100644 --- a/dist/PKGBUILD +++ b/dist/PKGBUILD @@ -1,4 +1,4 @@ -# Maintainer: loki5512344 +# Maintainer: loki5512344 pkgname=wherry pkgver=0.1.0 @@ -23,7 +23,7 @@ depends=( makedepends=('cargo' 'pkg-config') optdepends=('mold: faster linking') source=("https://github.com/loki5512344/Wherry/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('9974b7477dc3c051a74812d4be721da360a1498a737ceebad7650014da2b6d20') +sha256sums=('7f0ad6e308cf20439820b19136cb9113cb9b76e7cc81275248d5dd8fed812ea8') _repodir="Wherry-$pkgver" diff --git a/dist/wherry-bin/.SRCINFO b/dist/wherry-bin/.SRCINFO index b692117..de06f1a 100644 --- a/dist/wherry-bin/.SRCINFO +++ b/dist/wherry-bin/.SRCINFO @@ -17,6 +17,6 @@ pkgbase = wherry-bin depends = libssh2 depends = dbus source = https://github.com/loki5512344/Wherry/releases/download/v0.1.0/wherry-v0.1.0-x86_64-linux.tar.gz - sha256sums = SKIP + sha256sums = bbaa77db529594b94accfcea6374edc891b904bfae6a3ca895b028145dc9c77d pkgname = wherry-bin diff --git a/dist/wherry-bin/PKGBUILD b/dist/wherry-bin/PKGBUILD index 6116964..f33e2e2 100644 --- a/dist/wherry-bin/PKGBUILD +++ b/dist/wherry-bin/PKGBUILD @@ -1,4 +1,4 @@ -# Maintainer: loki5512344 +# Maintainer: loki5512344 pkgname=wherry-bin pkgver=0.1.0 @@ -21,7 +21,7 @@ depends=( 'dbus' ) source=("https://github.com/loki5512344/Wherry/releases/download/v$pkgver/wherry-v$pkgver-x86_64-linux.tar.gz") -sha256sums=('SKIP') +sha256sums=('bbaa77db529594b94accfcea6374edc891b904bfae6a3ca895b028145dc9c77d') package() { cp -r "$srcdir"/* "$pkgdir"/ From b2413e5e5a9839688c1b0155269fce9418ec1b84 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 14 Jul 2026 14:48:12 +0200 Subject: [PATCH 09/10] feat: auto dual-pane on connect, add resize handles, always show close btn --- frontend/css/layout.css | 37 ++++++- frontend/js/components/paneLayout.js | 155 ++++++++++++++++++--------- frontend/js/connectActions.js | 9 +- 3 files changed, 146 insertions(+), 55 deletions(-) diff --git a/frontend/css/layout.css b/frontend/css/layout.css index d2d47c2..2b87b12 100644 --- a/frontend/css/layout.css +++ b/frontend/css/layout.css @@ -649,8 +649,42 @@ html[data-platform="linux"] ::-webkit-scrollbar { height: 7px; } +/* ── Pane resize handle ──────────────────────────────────────────────── */ +.pane-resize-handle { + width: 5px; + cursor: col-resize; + background: transparent; + position: relative; + z-index: 10; + flex-shrink: 0; + margin: 0 -2px; + transition: background 0.12s var(--ease-out); +} +.pane-resize-handle:hover, +.pane-resize-handle:active { + background: var(--accent-soft); +} +.pane-resize-handle::after { + content: ""; + position: absolute; + left: 2px; + top: 50%; + transform: translateY(-50%); + width: 1px; + height: 36px; + background: var(--border); + border-radius: 1px; + transition: background 0.12s var(--ease-out), height 0.12s var(--ease-out); +} +.pane-resize-handle:hover::after, +.pane-resize-handle:active::after { + background: var(--accent); + height: 48px; +} + /* ── Split-pane grid ─────────────────────────────────────────────────── */ .pane-grid { + display: grid; flex: 1; min-height: 0; } @@ -663,9 +697,6 @@ html[data-platform="linux"] ::-webkit-scrollbar { position: relative; overflow: hidden; } -.pane-cell + .pane-cell { - border-left: 1px solid var(--border); -} .pane-cell.drop-zone { background: var(--bg-panel); outline: 2px solid var(--accent); diff --git a/frontend/js/components/paneLayout.js b/frontend/js/components/paneLayout.js index 5fb5fb5..76a4f9a 100644 --- a/frontend/js/components/paneLayout.js +++ b/frontend/js/components/paneLayout.js @@ -12,6 +12,8 @@ const paneRefs = new Map(); function paneKind(p) { return p.tabId ?? "local"; } function paneLabel(p) { return p.tabId === null ? "Local" : (state.tabs.find((t) => t.id === p.tabId)?.label ?? "?"); } +function ensureFlex(p) { if (p.flex == null) p.flex = 1; return p; } + /* Render the full pane grid */ export function renderPanes(container) { container.innerHTML = ""; @@ -19,10 +21,9 @@ export function renderPanes(container) { const wrap = document.createElement("div"); wrap.className = "pane-grid"; - wrap.style.cssText = `display:grid;grid-template-columns:${buildGrid()};flex:1;min-height:0;`; + applyGrid(wrap); wrap.addEventListener("dragover", (e) => { e.preventDefault(); }); wrap.addEventListener("drop", (e) => { - // Drop on empty area (after last pane) → append e.preventDefault(); const tabId = e.dataTransfer.getData("tab-id"); if (!tabId) return; @@ -31,13 +32,91 @@ export function renderPanes(container) { }); container.appendChild(wrap); - for (const pane of state.layout.panes) { - const cell = createPaneCell(pane); + for (let i = 0; i < state.layout.panes.length; i++) { + if (i > 0) { + const handle = createResizeHandle(i - 1); + wrap.appendChild(handle); + } + const cell = createPaneCell(state.layout.panes[i]); wrap.appendChild(cell); } } +function applyGrid(wrap) { + const n = state.layout.panes.length; + const totalFlex = state.layout.panes.reduce((s, p) => s + (p.flex || 1), 0); + const cols = state.layout.panes.map((p) => `${((p.flex || 1) / totalFlex * 100).toFixed(2)}fr`).join(" "); + wrap.style.cssText = `display:grid;grid-template-columns:${n > 1 ? cols : "1fr"};flex:1;min-height:0;`; +} + +function createResizeHandle(index) { + const div = document.createElement("div"); + div.className = "pane-resize-handle"; + div.dataset.index = index; + div.addEventListener("mousedown", onResizeStart); + return div; +} + +let resizing = null; + +function onResizeStart(e) { + e.preventDefault(); + const handle = e.currentTarget; + const index = parseInt(handle.dataset.index); + const wrap = handle.closest(".pane-grid"); + const rect = wrap.getBoundingClientRect(); + const startX = e.clientX; + + const panes = state.layout.panes; + const totalFlex = panes.reduce((s, p) => s + (p.flex || 1), 0); + const currentPct = panes.map((p) => (p.flex || 1) / totalFlex); + + resizing = { index, startX, wrap, rect, panes, currentPct, totalFlex }; + + document.addEventListener("mousemove", onResizeMove); + document.addEventListener("mouseup", onResizeEnd); + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; +} + +function onResizeMove(e) { + if (!resizing) return; + const { index, startX, rect, panes, currentPct } = resizing; + const dx = e.clientX - startX; + const totalWidth = rect.width; + if (totalWidth <= 0) return; + + const leftPct = currentPct[index]; + const rightPct = currentPct[index + 1]; + const pctChange = dx / totalWidth; + + const newLeft = Math.max(0.1, Math.min(0.9, leftPct + pctChange)); + const newRight = Math.max(0.1, Math.min(0.9, rightPct - pctChange)); + if (newLeft + newRight < 0.2) return; + + const newTotal = newLeft + newRight + currentPct.slice(0, index).reduce((s, v) => s + v, 0) + currentPct.slice(index + 2).reduce((s, v) => s + v, 0); + + const scale = currentPct.reduce((a, b) => a + b, 0) / newTotal; + for (let i = 0; i < panes.length; i++) { + if (i === index) panes[i].flex = newLeft * scale; + else if (i === index + 1) panes[i].flex = newRight * scale; + else panes[i].flex = currentPct[i] * scale; + } + + const wrap = resizing.wrap; + applyGrid(wrap); +} + +function onResizeEnd() { + resizing = null; + document.removeEventListener("mousemove", onResizeMove); + document.removeEventListener("mouseup", onResizeEnd); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; +} + function createPaneCell(pane) { + ensureFlex(pane); const cell = document.createElement("div"); cell.className = "pane-cell"; cell.dataset.paneId = pane.id; @@ -60,14 +139,12 @@ function createPaneCell(pane) { header.addEventListener("dragend", onHeaderDragEnd); // Close btn - if (state.layout.panes.length > 1) { - const closeBtn = document.createElement("button"); - closeBtn.className = "pane-close-btn"; - closeBtn.title = "Close"; - closeBtn.innerHTML = iconMarkup("close", 13); - closeBtn.addEventListener("click", (e) => { e.stopPropagation(); closePane(pane.id); }); - header.appendChild(closeBtn); - } + const closeBtn = document.createElement("button"); + closeBtn.className = "pane-close-btn"; + closeBtn.title = "Close pane"; + closeBtn.innerHTML = iconMarkup("close", 13); + closeBtn.addEventListener("click", (e) => { e.stopPropagation(); closePane(pane.id); }); + header.appendChild(closeBtn); // Body const body = document.createElement("div"); @@ -94,12 +171,6 @@ function createPaneCell(pane) { return cell; } -function buildGrid() { - const n = state.layout.panes.length; - if (n <= 1) return "1fr"; - return state.layout.panes.map(() => "1fr").join(" "); -} - /* In-place update (avoids full rebuild on every tick) */ export function updatePanes(container) { const wrap = container.querySelector(".pane-grid"); @@ -110,7 +181,7 @@ export function updatePanes(container) { const expected = state.layout.panes.map((p) => p.id); if (ids.join(",") !== expected.join(",")) return renderPanes(container); - wrap.style.gridTemplateColumns = buildGrid(); + applyGrid(wrap); for (const pane of state.layout.panes) { const ref = paneRefs.get(pane.id); @@ -127,20 +198,10 @@ export function updatePanes(container) { // ── Drag & Drop: tab bar → pane ───────────────────────────────────────── -/* Tab bar tabs become draggable — set data "tab-id" on dragstart. Pane cells - get visual drop zones (left/right/top/bottom/center), but two rules keep - it from feeling random: - 1. The pane already showing the dragged tab never offers itself as a - target -- there's nowhere sensible to put a tab inside its own pane. - 2. Every other pane needs a beat of sustained hover (HOVER_OPEN_DELAY) - before it "opens" as a drop target. Zones appearing the instant the - pointer crosses into a pane read as jumpy; the pause makes hovering - read as an intentional choice instead. */ - -let activeZone = null; // "-" of the currently shown zone overlay -let holdTimer = null; // pending "open" timer for the pane below -let holdPaneId = null; // pane the hold timer is counting for -let openPaneId = null; // pane that passed the hold and is now a live drop target +let activeZone = null; +let holdTimer = null; +let holdPaneId = null; +let openPaneId = null; function computeZone(cell, clientX, clientY) { const rect = cell.getBoundingClientRect(); @@ -193,10 +254,6 @@ function onPaneDragOver(e) { const paneId = cell.dataset.paneId; const pane = state.layout.panes.find((p) => p.id === paneId); - // Dragging a tab over the pane that already shows it -- never a valid - // target. Stop the event here so it doesn't bubble to the grid wrap's - // unconditional preventDefault(), which would otherwise still allow a - // drop (routed to the "append at end" handler) at this pointer position. if (pane && pane.tabId === getDraggedTabId()) { e.stopPropagation(); return; @@ -246,7 +303,7 @@ function onPaneDrop(e) { const wasOpen = openPaneId === paneId; const zone = cell.dataset.dropZone || "center"; resetPaneDrag(); - if (!tabId || !wasOpen) return; // released before this pane "opened" -- ignore + if (!tabId || !wasOpen) return; const idx = state.layout.panes.findIndex((p) => p.id === paneId); if (idx === -1) return; @@ -255,9 +312,9 @@ function onPaneDrop(e) { switch (zone) { case "left": addPane(tid, idx); break; case "right": addPane(tid, idx + 1); break; - case "top": addPane(tid, idx + 1); break; // in flat layout, left/right suffices - case "bottom": addPane(tid, idx + 1); break; // top/bottom = same as right - case "center": // replace this pane (but don't add duplicate) + case "top": addPane(tid, idx + 1); break; + case "bottom": addPane(tid, idx + 1); break; + case "center": if (!state.layout.panes.some((p) => p.tabId === tid && p.id !== paneId)) { state.layout.panes[idx].tabId = tid; } @@ -266,8 +323,6 @@ function onPaneDrop(e) { notify(); } -// A drag can end (dropped, cancelled, Escaped) outside every pane cell -- -// clean up hold/zone state unconditionally so it never gets stuck. window.addEventListener("dragend", resetPaneDrag); // ── Header drag (reorder) ─────────────────────────────────────────────── @@ -292,28 +347,28 @@ function parsedTabId(raw) { } function addPane(tabId, index) { - // If this tab is already in another pane, remove it const existing = state.layout.panes.findIndex((p) => p.tabId === tabId); if (existing !== -1) { state.layout.panes.splice(existing, 1); if (existing < index) index--; } - state.layout.panes.splice(index, 0, { id: `pane-${Date.now()}`, tabId }); + state.layout.panes.splice(index, 0, { id: `pane-${Date.now()}`, tabId, flex: 1 }); } function closePane(paneId) { - if (state.layout.panes.length <= 1) return; + const pane = state.layout.panes.find((p) => p.id === paneId); + if (!pane) return; + const isLastRemote = pane.tabId !== null && state.layout.panes.filter((p) => p.tabId !== null).length === 1; state.layout.panes = state.layout.panes.filter((p) => p.id !== paneId); + if (state.layout.panes.length === 0) { + state.layout.panes.push({ id: `pane-${Date.now()}`, tabId: null, flex: 1 }); + } notify(); } // ── Public API: open a tab in a pane from outside ─────────────────────── export function openInPane(tabId) { - // If already visible, do nothing (it's already in some pane) if (state.layout.panes.some((p) => p.tabId === tabId)) return; - - // Replace the first pane (single-pane UX — click = switch, not split) - // Splitting only happens via drag-and-drop into edge zones. state.layout.panes[0].tabId = tabId; notify(); } diff --git a/frontend/js/connectActions.js b/frontend/js/connectActions.js index 34dc24b..ab16401 100644 --- a/frontend/js/connectActions.js +++ b/frontend/js/connectActions.js @@ -59,8 +59,13 @@ export async function spawnConnect(params) { state.activeTabId = connectionId; state.lastRemoteTabId = connectionId; state.activePane = "remote"; - // Replace first pane with new connection (click = switch, not split) - state.layout.panes[0].tabId = connectionId; + // Open dual-pane: local (left) + remote (right) + if (state.layout.panes.length === 1 && state.layout.panes[0].tabId === null) { + state.layout.panes.push({ id: `pane-${Date.now()}`, tabId: connectionId, flex: 1 }); + state.layout.panes[0].flex = 1; + } else { + state.layout.panes[0].tabId = connectionId; + } state.connectLoading = false; state.showConnectDialog = false; state.statusMessage = `Connected to ${params.host}`; From a617fbfadf7f14496e72846342c46a825e230da9 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 14 Jul 2026 15:02:29 +0200 Subject: [PATCH 10/10] update sha256sums for v0.1.0 --- dist/.SRCINFO | 2 +- dist/PKGBUILD | 2 +- dist/wherry-bin/.SRCINFO | 2 +- dist/wherry-bin/PKGBUILD | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/.SRCINFO b/dist/.SRCINFO index f29096d..25a2590 100644 --- a/dist/.SRCINFO +++ b/dist/.SRCINFO @@ -20,6 +20,6 @@ pkgbase = wherry depends = dbus optdepends = mold: faster linking source = https://github.com/loki5512344/Wherry/archive/refs/tags/v0.1.0.tar.gz - sha256sums = 7f0ad6e308cf20439820b19136cb9113cb9b76e7cc81275248d5dd8fed812ea8 + sha256sums = 3cebca09e29629f4edb56a42074cad9b1f2539fc64cf3e96c3e7208dbacff1ec pkgname = wherry diff --git a/dist/PKGBUILD b/dist/PKGBUILD index e8a6134..876e2cd 100644 --- a/dist/PKGBUILD +++ b/dist/PKGBUILD @@ -23,7 +23,7 @@ depends=( makedepends=('cargo' 'pkg-config') optdepends=('mold: faster linking') source=("https://github.com/loki5512344/Wherry/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('7f0ad6e308cf20439820b19136cb9113cb9b76e7cc81275248d5dd8fed812ea8') +sha256sums=('3cebca09e29629f4edb56a42074cad9b1f2539fc64cf3e96c3e7208dbacff1ec') _repodir="Wherry-$pkgver" diff --git a/dist/wherry-bin/.SRCINFO b/dist/wherry-bin/.SRCINFO index de06f1a..d640799 100644 --- a/dist/wherry-bin/.SRCINFO +++ b/dist/wherry-bin/.SRCINFO @@ -17,6 +17,6 @@ pkgbase = wherry-bin depends = libssh2 depends = dbus source = https://github.com/loki5512344/Wherry/releases/download/v0.1.0/wherry-v0.1.0-x86_64-linux.tar.gz - sha256sums = bbaa77db529594b94accfcea6374edc891b904bfae6a3ca895b028145dc9c77d + sha256sums = d00bedf0ea2890a18d5c506880a33f4bd87eeec2228309279a3d06b1657a7b5e pkgname = wherry-bin diff --git a/dist/wherry-bin/PKGBUILD b/dist/wherry-bin/PKGBUILD index f33e2e2..6846021 100644 --- a/dist/wherry-bin/PKGBUILD +++ b/dist/wherry-bin/PKGBUILD @@ -21,7 +21,7 @@ depends=( 'dbus' ) source=("https://github.com/loki5512344/Wherry/releases/download/v$pkgver/wherry-v$pkgver-x86_64-linux.tar.gz") -sha256sums=('bbaa77db529594b94accfcea6374edc891b904bfae6a3ca895b028145dc9c77d') +sha256sums=('d00bedf0ea2890a18d5c506880a33f4bd87eeec2228309279a3d06b1657a7b5e') package() { cp -r "$srcdir"/* "$pkgdir"/