mirror of
https://github.com/zadam/trilium.git
synced 2025-12-20 22:34:23 +01:00
feat(right_pane): simplify collapsing mechanism
This commit is contained in:
parent
7b04ca8cc7
commit
7af063e7cd
@ -1,5 +1,7 @@
|
|||||||
body.experimental-feature-new-layout #right-pane {
|
body.experimental-feature-new-layout #right-pane {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
margin-inline: 0;
|
margin-inline: 0;
|
||||||
@ -23,6 +25,10 @@ body.experimental-feature-new-layout #right-pane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card:not(.collapsed) {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.gutter-vertical + .card .card-header {
|
.gutter-vertical + .card .card-header {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,11 +12,6 @@ import HighlightsList from "./HighlightsList";
|
|||||||
import TableOfContents from "./TableOfContents";
|
import TableOfContents from "./TableOfContents";
|
||||||
|
|
||||||
const MIN_WIDTH_PERCENT = 5;
|
const MIN_WIDTH_PERCENT = 5;
|
||||||
const COLLAPSED_SIZE = 25;
|
|
||||||
|
|
||||||
export const RightPanelContext = createContext({
|
|
||||||
setExpanded(cardEl: HTMLElement, expanded: boolean) {}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function RightPanelContainer() {
|
export default function RightPanelContainer() {
|
||||||
// Split between right pane and the content pane.
|
// Split between right pane and the content pane.
|
||||||
@ -38,140 +33,11 @@ export default function RightPanelContainer() {
|
|||||||
<HighlightsList />
|
<HighlightsList />
|
||||||
];
|
];
|
||||||
|
|
||||||
// Split between items.
|
|
||||||
const innerSplitRef = useRef<Split.Instance>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
const rightPaneContainer = document.getElementById("right-pane");
|
|
||||||
const elements = Array.from(rightPaneContainer?.children ?? []) as HTMLElement[];
|
|
||||||
const splitInstance = Split(elements, {
|
|
||||||
direction: "vertical",
|
|
||||||
minSize: COLLAPSED_SIZE,
|
|
||||||
gutterSize: 4
|
|
||||||
});
|
|
||||||
innerSplitRef.current = splitInstance;
|
|
||||||
return () => splitInstance.destroy();
|
|
||||||
}, [ items ]);
|
|
||||||
|
|
||||||
const sizesBeforeCollapse = useRef(new WeakMap<HTMLElement, number>());
|
const sizesBeforeCollapse = useRef(new WeakMap<HTMLElement, number>());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="right-pane">
|
<div id="right-pane">
|
||||||
<RightPanelContext.Provider value={{
|
|
||||||
setExpanded(cardEl, expanded) {
|
|
||||||
const splitInstance = innerSplitRef.current;
|
|
||||||
if (!splitInstance) return;
|
|
||||||
|
|
||||||
const rightPaneEl = document.getElementById("right-pane");
|
|
||||||
const children = Array.from(rightPaneEl?.querySelectorAll(":scope > .card") ?? []);
|
|
||||||
const pos = children.indexOf(cardEl);
|
|
||||||
if (pos === -1) return;
|
|
||||||
|
|
||||||
const sizes = splitInstance.getSizes(); // percentages
|
|
||||||
const COLLAPSED_SIZE = 0; // keep your current behavior; consider a small min later
|
|
||||||
|
|
||||||
// Choose recipients/donors: nearest expanded panes first; if none, all except pos.
|
|
||||||
const recipients = getRecipientsByDistance(sizes, pos, COLLAPSED_SIZE);
|
|
||||||
const fallback = getExpandedIndices(sizes, pos, -Infinity); // all other panes
|
|
||||||
const targets = recipients.length ? recipients : fallback;
|
|
||||||
|
|
||||||
if (!expanded) {
|
|
||||||
const sizeBeforeCollapse = sizes[pos];
|
|
||||||
sizesBeforeCollapse.current.set(cardEl, sizeBeforeCollapse);
|
|
||||||
|
|
||||||
// Collapse
|
|
||||||
sizes[pos] = COLLAPSED_SIZE;
|
|
||||||
|
|
||||||
// Give freed space to other panes
|
|
||||||
const freed = sizeBeforeCollapse - COLLAPSED_SIZE;
|
|
||||||
distributeInto(sizes, targets, freed);
|
|
||||||
} else {
|
|
||||||
const want = sizesBeforeCollapse.current.get(cardEl) ?? 50;
|
|
||||||
|
|
||||||
// Take space back from other panes to expand this one
|
|
||||||
const took = takeFrom(sizes, targets, want);
|
|
||||||
|
|
||||||
sizes[pos] = COLLAPSED_SIZE + took; // if donors couldn't provide all, expand partially
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional: tiny cleanup to avoid negatives / floating drift
|
|
||||||
for (let i = 0; i < sizes.length; i++) sizes[i] = clamp(sizes[i], 0, 100);
|
|
||||||
|
|
||||||
// Normalize to sum to 100 (Split.js likes this)
|
|
||||||
const sum = sizes.reduce((a, b) => a + b, 0);
|
|
||||||
if (sum > 0) {
|
|
||||||
for (let i = 0; i < sizes.length; i++) sizes[i] = (sizes[i] / sum) * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
splitInstance.setSizes(sizes);
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{items}
|
{items}
|
||||||
</RightPanelContext.Provider>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExpandedIndices(sizes, skipIndex, COLLAPSED_SIZE) {
|
|
||||||
const idxs = [];
|
|
||||||
for (let i = 0; i < sizes.length; i++) {
|
|
||||||
if (i === skipIndex) continue;
|
|
||||||
if (sizes[i] > COLLAPSED_SIZE) idxs.push(i);
|
|
||||||
}
|
|
||||||
return idxs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer nearby panes (VS Code-ish). Falls back to "all expanded panes".
|
|
||||||
function getRecipientsByDistance(sizes, pos, COLLAPSED_SIZE) {
|
|
||||||
const recipients = [];
|
|
||||||
for (let d = 1; d < sizes.length; d++) {
|
|
||||||
const left = pos - d;
|
|
||||||
const right = pos + d;
|
|
||||||
if (left >= 0 && sizes[left] > COLLAPSED_SIZE) recipients.push(left);
|
|
||||||
if (right < sizes.length && sizes[right] > COLLAPSED_SIZE) recipients.push(right);
|
|
||||||
}
|
|
||||||
return recipients;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Distribute `amount` into `recipients` proportionally to their current sizes.
|
|
||||||
function distributeInto(sizes, recipients, amount) {
|
|
||||||
if (amount === 0 || recipients.length === 0) return;
|
|
||||||
const total = recipients.reduce((sum, i) => sum + sizes[i], 0);
|
|
||||||
if (total <= 0) {
|
|
||||||
// equal split fallback
|
|
||||||
const delta = amount / recipients.length;
|
|
||||||
recipients.forEach(i => (sizes[i] += delta));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
recipients.forEach(i => {
|
|
||||||
const share = (sizes[i] / total) * amount;
|
|
||||||
sizes[i] += share;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take `amount` out of `donors` proportionally, without driving anyone below 0.
|
|
||||||
// Returns how much was actually taken.
|
|
||||||
function takeFrom(sizes, donors, amount) {
|
|
||||||
if (amount <= 0 || donors.length === 0) return 0;
|
|
||||||
|
|
||||||
// max each donor can contribute (don’t go below 0 here; you can change min if you want)
|
|
||||||
const caps = donors.map(i => ({ i, cap: Math.max(0, sizes[i]) }));
|
|
||||||
let remaining = amount;
|
|
||||||
|
|
||||||
// iterative proportional take with caps
|
|
||||||
for (let iter = 0; iter < 5 && remaining > 1e-9; iter++) {
|
|
||||||
const active = caps.filter(x => x.cap > 1e-9);
|
|
||||||
if (active.length === 0) break;
|
|
||||||
|
|
||||||
const total = active.reduce((s, x) => s + sizes[x.i], 0) || active.length;
|
|
||||||
for (const x of active) {
|
|
||||||
const weight = total === active.length ? 1 / active.length : (sizes[x.i] / total);
|
|
||||||
const want = remaining * weight;
|
|
||||||
const took = Math.min(x.cap, want);
|
|
||||||
sizes[x.i] -= took;
|
|
||||||
x.cap -= took;
|
|
||||||
remaining -= took;
|
|
||||||
if (remaining <= 1e-9) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return amount - remaining;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { useContext, useRef, useState } from "preact/hooks";
|
|||||||
|
|
||||||
import Icon from "../react/Icon";
|
import Icon from "../react/Icon";
|
||||||
import { ParentComponent } from "../react/react_utils";
|
import { ParentComponent } from "../react/react_utils";
|
||||||
import { RightPanelContext } from "./RightPanelContainer";
|
|
||||||
|
|
||||||
interface RightPanelWidgetProps {
|
interface RightPanelWidgetProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -13,7 +12,6 @@ interface RightPanelWidgetProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RightPanelWidget({ title, buttons, children }: RightPanelWidgetProps) {
|
export default function RightPanelWidget({ title, buttons, children }: RightPanelWidgetProps) {
|
||||||
const rightPanelContext = useContext(RightPanelContext);
|
|
||||||
const [ expanded, setExpanded ] = useState(true);
|
const [ expanded, setExpanded ] = useState(true);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const parentComponent = useContext(ParentComponent);
|
const parentComponent = useContext(ParentComponent);
|
||||||
@ -31,9 +29,6 @@ export default function RightPanelWidget({ title, buttons, children }: RightPane
|
|||||||
<Icon
|
<Icon
|
||||||
icon="bx bx-chevron-down"
|
icon="bx bx-chevron-down"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (containerRef.current) {
|
|
||||||
rightPanelContext.setExpanded(containerRef.current, !expanded);
|
|
||||||
}
|
|
||||||
setExpanded(!expanded);
|
setExpanded(!expanded);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -42,9 +37,9 @@ export default function RightPanelWidget({ title, buttons, children }: RightPane
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id={parentComponent?.componentId} class="body-wrapper">
|
<div id={parentComponent?.componentId} class="body-wrapper">
|
||||||
<div class="card-body">
|
{expanded && <div class="card-body">
|
||||||
{expanded && children}
|
{children}
|
||||||
</div>
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user