Make transfer panes resizable
This commit is contained in:
parent
c7f6750d6c
commit
97a65dbbd4
3 changed files with 51 additions and 29 deletions
|
|
@ -73,8 +73,8 @@ func renderStartPage(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Mail-Graveyard</title>
|
<title>Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
<link rel="stylesheet" href="/static/style.css?v=20260713-5">
|
||||||
<script src="/static/app.js?v=20260712-22" defer></script></head>
|
<script src="/static/app.js?v=20260713-5" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
||||||
|
|
@ -121,9 +121,9 @@ func renderShell(w http.ResponseWriter, r *http.Request, active, readingPane str
|
||||||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Mail-Graveyard</title>
|
<title>Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
<link rel="stylesheet" href="/static/style.css?v=20260713-5">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-22" defer></script></head>
|
<script src="/static/app.js?v=20260713-5" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
||||||
|
|
@ -697,9 +697,9 @@ func renderAccountsPage(w http.ResponseWriter, r *http.Request, accounts []Accou
|
||||||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Konten-Verwaltung - Mail-Graveyard</title>
|
<title>Konten-Verwaltung - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
<link rel="stylesheet" href="/static/style.css?v=20260713-5">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-22" defer></script></head>
|
<script src="/static/app.js?v=20260713-5" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
||||||
|
|
@ -743,9 +743,9 @@ func renderArchivesPage(w http.ResponseWriter, r *http.Request, archives []strin
|
||||||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Archiv-mbox Verwaltung - Mail-Graveyard</title>
|
<title>Archiv-mbox Verwaltung - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
<link rel="stylesheet" href="/static/style.css?v=20260713-5">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-22" defer></script></head>
|
<script src="/static/app.js?v=20260713-5" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
||||||
|
|
@ -791,9 +791,9 @@ func renderUsersPage(w http.ResponseWriter, r *http.Request, users []AppUser, ed
|
||||||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Benutzerverwaltung - Mail-Graveyard</title>
|
<title>Benutzerverwaltung - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
<link rel="stylesheet" href="/static/style.css?v=20260713-5">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-22" defer></script></head>
|
<script src="/static/app.js?v=20260713-5" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
||||||
|
|
@ -865,9 +865,9 @@ func renderTransferPage(w http.ResponseWriter, r *http.Request, mode transferMod
|
||||||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>%s - Mail-Graveyard</title>
|
<title>%s - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
<link rel="stylesheet" href="/static/style.css?v=20260713-5">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-22" defer></script></head>
|
<script src="/static/app.js?v=20260713-5" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
||||||
|
|
@ -890,9 +890,13 @@ func renderTransferPage(w http.ResponseWriter, r *http.Request, mode transferMod
|
||||||
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
|
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
|
||||||
<main class="transfer-workbench">
|
<main class="transfer-workbench">
|
||||||
<aside class="pane pane-tree" id="left-folders">%s</aside>
|
<aside class="pane pane-tree" id="left-folders">%s</aside>
|
||||||
|
<div class="pane-resizer" data-resize-index="0"></div>
|
||||||
<section class="pane pane-list" id="left-list">%s</section>
|
<section class="pane pane-list" id="left-list">%s</section>
|
||||||
|
<div class="pane-resizer" data-resize-index="1"></div>
|
||||||
<section class="pane pane-read" id="transfer-preview">%s</section>
|
<section class="pane pane-read" id="transfer-preview">%s</section>
|
||||||
|
<div class="pane-resizer" data-resize-index="2"></div>
|
||||||
<section class="pane pane-list" id="right-list">%s</section>
|
<section class="pane pane-list" id="right-list">%s</section>
|
||||||
|
<div class="pane-resizer" data-resize-index="3"></div>
|
||||||
<aside class="pane pane-tree" id="right-folders">%s</aside>
|
<aside class="pane pane-tree" id="right-folders">%s</aside>
|
||||||
</main>
|
</main>
|
||||||
<footer class="statusbar"><span id="mig-status" hx-get="/migrate/status" hx-trigger="load,every 3s" hx-swap="innerHTML">bereit</span></footer></body></html>`,
|
<footer class="statusbar"><span id="mig-status" hx-get="/migrate/status" hx-trigger="load,every 3s" hx-swap="innerHTML">bereit</span></footer></body></html>`,
|
||||||
|
|
@ -1313,9 +1317,9 @@ func renderSourceEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts [
|
||||||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Quell-Postfach - Mail-Graveyard</title>
|
<title>Quell-Postfach - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
<link rel="stylesheet" href="/static/style.css?v=20260713-5">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-22" defer></script></head>
|
<script src="/static/app.js?v=20260713-5" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
||||||
|
|
@ -1357,9 +1361,9 @@ func renderTargetEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts [
|
||||||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Ziel-Postfach - Mail-Graveyard</title>
|
<title>Ziel-Postfach - Mail-Graveyard</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
<link rel="stylesheet" href="/static/style.css?v=20260713-5">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
<script src="/static/app.js?v=20260712-22" defer></script></head>
|
<script src="/static/app.js?v=20260713-5" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
|
||||||
|
|
|
||||||
42
frontend-js/dist/app.js
vendored
42
frontend-js/dist/app.js
vendored
|
|
@ -1,8 +1,19 @@
|
||||||
(function () {
|
(function () {
|
||||||
const key = "mail-graveyard-pane-widths-v2";
|
|
||||||
const minWidths = [160, 220, 300];
|
|
||||||
const handleWidth = 6;
|
const handleWidth = 6;
|
||||||
|
|
||||||
|
function storageKey(container) {
|
||||||
|
if (container.classList.contains("transfer-workbench")) return "mail-graveyard-transfer-widths-v1";
|
||||||
|
if (container.classList.contains("four-pane")) return "mail-graveyard-four-pane-widths-v1";
|
||||||
|
return "mail-graveyard-pane-widths-v2";
|
||||||
|
}
|
||||||
|
|
||||||
|
function minWidths(container) {
|
||||||
|
const count = panes(container).length;
|
||||||
|
if (container.classList.contains("transfer-workbench")) return [220, 240, 320, 240, 220];
|
||||||
|
if (count === 4) return [160, 220, 280, 280];
|
||||||
|
return [160, 220, 300];
|
||||||
|
}
|
||||||
|
|
||||||
function panes(container) {
|
function panes(container) {
|
||||||
return Array.from(container.querySelectorAll(".pane"));
|
return Array.from(container.querySelectorAll(".pane"));
|
||||||
}
|
}
|
||||||
|
|
@ -12,13 +23,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampWidths(widths, total) {
|
function clampWidths(widths, total) {
|
||||||
const next = widths.map((w, i) => Math.max(minWidths[i], Math.round(w || minWidths[i])));
|
const mins = minWidths(this);
|
||||||
const max = Math.max(total - handleWidth * (next.length - 1), minWidths.reduce((a, b) => a + b, 0));
|
const next = widths.map((w, i) => Math.max(mins[i] || mins[mins.length - 1], Math.round(w || mins[i] || mins[mins.length - 1])));
|
||||||
|
const minTotal = mins.slice(0, next.length).reduce((a, b) => a + b, 0);
|
||||||
|
const max = Math.max(total - handleWidth * (next.length - 1), minTotal);
|
||||||
const sum = next.reduce((a, b) => a + b, 0);
|
const sum = next.reduce((a, b) => a + b, 0);
|
||||||
if (sum > max) {
|
if (sum > max) {
|
||||||
let extra = sum - max;
|
let extra = sum - max;
|
||||||
for (let i = next.length - 1; i >= 0 && extra > 0; i--) {
|
for (let i = next.length - 1; i >= 0 && extra > 0; i--) {
|
||||||
const canTake = Math.max(0, next[i] - minWidths[i]);
|
const canTake = Math.max(0, next[i] - (mins[i] || mins[mins.length - 1]));
|
||||||
const take = Math.min(canTake, extra);
|
const take = Math.min(canTake, extra);
|
||||||
next[i] -= take;
|
next[i] -= take;
|
||||||
extra -= take;
|
extra -= take;
|
||||||
|
|
@ -29,20 +42,24 @@
|
||||||
|
|
||||||
function applyWidths(container, widths) {
|
function applyWidths(container, widths) {
|
||||||
const total = container.getBoundingClientRect().width;
|
const total = container.getBoundingClientRect().width;
|
||||||
const w = clampWidths(widths, total);
|
const w = clampWidths.call(container, widths, total);
|
||||||
container.style.gridTemplateColumns =
|
const columns = [];
|
||||||
`${w[0]}px ${handleWidth}px ${w[1]}px ${handleWidth}px minmax(${w[2]}px, 1fr)`;
|
w.forEach((width, index) => {
|
||||||
localStorage.setItem(key, JSON.stringify(w));
|
columns.push(index === w.length - 1 ? `minmax(${width}px, 1fr)` : `${width}px`);
|
||||||
|
if (index < w.length - 1) columns.push(`${handleWidth}px`);
|
||||||
|
});
|
||||||
|
container.style.gridTemplateColumns = columns.join(" ");
|
||||||
|
localStorage.setItem(storageKey(container), JSON.stringify(w));
|
||||||
}
|
}
|
||||||
|
|
||||||
function restore(container) {
|
function restore(container) {
|
||||||
try {
|
try {
|
||||||
const saved = JSON.parse(localStorage.getItem(key) || "null");
|
const saved = JSON.parse(localStorage.getItem(storageKey(container)) || "null");
|
||||||
if (Array.isArray(saved) && saved.length === 3) {
|
if (Array.isArray(saved) && saved.length === panes(container).length) {
|
||||||
applyWidths(container, saved);
|
applyWidths(container, saved);
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(storageKey(container));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,6 +106,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => bind(document.querySelector(".transfer-workbench")));
|
||||||
window.addEventListener("DOMContentLoaded", () => bind(document.querySelector(".four-pane")));
|
window.addEventListener("DOMContentLoaded", () => bind(document.querySelector(".four-pane")));
|
||||||
window.addEventListener("DOMContentLoaded", () => bind(document.querySelector(".three-pane")));
|
window.addEventListener("DOMContentLoaded", () => bind(document.querySelector(".three-pane")));
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
2
frontend-js/dist/style.css
vendored
2
frontend-js/dist/style.css
vendored
|
|
@ -492,7 +492,7 @@ a.tree-node { display: block; color: inherit; text-decoration: none; }
|
||||||
.transfer-workbench {
|
.transfer-workbench {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 310px minmax(300px, 1fr) minmax(420px, 1.15fr) minmax(300px, 1fr) 310px;
|
grid-template-columns: 310px 6px minmax(300px, 1fr) 6px minmax(420px, 1.15fr) 6px minmax(300px, 1fr) 6px 310px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
.transfer-workbench .pane:last-child {
|
.transfer-workbench .pane:last-child {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue