Skip to content

Commit

Permalink
feat: add layout priority prop
Browse files Browse the repository at this point in the history
  • Loading branch information
johnwalley committed May 3, 2022
1 parent d3c28cd commit d0ccf47
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 23 deletions.
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

0 comments on commit d0ccf47

Please sign in to comment.