文件
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

168 行
5.4 KiB
TypeScript

import { produce } from 'immer';
import iterateItems from './iterateItems';
import { LayoutItem, LayoutItemDirection } from './types';
// Fallback size assigned to a non-absorber sibling that has none, so the
// absorber stays the only width/height-less child of its container.
const itemMinWidthFallback = 250;
const itemMinHeightFallback = 250;
function isLastVisible(itemIndex: number, item: LayoutItem, parent: LayoutItem) {
if (item.visible === false) return false;
for (let i = parent.children.length - 1; i >= 0; i--) {
const child = parent.children[i];
if (child && child.visible !== false) return i === itemIndex;
}
return false;
}
// A subtree counts as flexible if it carries the flag at any nested level, so
// the absorber role propagates up to the root container.
function containsFlexible(item: LayoutItem): boolean {
if (item.visible === false) return false;
if (item.flexible) return true;
if (!item.children) return false;
for (const child of item.children) {
if (containsFlexible(child)) return true;
}
return false;
}
function explicitAbsorberIndex(parent: LayoutItem) {
if (!parent.children) return -1;
for (let i = 0; i < parent.children.length; i++) {
const child = parent.children[i];
if (child.visible === false) continue;
if (containsFlexible(child)) return i;
}
return -1;
}
function updateItemSize(itemIndex: number, itemDraft: LayoutItem, parent: LayoutItem) {
if (!parent) return;
// If a container has only one child, this child should not
// have a width and height, and simply fill up the container
if (parent.children.length === 1) {
delete itemDraft.width;
delete itemDraft.height;
}
const explicitIdx = explicitAbsorberIndex(parent);
if (explicitIdx !== -1) {
// Strip the absorber's size and assign fallback sizes to any sibling
// that lacks one, so the absorber alone fills remaining space.
if (explicitIdx !== itemIndex || itemDraft.visible === false) return;
if (parent.direction === LayoutItemDirection.Row) {
delete itemDraft.width;
for (let i = 0; i < parent.children.length; i++) {
if (i === itemIndex) continue;
const child = parent.children[i];
if (child.visible === false) continue;
if (!('width' in child)) {
(child as { width?: number }).width = child.minWidth || itemMinWidthFallback;
}
}
} else {
delete itemDraft.height;
for (let i = 0; i < parent.children.length; i++) {
if (i === itemIndex) continue;
const child = parent.children[i];
if (child.visible === false) continue;
if (!('height' in child)) {
(child as { height?: number }).height = child.minHeight || itemMinHeightFallback;
}
}
}
return;
}
// No explicit absorber: legacy fallback makes the last visible child
// flexible when every other sibling is already sized.
if (isLastVisible(itemIndex, itemDraft, parent)) {
let allChildrenAreSized = true;
for (const child of parent.children) {
if (child.visible === false) continue;
if (parent.direction === LayoutItemDirection.Row) {
if (!child.width) { allChildrenAreSized = false; break; }
} else {
if (!child.height) { allChildrenAreSized = false; break; }
}
}
if (allChildrenAreSized) {
if (parent.direction === LayoutItemDirection.Row) {
delete itemDraft.width;
} else {
delete itemDraft.height;
}
}
}
}
// All items get a draggable trailing edge except the last visible child,
// whose trailing edge is the container's edge.
function updateResizeRules(itemIndex: number, itemDraft: LayoutItem, parent: LayoutItem) {
if (!parent) return;
const isLastVisibleChild = isLastVisible(itemIndex, itemDraft, parent);
itemDraft.resizableRight = parent.direction === LayoutItemDirection.Row && !isLastVisibleChild;
itemDraft.resizableBottom = parent.direction === LayoutItemDirection.Column && !isLastVisibleChild;
}
// Container direction should alternate between row (for the root) and
// columns, then rows again.
function updateDirection(_itemIndex: number, itemDraft: LayoutItem, parent: LayoutItem) {
if (!parent) {
itemDraft.direction = LayoutItemDirection.Row;
} else {
itemDraft.direction = parent.direction === LayoutItemDirection.Row ? LayoutItemDirection.Column : LayoutItemDirection.Row;
}
}
function itemShouldBeVisible(item: LayoutItem): boolean {
if (!item.children) return item.visible !== false;
let oneIsVisible = false;
for (const child of item.children) {
if (itemShouldBeVisible(child)) {
oneIsVisible = true;
break;
}
}
return oneIsVisible;
}
// If all children of a container are hidden, the container should be
// hidden too. A container visibility cannot be changed by the user.
function updateContainerVisibility(_itemIndex: number, itemDraft: LayoutItem, _parent: LayoutItem) {
if (itemDraft.children) {
itemDraft.visible = itemShouldBeVisible(itemDraft);
} else {
itemDraft.visible = itemDraft.visible !== false;
}
}
export default function validateLayout(layout: LayoutItem): LayoutItem {
if (!layout) throw new Error('Layout is null');
if (!layout.children || !layout.children.length) throw new Error('Root does not have children');
return produce(layout, (draft: LayoutItem) => {
draft.isRoot = true;
iterateItems(draft, (itemIndex: number, itemDraft: LayoutItem, parent: LayoutItem) => {
updateItemSize(itemIndex, itemDraft, parent);
updateResizeRules(itemIndex, itemDraft, parent);
updateDirection(itemIndex, itemDraft, parent);
updateContainerVisibility(itemIndex, itemDraft, parent);
return true;
});
});
}