mirror of
https://github.com/n8n-io/n8n.git
synced 2026-08-29 01:39:24 +08:00
fix(editor): Keep NDV panels spanning the full container width (#35818)
This commit is contained in:
+66
-2
@@ -51,8 +51,9 @@ describe('useNdvLayout', () => {
|
||||
localStorage.setItem(key, JSON.stringify({ left: 0, main: 5, right: 0 }));
|
||||
|
||||
const { panelWidthPercentage } = useNdvLayout({ container, hasInputPanel, paneType });
|
||||
expect(panelWidthPercentage.value.left).toBeCloseTo(12);
|
||||
expect(panelWidthPercentage.value.right).toBeCloseTo(12);
|
||||
expect(panelWidthPercentage.value.left).toBeGreaterThanOrEqual(12);
|
||||
expect(panelWidthPercentage.value.right).toBeGreaterThanOrEqual(12);
|
||||
expect(panelWidthPercentage.value.main).toBeCloseTo(36.8);
|
||||
});
|
||||
|
||||
it('updates layout on resize (left)', () => {
|
||||
@@ -109,4 +110,67 @@ describe('useNdvLayout', () => {
|
||||
expect(panelWidthPercentage.value.main).toBeCloseTo(42);
|
||||
expect(panelWidthPercentage.value.right).toBeCloseTo(29);
|
||||
});
|
||||
|
||||
describe('when the stored layout cannot be used as-is', () => {
|
||||
const totalOf = ({ left, main, right }: { left: number; main: number; right: number }) =>
|
||||
left + main + right;
|
||||
|
||||
it('spans the full container when stored values fall below the minimums', () => {
|
||||
containerWidth.value = 1317;
|
||||
const key = `${LOCAL_STORAGE_NDV_PANEL_WIDTH}_REGULAR`;
|
||||
localStorage.setItem(key, JSON.stringify({ left: 1, main: 1, right: 1 }));
|
||||
|
||||
const { panelWidthPercentage } = useNdvLayout({ container, hasInputPanel, paneType });
|
||||
|
||||
// Minimums alone only add up to 46% of the container, leaving the canvas visible behind.
|
||||
expect(totalOf(panelWidthPercentage.value)).toBeCloseTo(100);
|
||||
expect(panelWidthPercentage.value.left).toBeGreaterThanOrEqual((120 / 1317) * 100);
|
||||
expect(panelWidthPercentage.value.right).toBeGreaterThanOrEqual((120 / 1317) * 100);
|
||||
expect(panelWidthPercentage.value.main).toBeGreaterThanOrEqual((368 / 1317) * 100);
|
||||
});
|
||||
|
||||
it('falls back to the defaults when the stored value is not usable', () => {
|
||||
containerWidth.value = 1317;
|
||||
const key = `${LOCAL_STORAGE_NDV_PANEL_WIDTH}_REGULAR`;
|
||||
localStorage.setItem(key, JSON.stringify({ left: null, main: null, right: null }));
|
||||
|
||||
const { panelWidthPercentage } = useNdvLayout({ container, hasInputPanel, paneType });
|
||||
|
||||
expect(panelWidthPercentage.value.main).toBeCloseTo((420 / 1317) * 100);
|
||||
expect(totalOf(panelWidthPercentage.value)).toBeCloseTo(100);
|
||||
});
|
||||
|
||||
it('keeps a usable layout while the container is unmeasured', () => {
|
||||
containerWidth.value = 0;
|
||||
|
||||
const { panelWidthPercentage } = useNdvLayout({ container, hasInputPanel, paneType });
|
||||
|
||||
expect(Object.values(panelWidthPercentage.value).every(Number.isFinite)).toBe(true);
|
||||
expect(totalOf(panelWidthPercentage.value)).toBeCloseTo(100);
|
||||
});
|
||||
|
||||
it('does not persist while the container is unmeasured', () => {
|
||||
containerWidth.value = 0;
|
||||
const spy = vi.spyOn(Storage.prototype, 'setItem');
|
||||
|
||||
const { onResizeEnd } = useNdvLayout({ container, hasInputPanel, paneType });
|
||||
onResizeEnd();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('keeps the left panel collapsed for "inputless" layouts', () => {
|
||||
containerWidth.value = 1317;
|
||||
hasInputPanel.value = false;
|
||||
paneType.value = 'inputless';
|
||||
const key = `${LOCAL_STORAGE_NDV_PANEL_WIDTH}_INPUTLESS`;
|
||||
localStorage.setItem(key, JSON.stringify({ left: 0, main: 1, right: 1 }));
|
||||
|
||||
const { panelWidthPercentage } = useNdvLayout({ container, hasInputPanel, paneType });
|
||||
|
||||
expect(panelWidthPercentage.value.left).toBe(0);
|
||||
expect(totalOf(panelWidthPercentage.value)).toBeCloseTo(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,6 +72,12 @@ export function useNdvLayout(options: UseNdvLayoutOptions) {
|
||||
}
|
||||
});
|
||||
|
||||
const isUsablePanelSize = (size: NdvPanelsSize | null | undefined): size is NdvPanelsSize =>
|
||||
!!size &&
|
||||
Number.isFinite(size.left) &&
|
||||
Number.isFinite(size.main) &&
|
||||
Number.isFinite(size.right);
|
||||
|
||||
const safePanelWidth = ({ left, main, right }: { left: number; main: number; right: number }) => {
|
||||
const hasInput = toValue(options.hasInputPanel);
|
||||
const minLeft = hasInput ? minPanelWidthPercentage.value : 0;
|
||||
@@ -85,32 +91,41 @@ export function useNdvLayout(options: UseNdvLayoutOptions) {
|
||||
};
|
||||
|
||||
const total = newPanelWidth.left + newPanelWidth.main + newPanelWidth.right;
|
||||
const sides = newPanelWidth.left + newPanelWidth.right;
|
||||
|
||||
if (total > 100) {
|
||||
const overflow = total - 100;
|
||||
// Panels must always span the container: distribute any difference across the
|
||||
// side panels, otherwise a short total leaves the canvas showing through.
|
||||
if (total !== 100 && sides > 0) {
|
||||
const diff = 100 - total;
|
||||
const leftShare = newPanelWidth.left / sides;
|
||||
|
||||
const trimLeft = (newPanelWidth.left / (newPanelWidth.left + newPanelWidth.right)) * overflow;
|
||||
const trimRight = overflow - trimLeft;
|
||||
|
||||
newPanelWidth.left = Math.max(minLeft, newPanelWidth.left - trimLeft);
|
||||
newPanelWidth.right = Math.max(minRight, newPanelWidth.right - trimRight);
|
||||
newPanelWidth.left = Math.max(minLeft, newPanelWidth.left + diff * leftShare);
|
||||
newPanelWidth.right = Math.max(minRight, newPanelWidth.right + diff * (1 - leftShare));
|
||||
}
|
||||
|
||||
return newPanelWidth;
|
||||
};
|
||||
|
||||
const persistPanelSize = () => {
|
||||
// Before the container is measured the sizes are placeholders, not something
|
||||
// the user chose — persisting them would overwrite their actual layout.
|
||||
if (!containerWidth.value || !isUsablePanelSize(panelWidthPercentage.value)) return;
|
||||
|
||||
localStorage.setItem(localStorageKey.value, JSON.stringify(panelWidthPercentage.value));
|
||||
};
|
||||
|
||||
const loadPanelSize = () => {
|
||||
if (!containerWidth.value) return;
|
||||
|
||||
const storedPanelSizeString = localStorage.getItem(localStorageKey.value);
|
||||
const defaultSize = defaultPanelSize.value;
|
||||
if (storedPanelSizeString) {
|
||||
const storedPanelSize = jsonParse<NdvPanelsSize>(storedPanelSizeString, {
|
||||
fallbackValue: defaultSize,
|
||||
});
|
||||
panelWidthPercentage.value = safePanelWidth(storedPanelSize ?? defaultSize);
|
||||
panelWidthPercentage.value = safePanelWidth(
|
||||
isUsablePanelSize(storedPanelSize) ? storedPanelSize : defaultSize,
|
||||
);
|
||||
} else {
|
||||
panelWidthPercentage.value = safePanelWidth(defaultSize);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user