Add resizable viewer panes
This commit is contained in:
parent
a4ea4c120e
commit
2bdf622257
3 changed files with 110 additions and 7 deletions
|
|
@ -41,7 +41,8 @@ func renderShell(w http.ResponseWriter, active, readingPane string) {
|
||||||
<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">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
<script src="/static/htmx.min.js"></script></head>
|
<script src="/static/htmx.min.js"></script>
|
||||||
|
<script src="/static/app.js" defer></script></head>
|
||||||
<body class="ol2013">
|
<body class="ol2013">
|
||||||
<header class="ribbon">
|
<header class="ribbon">
|
||||||
<nav class="ribbon-tabs">
|
<nav class="ribbon-tabs">
|
||||||
|
|
@ -57,13 +58,16 @@ func renderShell(w http.ResponseWriter, active, readingPane string) {
|
||||||
hx-get="/view" hx-trigger="load" hx-target="#tree" hx-swap="innerHTML">
|
hx-get="/view" hx-trigger="load" hx-target="#tree" hx-swap="innerHTML">
|
||||||
<div class="pane-head">Postfächer</div>
|
<div class="pane-head">Postfächer</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
<div class="pane-resizer" data-resize-index="0"></div>
|
||||||
<section class="pane pane-list" id="list">
|
<section class="pane pane-list" id="list">
|
||||||
<div class="pane-head">Nachrichten</div>
|
<div class="pane-head">Nachrichten</div>
|
||||||
</section>
|
</section>
|
||||||
|
<div class="pane-resizer" data-resize-index="1"></div>
|
||||||
<section class="pane pane-read" id="read">
|
<section class="pane pane-read" id="read">
|
||||||
<div class="pane-head">Original</div>
|
<div class="pane-head">Original</div>
|
||||||
<div class="read-body">%s</div>
|
<div class="read-body">%s</div>
|
||||||
</section>
|
</section>
|
||||||
|
<div class="pane-resizer" data-resize-index="2"></div>
|
||||||
<section class="pane pane-read pane-target" id="read-target">
|
<section class="pane pane-read pane-target" id="read-target">
|
||||||
%s
|
%s
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
93
frontend-js/dist/app.js
vendored
Normal file
93
frontend-js/dist/app.js
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
(function () {
|
||||||
|
const key = "mail-graveyard-pane-widths";
|
||||||
|
const minWidths = [160, 220, 260, 260];
|
||||||
|
const handleWidth = 6;
|
||||||
|
|
||||||
|
function panes(container) {
|
||||||
|
return Array.from(container.querySelectorAll(".pane"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function widthsFromDOM(container) {
|
||||||
|
return panes(container).map((pane) => Math.round(pane.getBoundingClientRect().width));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampWidths(widths, total) {
|
||||||
|
const next = widths.map((w, i) => Math.max(minWidths[i], Math.round(w || minWidths[i])));
|
||||||
|
const max = Math.max(total - handleWidth * 3, minWidths.reduce((a, b) => a + b, 0));
|
||||||
|
const sum = next.reduce((a, b) => a + b, 0);
|
||||||
|
if (sum > max) {
|
||||||
|
let extra = sum - max;
|
||||||
|
for (let i = next.length - 1; i >= 0 && extra > 0; i--) {
|
||||||
|
const canTake = Math.max(0, next[i] - minWidths[i]);
|
||||||
|
const take = Math.min(canTake, extra);
|
||||||
|
next[i] -= take;
|
||||||
|
extra -= take;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyWidths(container, widths) {
|
||||||
|
const total = container.getBoundingClientRect().width;
|
||||||
|
const w = clampWidths(widths, total);
|
||||||
|
container.style.gridTemplateColumns =
|
||||||
|
`${w[0]}px ${handleWidth}px ${w[1]}px ${handleWidth}px ${w[2]}px ${handleWidth}px ${w[3]}px`;
|
||||||
|
localStorage.setItem(key, JSON.stringify(w));
|
||||||
|
}
|
||||||
|
|
||||||
|
function restore(container) {
|
||||||
|
try {
|
||||||
|
const saved = JSON.parse(localStorage.getItem(key) || "null");
|
||||||
|
if (Array.isArray(saved) && saved.length === 4) {
|
||||||
|
applyWidths(container, saved);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind(container) {
|
||||||
|
if (!container || container.dataset.resizeBound === "1") return;
|
||||||
|
container.dataset.resizeBound = "1";
|
||||||
|
restore(container);
|
||||||
|
|
||||||
|
container.querySelectorAll(".pane-resizer").forEach((handle) => {
|
||||||
|
handle.addEventListener("pointerdown", (event) => {
|
||||||
|
if (window.matchMedia("(max-width: 820px)").matches) return;
|
||||||
|
event.preventDefault();
|
||||||
|
handle.setPointerCapture(event.pointerId);
|
||||||
|
handle.classList.add("dragging");
|
||||||
|
document.body.classList.add("resizing");
|
||||||
|
|
||||||
|
const index = Number(handle.dataset.resizeIndex || 0);
|
||||||
|
const startX = event.clientX;
|
||||||
|
const start = widthsFromDOM(container);
|
||||||
|
|
||||||
|
function move(moveEvent) {
|
||||||
|
const delta = moveEvent.clientX - startX;
|
||||||
|
const next = start.slice();
|
||||||
|
const left = Math.max(minWidths[index], start[index] + delta);
|
||||||
|
const used = left - start[index];
|
||||||
|
const right = Math.max(minWidths[index + 1], start[index + 1] - used);
|
||||||
|
const actual = start[index + 1] - right;
|
||||||
|
next[index] = start[index] + actual;
|
||||||
|
next[index + 1] = right;
|
||||||
|
applyWidths(container, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
function up(upEvent) {
|
||||||
|
handle.releasePointerCapture(upEvent.pointerId);
|
||||||
|
handle.classList.remove("dragging");
|
||||||
|
document.body.classList.remove("resizing");
|
||||||
|
window.removeEventListener("pointermove", move);
|
||||||
|
window.removeEventListener("pointerup", up);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("pointermove", move);
|
||||||
|
window.addEventListener("pointerup", up);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => bind(document.querySelector(".four-pane")));
|
||||||
|
})();
|
||||||
18
frontend-js/dist/style.css
vendored
18
frontend-js/dist/style.css
vendored
|
|
@ -56,13 +56,22 @@ body.ol2013 {
|
||||||
min-height: 0; /* erlaubt Scrollen in den Panes */
|
min-height: 0; /* erlaubt Scrollen in den Panes */
|
||||||
}
|
}
|
||||||
.three-pane { grid-template-columns: 220px 340px 1fr; }
|
.three-pane { grid-template-columns: 220px 340px 1fr; }
|
||||||
.four-pane { grid-template-columns: 220px 340px minmax(280px, 1fr) minmax(280px, 1fr); }
|
.four-pane { grid-template-columns: 220px 6px 340px 6px minmax(280px, 1fr) 6px minmax(280px, 1fr); }
|
||||||
.pane {
|
.pane {
|
||||||
border-right: 1px solid var(--ol-border);
|
border-right: 1px solid var(--ol-border);
|
||||||
background: var(--ol-pane);
|
background: var(--ol-pane);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
.pane-resizer {
|
||||||
|
background: #f7f7f7;
|
||||||
|
border-right: 1px solid var(--ol-border);
|
||||||
|
cursor: col-resize;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.pane-resizer:hover,
|
||||||
|
.pane-resizer.dragging { background: var(--ol-sel-bd); }
|
||||||
|
body.resizing { cursor: col-resize; user-select: none; }
|
||||||
.pane-tree { background: var(--ol-tree); }
|
.pane-tree { background: var(--ol-tree); }
|
||||||
.pane-target { border-right: 0; }
|
.pane-target { border-right: 0; }
|
||||||
.pane-head {
|
.pane-head {
|
||||||
|
|
@ -163,11 +172,7 @@ a.tree-node { display: block; color: inherit; text-decoration: none; }
|
||||||
|
|
||||||
@media (max-width: 1180px) {
|
@media (max-width: 1180px) {
|
||||||
.four-pane {
|
.four-pane {
|
||||||
grid-template-columns: 200px 320px minmax(300px, 1fr);
|
grid-template-columns: 200px 6px 320px 6px minmax(280px, 1fr) 6px minmax(280px, 1fr);
|
||||||
}
|
|
||||||
.pane-target {
|
|
||||||
grid-column: 3;
|
|
||||||
border-top: 1px solid var(--ol-border);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,6 +187,7 @@ a.tree-node { display: block; color: inherit; text-decoration: none; }
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
border-bottom: 1px solid var(--ol-border);
|
border-bottom: 1px solid var(--ol-border);
|
||||||
}
|
}
|
||||||
|
.pane-resizer { display: none; }
|
||||||
.pane-target { grid-column: auto; }
|
.pane-target { grid-column: auto; }
|
||||||
.searchbar { top: 30px; }
|
.searchbar { top: 30px; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue