Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add layout priority prop #267

Merged
merged 1 commit into from
May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ Maximum size of this pane. Overrides `maxSize` set on parent component.

Minimum size of this pane. Overrides `minSize` set on parent component.

### priority

The priority of the pane when the layout algorithm runs. Panes with higher priority will be resized first.

Only used when `proportionalLayout` is false.

### preferredSize

Preferred size of this pane. Allotment will attempt to use this size when adding this pane (including on initial mount) as well as when a user double clicks a sash, or the `reset` method is called on the Allotment instance.
Expand Down
11 changes: 10 additions & 1 deletion src/allotment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import { isIOS } from "./helpers/platform";
import { LayoutService } from "./layout-service";
import { PaneView } from "./pane-view";
import { Orientation, setGlobalSashSize } from "./sash";
import { Sizing, SplitView, SplitViewOptions } from "./split-view";
import {
LayoutPriority,
Sizing,
SplitView,
SplitViewOptions,
} from "./split-view";

function isPane(item: React.ReactNode): item is typeof Pane {
return (item as any).type.displayName === "Allotment.Pane";
Expand All @@ -29,6 +34,7 @@ function isPaneProps(props: AllotmentProps | PaneProps): props is PaneProps {
(props as PaneProps).minSize !== undefined ||
(props as PaneProps).maxSize !== undefined ||
(props as PaneProps).preferredSize !== undefined ||
(props as PaneProps).priority !== undefined ||
(props as PaneProps).visible !== undefined
);
}
Expand All @@ -47,6 +53,7 @@ export interface CommonProps {
export type PaneProps = {
children: React.ReactNode;
preferredSize?: number | string;
priority?: LayoutPriority;
visible?: boolean;
} & CommonProps;

Expand Down Expand Up @@ -194,6 +201,7 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
element: document.createElement("div"),
minimumSize: props?.minSize ?? minSize,
maximumSize: props?.maxSize ?? maxSize,
priority: props?.priority ?? LayoutPriority.Normal,
...(props?.preferredSize && {
preferredSize: props?.preferredSize,
}),
Expand Down Expand Up @@ -288,6 +296,7 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
element: document.createElement("div"),
minimumSize: props?.minSize ?? minSize,
maximumSize: props?.maxSize ?? maxSize,
priority: props?.priority ?? LayoutPriority.Normal,
...(props?.preferredSize && {
preferredSize: props?.preferredSize,
}),
Expand Down
12 changes: 12 additions & 0 deletions src/helpers/array.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/**
* Pushes an element to the start of the array, if found.
*/
export function pushToStart<T>(arr: T[], value: T): void {
const index = arr.indexOf(value);

if (index > -1) {
arr.splice(index, 1);
arr.unshift(value);
}
}

/**
* Pushes an element to the end of the array, if found.
*/
Expand Down
6 changes: 5 additions & 1 deletion src/pane-view/pane-view.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { endsWith } from "../helpers/string";
import { LayoutService } from "../layout-service";
import { View } from "../split-view";
import { LayoutPriority, View } from "../split-view";

export interface Layout {
getPreferredSize: () => number | undefined;
Expand Down Expand Up @@ -42,6 +42,7 @@ export interface PaneViewOptions {
element: HTMLElement;
minimumSize?: number;
maximumSize?: number;
priority?: LayoutPriority;
preferredSize?: number | string;
snap?: boolean;
}
Expand All @@ -51,6 +52,7 @@ export class PaneView implements View {
public maximumSize: number = Number.POSITIVE_INFINITY;

readonly element: HTMLElement;
readonly priority?: LayoutPriority | undefined;
readonly snap: boolean;

private layoutService: LayoutService;
Expand Down Expand Up @@ -128,6 +130,8 @@ export class PaneView implements View {
this.layoutStrategy = new NullLayout();
}

this.priority = options.priority ?? LayoutPriority.Normal;

this.snap = typeof options.snap === "boolean" ? options.snap : false;
}

Expand Down
91 changes: 79 additions & 12 deletions src/split-view/split-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import EventEmitter from "eventemitter3";
import clamp from "lodash.clamp";

import styles from "../allotment.module.css";
import { pushToEnd, range } from "../helpers/array";
import { pushToEnd, pushToStart, range } from "../helpers/array";
import { Disposable } from "../helpers/disposable";
import { endsWith } from "../helpers/string";
import {
Orientation,
Sash,
Expand Down Expand Up @@ -101,6 +100,12 @@ export interface SplitViewOptions {
readonly getSashOrthogonalSize?: () => number;
}

export const enum LayoutPriority {
Normal = "NORMAL",
Low = "LOW",
High = "HIGH",
}

/**
* The interface to implement for views within a {@link SplitView}.
*/
Expand All @@ -122,6 +127,14 @@ export interface View {
*/
readonly maximumSize: number;

/**
* The priority of the view when the {@link SplitView.resize layout} algorithm
* runs. Views with higher priority will be resized first.
*
* @remarks Only used when `proportionalLayout` is false.
*/
readonly priority?: LayoutPriority;

/**
* Whether the view will snap whenever the user reaches its minimum size or
* attempts to grow it beyond the minimum size.
Expand Down Expand Up @@ -174,9 +187,7 @@ export class PaneView implements View {
this.snap = typeof options.snap === "boolean" ? options.snap : false;
}

layout(size: number): void {
//console.log(size);
}
layout(_size: number): void {}
}

type ViewItemSize = number | { cachedVisibleSize: number };
Expand Down Expand Up @@ -211,6 +222,10 @@ abstract class ViewItem {
return this._size;
}

get priority(): LayoutPriority | undefined {
return this.view.priority;
}

get snap(): boolean {
return !!this.view.snap;
}
Expand Down Expand Up @@ -607,7 +622,23 @@ export class SplitView extends EventEmitter implements Disposable {
this.size = size;

if (!this.proportions) {
this.resize(this.viewItems.length - 1, size - previousSize, undefined);
const indexes = range(0, this.viewItems.length);

const lowPriorityIndexes = indexes.filter(
(i) => this.viewItems[i].priority === LayoutPriority.Low
);

const highPriorityIndexes = indexes.filter(
(i) => this.viewItems[i].priority === LayoutPriority.High
);

this.resize(
this.viewItems.length - 1,
size - previousSize,
undefined,
lowPriorityIndexes,
highPriorityIndexes
);
} else {
for (let i = 0; i < this.viewItems.length; i++) {
const item = this.viewItems[i];
Expand Down Expand Up @@ -635,14 +666,25 @@ export class SplitView extends EventEmitter implements Disposable {
return;
}

const lowPriorityIndexes = [index];
const indexes = range(0, this.viewItems.length).filter((i) => i !== index);

const lowPriorityIndexes = [
...indexes.filter(
(i) => this.viewItems[i].priority === LayoutPriority.Low
),
index,
];

const highPriorityIndexes = indexes.filter(
(i) => this.viewItems[i].priority === LayoutPriority.High
);

const item = this.viewItems[index];
size = Math.round(size);
size = clamp(size, item.minimumSize, Math.min(item.maximumSize, this.size));

item.size = size;
this.relayout(lowPriorityIndexes);
this.relayout(lowPriorityIndexes, highPriorityIndexes);
}

public resizeViews(sizes: number[]): void {
Expand Down Expand Up @@ -730,7 +772,17 @@ export class SplitView extends EventEmitter implements Disposable {
item.size = clamp(size, item.minimumSize, item.maximumSize);
}

this.relayout();
const indexes = range(0, this.viewItems.length);

const lowPriorityIndexes = indexes.filter(
(i) => this.viewItems[i].priority === LayoutPriority.Low
);

const highPriorityIndexes = indexes.filter(
(i) => this.viewItems[i].priority === LayoutPriority.High
);

this.relayout(lowPriorityIndexes, highPriorityIndexes);
}

public dispose(): void {
Expand All @@ -740,14 +792,18 @@ export class SplitView extends EventEmitter implements Disposable {
this.sashContainer.remove();
}

private relayout(lowPriorityIndexes?: number[]): void {
private relayout(
lowPriorityIndexes?: number[],
highPriorityIndexes?: number[]
): void {
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);

this.resize(
this.viewItems.length - 1,
this.size - contentSize,
undefined,
lowPriorityIndexes
lowPriorityIndexes,
highPriorityIndexes
);

this.distributeEmptySpace();
Expand Down Expand Up @@ -851,11 +907,13 @@ export class SplitView extends EventEmitter implements Disposable {

const delta = current - start;

// TODO: Should this be conditional on alt?
this.resize(
index,
delta,
sizes,
undefined,
undefined,
minDelta,
maxDelta,
snapBefore,
Expand Down Expand Up @@ -894,6 +952,7 @@ export class SplitView extends EventEmitter implements Disposable {
delta: number,
sizes = this.viewItems.map((i) => i.size),
lowPriorityIndexes?: number[],
highPriorityIndexes?: number[],
overloadMinDelta: number = Number.NEGATIVE_INFINITY,
overloadMaxDelta: number = Number.POSITIVE_INFINITY,
snapBefore?: SashDragSnapState,
Expand All @@ -906,6 +965,13 @@ export class SplitView extends EventEmitter implements Disposable {
const upIndexes = range(index, -1, -1);
const downIndexes = range(index + 1, this.viewItems.length);

if (highPriorityIndexes) {
for (const index of highPriorityIndexes) {
pushToStart(upIndexes, index);
pushToStart(downIndexes, index);
}
}

if (lowPriorityIndexes) {
for (const index of lowPriorityIndexes) {
pushToEnd(upIndexes, index);
Expand Down Expand Up @@ -969,7 +1035,8 @@ export class SplitView extends EventEmitter implements Disposable {
index,
delta,
sizes,
undefined,
lowPriorityIndexes,
highPriorityIndexes,
overloadMinDelta,
overloadMaxDelta
);
Expand Down
11 changes: 9 additions & 2 deletions stories/advanced.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Meta, Story } from "@storybook/react";
import { useState } from "react";

import { Allotment } from "../src";
import { LayoutPriority } from "../src/split-view";
import styles from "./advanced.stories.module.css";
import { ActivityBar } from "./components/activity-bar";
import { AuxiliaryBar } from "./components/auxiliary-bar";
Expand Down Expand Up @@ -62,6 +63,7 @@ export const VisualStudioCode: Story = ({
<Allotment.Pane
key="sidebar"
minSize={170}
priority={LayoutPriority.Low}
preferredSize={300}
visible={primarySideBar}
snap
Expand All @@ -81,6 +83,7 @@ export const VisualStudioCode: Story = ({
<Allotment.Pane
key="auxiliarySidebar"
minSize={170}
priority={LayoutPriority.Low}
preferredSize={300}
visible={secondarySideBar}
snap
Expand All @@ -91,7 +94,7 @@ export const VisualStudioCode: Story = ({

return (
<div className={styles.container}>
<Allotment>
<Allotment proportionalLayout={false}>
<Allotment.Pane
key="activityBar"
minSize={48}
Expand All @@ -113,7 +116,11 @@ export const VisualStudioCode: Story = ({
/>
</Allotment.Pane>
{primarySideBarPosition === "left" ? sidebar : auxiliarySidebar}
<Allotment.Pane key="content" minSize={300}>
<Allotment.Pane
key="content"
minSize={300}
priority={LayoutPriority.High}
>
<Allotment
vertical
snap
Expand Down
Loading