Skip to content

Commit

Permalink
feat: something that sort of works
Browse files Browse the repository at this point in the history
  • Loading branch information
johnwalley committed Apr 9, 2022
1 parent 737f442 commit e558c4e
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 52 deletions.
134 changes: 86 additions & 48 deletions src/allotment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ import React, {
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import useResizeObserver from "use-resize-observer";

import styles from "./allotment.module.css";
import { isIOS } from "./helpers/platform";
import { Orientation, setGlobalSashSize } from "./sash";
import { Sizing, SplitView, SplitViewOptions } from "./split-view/split-view";
import {
LayoutService,
PaneView,
Sizing,
SplitView,
SplitViewOptions,
} from "./split-view/split-view";

function isPane(item: React.ReactNode): item is typeof Pane {
return (item as any).type.displayName === "Allotment.Pane";
Expand All @@ -39,7 +46,7 @@ export interface CommonProps {

export type PaneProps = {
children: React.ReactNode;
preferredSize?: number;
preferredSize?: number | string;
visible?: boolean;
} & CommonProps;

Expand Down Expand Up @@ -106,6 +113,10 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
const splitViewPropsRef = useRef(new Map<React.Key, PaneProps>());
const splitViewRef = useRef<SplitView | null>(null);
const splitViewViewRef = useRef(new Map<React.Key, HTMLElement>());
const layoutService = useRef<LayoutService>(new LayoutService());
const views = useRef<PaneView[]>([]);

const [dimensionsInitialized, setDimensionsInitialized] = useState(false);

if (process.env.NODE_ENV !== "production" && sizes) {
console.warn(
Expand Down Expand Up @@ -159,16 +170,20 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
previousKeys.current[index]
);

const view = new PaneView(layoutService.current, {
element: document.createElement("div"),
minimumSize: props?.minSize ?? minSize,
maximumSize: props?.maxSize ?? maxSize,
...(props?.preferredSize && {
preferredSize: props?.preferredSize,
}),
snap: props?.snap ?? snap,
});

return {
container: [...splitViewViewRef.current.values()][index],
size: size,
view: {
element: document.createElement("div"),
minimumSize: props?.minSize ?? minSize,
maximumSize: props?.maxSize ?? maxSize,
snap: props?.snap ?? snap,
layout: () => {},
},
view: view,
};
}),
},
Expand Down Expand Up @@ -204,18 +219,16 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
if (onReset) {
onReset();
} else {
const keys = childrenArray.map((child) => child.key as string);

const resizeToPreferredSize = (index: number): boolean => {
const props = splitViewPropsRef.current.get(keys[index]);
const view = views.current?.[index];

if (typeof props?.preferredSize !== "number") {
if (typeof view?.preferredSize !== "number") {
return false;
}

splitViewRef.current?.resizeView(
index,
Math.round(props.preferredSize)
Math.round(view.preferredSize)
);

return true;
Expand All @@ -229,8 +242,6 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
return;
}

console.log("distributeViewSizes");

splitViewRef.current?.distributeViewSizes();
}
});
Expand All @@ -247,58 +258,85 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
* Add, remove or update views as children change
*/
useEffect(() => {
const keys = childrenArray.map((child) => child.key as string);
if (dimensionsInitialized) {
const keys = childrenArray.map((child) => child.key as string);

const enter = keys.filter((key) => !previousKeys.current.includes(key));
const update = keys.filter((key) => previousKeys.current.includes(key));
const exit = previousKeys.current.map((key) => !keys.includes(key));
const enter = keys.filter((key) => !previousKeys.current.includes(key));
const update = keys.filter((key) => previousKeys.current.includes(key));
const exit = previousKeys.current.map((key) => !keys.includes(key));

exit.forEach((flag, index) => {
if (flag) {
splitViewRef.current?.removeView(index);
}
});
exit.forEach((flag, index) => {
if (flag) {
splitViewRef.current?.removeView(index);
views.current.splice(index, 1);
}
});

for (const enterKey of enter) {
const props = splitViewPropsRef.current.get(enterKey);
for (const enterKey of enter) {
const props = splitViewPropsRef.current.get(enterKey);

splitViewRef.current?.addView(
splitViewViewRef.current.get(enterKey)!,
{
const view = new PaneView(layoutService.current, {
element: document.createElement("div"),
minimumSize: props?.minSize ?? minSize,
maximumSize: props?.maxSize ?? maxSize,
...(props?.preferredSize && {
preferredSize: props?.preferredSize,
}),
snap: props?.snap ?? snap,
layout: () => {},
},
Sizing.Distribute,
keys.findIndex((key) => key === enterKey)
);
}
});

splitViewRef.current?.addView(
splitViewViewRef.current.get(enterKey)!,
view,
Sizing.Distribute,
keys.findIndex((key) => key === enterKey)
);

views.current.splice(
keys.findIndex((key) => key === enterKey),
0,
view
);
}

for (const enterKey of enter) {
const index = keys.findIndex((key) => key === enterKey);

const preferredSize = views.current[index].preferredSize;

if (preferredSize !== undefined) {
splitViewRef.current?.resizeView(index, preferredSize);
}
}

for (const updateKey of [...enter, ...update]) {
const props = splitViewPropsRef.current.get(updateKey);
const index = keys.findIndex((key) => key === updateKey);
for (const updateKey of [...enter, ...update]) {
const props = splitViewPropsRef.current.get(updateKey);
const index = keys.findIndex((key) => key === updateKey);

if (props && isPaneProps(props)) {
if (props.visible !== undefined) {
if (splitViewRef.current?.isViewVisible(index) !== props.visible) {
splitViewRef.current?.setViewVisible(index, props.visible);
if (props && isPaneProps(props)) {
if (props.visible !== undefined) {
if (
splitViewRef.current?.isViewVisible(index) !== props.visible
) {
splitViewRef.current?.setViewVisible(index, props.visible);
}
}
}
}
}

if (enter.length > 0 || exit.length > 0) {
previousKeys.current = keys;
if (enter.length > 0 || exit.length > 0) {
previousKeys.current = keys;
}
}
}, [childrenArray, maxSize, minSize, snap]);
}, [childrenArray, dimensionsInitialized, maxSize, minSize, snap]);

useResizeObserver({
ref: containerRef,
onResize: ({ width, height }) => {
if (width && height) {
splitViewRef.current?.layout(vertical ? height : width);
layoutService.current.layout(vertical ? height : width);
setDimensionsInitialized(true);
}
},
});
Expand All @@ -320,7 +358,7 @@ const Allotment = forwardRef<AllotmentHandle, AllotmentProps>(
)}
>
<div className={styles.splitViewContainer}>
{React.Children.toArray(children).map((child, index) => {
{React.Children.toArray(children).map((child) => {
if (!React.isValidElement(child)) {
return null;
}
Expand Down
10 changes: 10 additions & 0 deletions src/helpers/string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Checks if `string` ends with the given target string.
*/
export function endsWith(string: string, target: string): boolean {
const length = string.length;

const position = length - target.length;

return position >= 0 && string.slice(position, length) === target;
}
88 changes: 87 additions & 1 deletion src/split-view/split-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import clamp from "lodash.clamp";
import styles from "../allotment.module.css";
import { pushToEnd, range } from "../helpers/array";
import { Disposable } from "../helpers/disposable";
import { endsWith } from "../helpers/string";
import {
Orientation,
Sash,
Expand Down Expand Up @@ -145,6 +146,92 @@ export interface View {
setVisible?(visible: boolean): void;
}

export class LayoutService {
private _size!: number;

get size() {
return this._size;
}

public layout(size: number) {
this._size = size;
}
}

export interface PaneViewOptions {
element: HTMLElement;
minimumSize?: number;
maximumSize?: number;
preferredSize?: number | string;
snap?: boolean;
}

export class PaneView implements View {
public minimumSize: number = 0;
public maximumSize: number = Number.POSITIVE_INFINITY;

readonly element: HTMLElement;
readonly snap: boolean;

private layoutService: LayoutService;
private _preferredSize: () => number | undefined;

get preferredSize() {
return this._preferredSize();
}

constructor(layoutService: LayoutService, options: PaneViewOptions) {
this.layoutService = layoutService;
this.element = options.element;

this.minimumSize =
typeof options.minimumSize === "number" ? options.minimumSize : 30;

this.maximumSize =
typeof options.maximumSize === "number"
? options.maximumSize
: Number.POSITIVE_INFINITY;

if (typeof options.preferredSize === "number") {
this._preferredSize = () => {
return options.preferredSize as number;
};
} else if (typeof options.preferredSize === "string") {
if (endsWith(options.preferredSize, "%")) {
const percentage = Number(options.preferredSize.slice(0, -1)) / 100;

this._preferredSize = () => {
return percentage * this.layoutService.size;
};
} else if (endsWith(options.preferredSize, "px")) {
const pixels = Number(options.preferredSize.slice(0, -2)) / 100;

this._preferredSize = () => {
return pixels;
};
} else if (Number.parseFloat(options.preferredSize)) {
const number = Number.parseFloat(options.preferredSize);

this._preferredSize = () => {
return number;
};
} else {
this._preferredSize = () => {
return undefined;
};
}
} else {
this._preferredSize = () => {
return undefined;
};
}

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

layout(_size: number): void {}
}

type ViewItemSize = number | { cachedVisibleSize: number };

abstract class ViewItem {
Expand Down Expand Up @@ -662,7 +749,6 @@ export class SplitView extends EventEmitter implements Disposable {

for (const item of flexibleViewItems) {
item.size = clamp(size, item.minimumSize, item.maximumSize);
console.log(item.size);
}

this.relayout();
Expand Down
2 changes: 1 addition & 1 deletion stories/advanced.stories.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
color: #cccccc;
font-family: sans-serif;
height: 480px;
width: 640px;
width: 1024px;
}
15 changes: 13 additions & 2 deletions stories/advanced.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ export const VisualStudioCode: Story = ({
const [openEditors, setOpenEditors] = useState<Document[]>(DOCUMENTS);

const sidebar = (
<Allotment.Pane key="sidebar" minSize={170} visible={primarySideBar} snap>
<Allotment.Pane
key="sidebar"
minSize={170}
preferredSize={300}
visible={primarySideBar}
snap
>
<Sidebar
title={ACTIVITIES[activity]}
documents={DOCUMENTS}
Expand Down Expand Up @@ -113,7 +119,12 @@ export const VisualStudioCode: Story = ({
}}
/>
</Allotment.Pane>
<Allotment.Pane key="terminal" minSize={78} visible={panelVisible}>
<Allotment.Pane
key="terminal"
minSize={78}
preferredSize="40%"
visible={panelVisible}
>
<Panel
maximized={!editorVisible}
onClose={() => {
Expand Down
Loading

0 comments on commit e558c4e

Please sign in to comment.