Skip to content

Commit 1e21f1c

Browse files
authored
Flatten rendered tree for performance (#46)
1 parent 15ad729 commit 1e21f1c

29 files changed

+606
-421
lines changed

README.md

Lines changed: 65 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ The best way to see it is a simple [**Demo**](https://palantir.github.io/react-m
1212
[![screencast demo](./screencast.gif)](./screencast.gif)
1313

1414
## Usage
15-
The core of react-mosaic's operations revolve around the simple binary tree [specified by `MosaicNode<T>`](./src/types.ts#L22).
16-
`T` is the type of the leaves of the tree and can be anything that can be resolved to a `React.ReactElement` for display.
15+
The core of react-mosaic's operations revolve around the simple binary tree [specified by `MosaicNode<T>`](./src/types.ts#L27).
16+
[`T`](./src/types.ts#L22) is the type of the leaves of the tree and is a `string` or a `number` that can be resolved to a `JSX.Element` for display.
1717

1818
### Installation
1919
1. `yarn add react-mosaic-component`
@@ -37,48 +37,18 @@ Mosaic supports the Blueprint Dark Theme out of the box when rendered with the `
3737

3838
### Examples
3939

40-
#### Simple tiling of `ReactElement`s
40+
#### Simple Tiling
4141
```tsx
4242
import { Mosaic } from 'react-mosaic-component';
4343

44-
// Make a simple extension class to preserve generic type checking in TSX
45-
class ElementMosaic extends Mosaic<React.ReactElement> { }
46-
47-
export const app = (
48-
<ElementMosaic
49-
renderTile={ e => e }
50-
initialValue={{
51-
direction: 'row',
52-
first: <div>Left Window</div>,
53-
second: {
54-
direction: 'column',
55-
first: <div>Top Right Window</div>,
56-
second: <div>Bottom Right Window</div>
57-
}
58-
}}
59-
/>
60-
);
61-
```
62-
`renderTile` is a stateless lookup function to convert `T` into a displayable `ReactElement`.
63-
Here `T` is already a `ReactElement`, so `renderTile` can simply be the identity function.
64-
This example renders a simple tiled interface with one element on the left half, and two stacked elements on the right half.
65-
The user can resize these panes but there is no other advanced functionality.
66-
67-
#### Tiling with IDs
68-
```tsx
69-
export type ViewId = string;
70-
71-
// Make a simple extension class to preserve generic type checking in TSX
72-
class ViewIdMosaic extends Mosaic<ViewId> { }
73-
74-
const ELEMENT_MAP: { [viewId: string]: React.ReactElement<any> } = {
44+
const ELEMENT_MAP: { [viewId: string]: JSX.Element } = {
7545
a: <div>Left Window</div>,
7646
b: <div>Top Right Window</div>,
7747
c: <div>Bottom Right Window</div>
7848
};
7949

8050
export const app = (
81-
<ViewIdMosaic
51+
<Mosaic
8252
renderTile={ id => ELEMENT_MAP[id] }
8353
initialValue={{
8454
direction: 'row',
@@ -92,21 +62,25 @@ export const app = (
9262
/>
9363
);
9464
```
95-
Here `T` is a `ViewId` that can be used to look elements up in `ELEMENT_MAP`.
96-
This allows for easier view state specification and serialization.
97-
The resulting view looks and functions identically to the previous example.
65+
`renderTile` is a stateless lookup function to convert `T` into a displayable `JSX.Element`.
66+
By default `T` is `string`.
67+
`initialValue` is a [`MosaicNode<T>`](./src/types.ts#L27).
68+
69+
The user can resize these panes but there is no other advanced functionality.
70+
This example renders a simple tiled interface with one element on the left half, and two stacked elements on the right half.
71+
The user can resize these panes but there is no other advanced functionality.
9872

9973
#### Drag, Drop, and other advanced functionality with `MosaicWindow`
10074
`MosaicWindow` is a component that renders a toolbar and controls around its children for a tile as well as providing full featured drag and drop functionality.
10175

10276
```tsx
103-
export type ViewId = string;
77+
export type ViewId = 'a' | 'b' | 'c' | 'new';
10478

10579
// Make a simple extension class to preserve generic type checking in TSX
10680
class ViewIdMosaic extends Mosaic<ViewId> { }
10781
class ViewIdMosaicWindow extends MosaicWindow<ViewId> { }
10882

109-
const TITLE_MAP: { [viewId: string]: string } = {
83+
const TITLE_MAP: Record<ViewId, string> = {
11084
a: 'Left Window',
11185
b: 'Top Right Window',
11286
c: 'Bottom Right Window',
@@ -115,12 +89,13 @@ const TITLE_MAP: { [viewId: string]: string } = {
11589

11690
export const app = (
11791
<ViewIdMosaic
118-
renderTile={ id => (
92+
renderTile={(id, path) => (
11993
<ViewIdMosaicWindow
120-
createNode={ () => 'new' }
121-
title={ TITLE_MAP[id] }
94+
path={path}
95+
createNode={() => 'new'}
96+
title={TITLE_MAP[id]}
12297
>
123-
<div>title</div>
98+
<h1>{TITLE_MAP[id]}</h1>
12499
</ViewIdMosaicWindow>
125100
)}
126101
initialValue={{
@@ -135,9 +110,12 @@ export const app = (
135110
/>
136111
);
137112
```
113+
Here `T` is a `ViewId` that can be used to look elements up in `TITLE_MAP`.
114+
This allows for easy view state specification and serialization.
138115
This will render a view that looks very similar to the previous examples, but now each of the windows will have a toolbar with buttons.
139116
These toolbars can be dragged around by a user to rearrange their workspace.
140-
`MosaicWindow` API docs [here](#MosaicWindow).
117+
118+
`MosaicWindow` API docs [here](#mosaicwindow).
141119

142120
#### Controlled vs. Uncontrolled
143121
Mosaic views have two modes, similar to `React.DOM` input elements:
@@ -151,7 +129,8 @@ All of the previous examples show use of Mosaic in an Uncontrolled fashion.
151129

152130
#### TS/JS vs. TSX/JSX
153131
Components export both factories and component classes.
154-
If you are using TS/JS then use the factories; if you are using TSX/JSX then use the exported class but know that you will lose the generics if you aren't careful.
132+
If you are using TS/JS then use the factories;
133+
if you are using TSX/JSX then use the exported class but know that you will lose the generics if you aren't careful.
155134
The exported classes are named as the base name of the component (e.g. `MosaicWindow`) while the exported factories
156135
have 'Factory' appended (e.g. `MosaicWindowFactory`).
157136

@@ -163,9 +142,9 @@ for a more interesting example that shows the usage of Mosaic as a controlled co
163142

164143
#### Mosaic Props
165144
```typescript
166-
export interface MosaicBaseProps<T> {
145+
export interface MosaicBaseProps<T extends MosaicKey> {
167146
/**
168-
* Lookup function to convert `T` to a displayable `ReactElement`
147+
* Lookup function to convert `T` to a displayable `JSX.Element`
169148
*/
170149
renderTile: TileRenderer<T>;
171150
/**
@@ -186,47 +165,54 @@ export interface MosaicBaseProps<T> {
186165
* View to display when the current value is `null`
187166
* default: Simple NonIdealState view
188167
*/
189-
zeroStateView?: React.ReactElement<any>;
168+
zeroStateView?: JSX.Element;
190169
}
191170

192-
export interface MosaicControlledProps<T> extends MosaicBaseProps<T> {
171+
export interface MosaicControlledProps<T extends MosaicKey> extends MosaicBaseProps<T> {
193172
/**
194173
* The tree to render
195174
*/
196175
value: MosaicNode<T> | null;
197176
onChange: (newNode: MosaicNode<T> | null) => void;
198177
}
199178

200-
export interface MosaicUncontrolledProps<T> extends MosaicBaseProps<T> {
179+
export interface MosaicUncontrolledProps<T extends MosaicKey> extends MosaicBaseProps<T> {
201180
/**
202181
* The initial tree to render, can be modified by the user
203182
*/
204183
initialValue: MosaicNode<T> | null;
205184
}
206185

207-
export type MosaicProps<T> = MosaicControlledProps<T> | MosaicUncontrolledProps<T>;
186+
export type MosaicProps<T extends MosaicKey> = MosaicControlledProps<T> | MosaicUncontrolledProps<T>;
208187
```
209188

210189
#### `MosaicWindow`
211190

212191
```typescript
213-
export interface MosaicWindowProps<T> {
192+
export interface MosaicWindowProps<T extends MosaicKey> {
214193
title: string;
194+
/**
195+
* Current path to this window, provided by `renderTile`
196+
*/
197+
path: MosaicBranch[];
215198
className?: string;
216199
/**
217200
* Controls in the top right of the toolbar
218201
* default: [Replace, Split, Expand, Remove] if createNode is defined and [Expand, Remove] otherwise
219202
*/
220-
toolbarControls?: React.ReactElement<any>[];
203+
toolbarControls?: React.ReactNode;
221204
/**
222205
* Additional controls that will be hidden in a drawer beneath the toolbar.
223206
* default: []
224207
*/
225-
additionalControls?: React.ReactElement<any>[];
208+
additionalControls?: React.ReactNode;
226209
/**
227210
* Label for the button that expands the drawer
228211
*/
229212
additionalControlButtonText?: string;
213+
/**
214+
* Whether or not a user should be able to drag windows around
215+
*/
230216
draggable?: boolean;
231217
/**
232218
* Method called when a new node is required (such as the Split or Replace buttons)
@@ -238,29 +224,31 @@ export interface MosaicWindowProps<T> {
238224
renderPreview?: (props: MosaicWindowProps<T>) => JSX.Element;
239225
}
240226
```
241-
The default controls rendered by `MosaicWindow` can be accessed from [`defaultToolbarControls`](src/buttons/MosaicButton.tsx)
227+
The default controls rendered by `MosaicWindow` can be accessed from [`defaultToolbarControls`](./src/buttons/defaultToolbarControls.tsx)
242228

243229
### Advanced API
244230
The above API is good for most consumers, however Mosaic provides functionality on the [Context](https://facebook.github.io/react/docs/context.html) of its children that make it easier to alter the view state.
245231
All leaves rendered by Mosaic will have the following available on React context.
246232
These are used extensively by `MosaicWindow`.
247233

248234
```typescript
235+
/**
236+
* Valid node types
237+
* @see React.Key
238+
*/
239+
export type MosaicKey = string | number;
249240
export type MosaicBranch = 'first' | 'second';
250241
export type MosaicPath = MosaicBranch[];
251242

252-
export interface MosaicTileContext<T> {
253-
/**
254-
* These actions are used to alter the state of the view tree
255-
*/
256-
mosaicActions: MosaicRootActions<T>;
257-
/**
258-
* Returns the path to this tile
259-
*/
260-
getMosaicPath: () => MosaicPath;
243+
/**
244+
* Context provided to everything within Mosaic
245+
*/
246+
export interface MosaicContext<T extends MosaicKey> {
247+
mosaicActions: MosaicRootActions<T>;
248+
mosaicId: string;
261249
}
262250

263-
export interface MosaicRootActions<T> {
251+
export interface MosaicRootActions<T extends MosaicKey> {
264252
/**
265253
* Increases the size of this node and bubbles up the tree
266254
* @param path Path to node to expand
@@ -298,7 +286,7 @@ export interface MosaicRootActions<T> {
298286
Children (and toolbar elements) within `MosaicWindow` are passed the following additional functions on context.
299287

300288
```typescript
301-
export interface MosaicWindowContext<T> extends MosaicTileContext<T> {
289+
export interface MosaicWindowContext<T extends MosaicKey> extends MosaicContext<T> {
302290
mosaicWindowActions: MosaicWindowActions;
303291
}
304292

@@ -319,6 +307,10 @@ export interface MosaicWindowActions {
319307
* Sets the open state for the tray that holds additional controls
320308
*/
321309
setAdditionalControlsOpen: (open: boolean) => void;
310+
/**
311+
* Returns the path to this window
312+
*/
313+
getPath: () => MosaicPath;
322314
}
323315
```
324316

@@ -345,18 +337,19 @@ class RemoveButton extends React.PureComponent<Props> {
345337
```
346338

347339
### Mutating the Tree
348-
Utilities are provided for working with the MosaicNode tree in [`mosaicUtilities`](./src/mosaicUtilities.ts) and
349-
[`mosaicUpdates`](./src/mosaicUpdates.ts)
340+
Utilities are provided for working with the MosaicNode tree in [`mosaicUtilities`](src/util/mosaicUtilities.ts) and
341+
[`mosaicUpdates`](src/util/mosaicUpdates.ts)
350342
#### MosaicUpdate
351-
[`MosaicUpdateSpec`](./src/types.ts#L43) is an argument meant to be passed to [`immutability-helper`](https://github.com/kolodny/immutability-helper)
352-
to modify the state at a path. [`mosaicUpdates`](./src/mosaicUpdates.ts) has examples.
343+
[`MosaicUpdateSpec`](./src/types.ts#L48) is an argument meant to be passed to [`immutability-helper`](https://github.com/kolodny/immutability-helper)
344+
to modify the state at a path.
345+
[`mosaicUpdates`](src/util/mosaicUpdates.ts) has examples.
353346

354347
```
355348
/**
356349
* Used by many utility methods to update the tree.
357350
* spec will be passed to https://github.com/kolodny/immutability-helper
358351
*/
359-
export interface MosaicUpdateSpec<T> {
352+
export interface MosaicUpdateSpec<T extends MosaicKey> {
360353
$set?: MosaicNode<T>;
361354
splitPercentage?: {
362355
$set: number | null;

demo/ExampleApp.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ const additionalControls = React.Children.toArray([
5151
<CloseAdditionalControlsButton/>,
5252
]);
5353

54+
const EMPTY_ARRAY: any[] = [];
55+
5456
export interface ExampleAppState {
5557
currentNode: MosaicNode<number> | null;
5658
currentTheme: Theme;
@@ -73,11 +75,12 @@ export class ExampleApp extends React.PureComponent<{}, ExampleAppState> {
7375
<div className='react-mosaic-example-app'>
7476
{this.renderNavBar()}
7577
<NumberMosaic
76-
renderTile={(count: number) => (
78+
renderTile={(count, path) => (
7779
<NumberMosaicWindow
78-
additionalControls={count === 3 ? additionalControls : []}
80+
additionalControls={count === 3 ? additionalControls : EMPTY_ARRAY}
7981
title={`Window ${count}`}
8082
createNode={this.createNode}
83+
path={path}
8184
>
8285
<div className='example-window'>
8386
<h1>{`Window ${count}`}</h1>

demo/example.less

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ html, body, #app, .react-mosaic-example-app {
5555
color: @white;
5656
}
5757

58-
> .mosaic-root {
58+
> .mosaic {
5959
height: ~"calc(100% - 50px)";
6060
}
6161
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"uuid": "^3.0.1"
7878
},
7979
"peerDependencies": {
80-
"react": "^15.0.0 || ^16.0.0"
80+
"react": "^16.0.0"
8181
},
8282
"keywords": [
8383
"ui",

0 commit comments

Comments
 (0)