Skip to content

Commit c918a28

Browse files
authored
Merge pull request #64 from projectstorm/improve_initial_render_logic
Improve initial render logic
2 parents a590fc7 + 845b677 commit c918a28

File tree

13 files changed

+171
-33
lines changed

13 files changed

+171
-33
lines changed

.changeset/eight-fishes-cheer.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@projectstorm/react-workspaces-core': minor
3+
---
4+
5+
- New behavior to install auto recompute on overconstrained panels automatically
6+
- new widget to temp hide rendering until expand computation has completed initially (for expand nodes specifically)
7+
- some general code style and structure improvements

demo/.storybook/main.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ const config: StorybookConfig = {
1717
return {
1818
...config,
1919
devtool: 'inline-source-map',
20+
resolve: {
21+
...config.resolve,
22+
alias: {
23+
'@emotion/react': require.resolve('@emotion/react')
24+
}
25+
},
2026
module: {
2127
...config.module,
2228
rules: [

demo/stories/helpers/complexModel.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,6 @@ import { WorkspaceEngine } from '@projectstorm/react-workspaces-core';
77

88
export const createComplexModel = (engine: WorkspaceEngine) => {
99
let model = new RootWorkspaceModel(engine);
10-
model.registerListener({
11-
overConstrainedChanged: () => {
12-
console.log(`overconstrained: ${model.r_overConstrained ? 'true' : 'false'}`);
13-
14-
// when we overconstrained, we can use the directive below to cause the children layouts on the root model
15-
// to be recomputed (this method exists on all ExpandNodeModels )
16-
if (model.r_overConstrained) {
17-
// model.recomputeInitialSizes();
18-
}
19-
}
20-
});
2110
model.setHorizontal(true);
2211

2312
const trayFactory = engine.getFactory<WorkspaceTrayFactory>(WorkspaceTrayModel.NAME);

demo/stories/helpers/simpleModel.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { ExpandNodeModel } from '@projectstorm/react-workspaces-core';
22
import { genVerticalNode } from './tools';
3+
import { DefaultWorkspacePanelModel } from '@projectstorm/react-workspaces-defaults';
34

45
export const createSimpleModel = () => {
56
let model = new ExpandNodeModel();
6-
model.setHorizontal(true);
77
model
8-
8+
.setHorizontal(true)
9+
//
910
//left panel
1011
.addModel(genVerticalNode())
1112
.addModel(genVerticalNode())

demo/stories/helpers/tools.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'typeface-open-sans';
66
import {
77
DebugLayer,
88
ExpandNodeModel,
9+
overConstrainRecomputeBehavior,
910
WorkspaceEngine,
1011
WorkspaceNodeFactory,
1112
WorkspaceNodeModel,
@@ -100,6 +101,10 @@ export const useEngine = (args: { DebugDividers?: boolean; DebugResizers?: boole
100101
e.registerFactory(workspaceNodeFactory);
101102
e.registerFactory(windowFactory);
102103

104+
overConstrainRecomputeBehavior({
105+
engine: e
106+
});
107+
103108
draggingItemBehavior({
104109
engine: e,
105110
getDropZoneForModel: (model) => {

packages/core/src/entities/node/ExpandNodeModel.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1-
import { ResizeDivision, WorkspaceNodeModel, WorkspaceNodeModelSerialized } from './WorkspaceNodeModel';
1+
import {
2+
ResizeDivision,
3+
WorkspaceNodeModel,
4+
WorkspaceNodeModelListener,
5+
WorkspaceNodeModelSerialized
6+
} from './WorkspaceNodeModel';
27
import { WorkspaceModel } from '../../core-models/WorkspaceModel';
38
import * as _ from 'lodash';
49
import { WorkspaceEngine } from '../../core/WorkspaceEngine';
510

6-
export interface ExpandNodeModelSerialized extends WorkspaceNodeModelSerialized {}
11+
export interface ExpandNodeModelSerialized extends WorkspaceNodeModelSerialized {
12+
computed_initial?: boolean;
13+
}
14+
15+
export interface ExpandNodeModelListener extends WorkspaceNodeModelListener {
16+
recomputed?: () => any;
17+
}
718

819
/**
920
* This is a smarter version of the standard Node model which can work with
@@ -14,13 +25,16 @@ export interface ExpandNodeModelSerialized extends WorkspaceNodeModelSerialized
1425
* want to expand (expandHorizontal/Vertical == false)
1526
*/
1627
export class ExpandNodeModel<
17-
S extends ExpandNodeModelSerialized = ExpandNodeModelSerialized
18-
> extends WorkspaceNodeModel<S> {
28+
S extends ExpandNodeModelSerialized = ExpandNodeModelSerialized,
29+
L extends ExpandNodeModelListener = ExpandNodeModelListener
30+
> extends WorkspaceNodeModel<S, L> {
1931
busy: boolean;
32+
computed_initial: boolean;
2033

2134
constructor() {
2235
super();
2336
this.busy = false;
37+
this.computed_initial = false;
2438
}
2539

2640
addModel(model: WorkspaceModel, position: number = null): this {
@@ -29,9 +43,19 @@ export class ExpandNodeModel<
2943
return this;
3044
}
3145

46+
toArray(): S {
47+
return {
48+
...super.toArray(),
49+
computed_initial: this.computed_initial
50+
};
51+
}
52+
3253
fromArray(payload: S, engine: WorkspaceEngine) {
33-
// we disable re-computation since the panels should have their correct sizes
34-
this.busy = true;
54+
this.computed_initial = payload.computed_initial ?? false;
55+
// we disable re-computation since the panels should have their correct sizes already
56+
if (this.computed_initial) {
57+
this.busy = true;
58+
}
3559
super.fromArray(payload, engine);
3660
this.busy = false;
3761
}
@@ -41,10 +65,12 @@ export class ExpandNodeModel<
4165
return;
4266
}
4367
this.busy = true;
44-
4568
let length = await this.r_dimensions.waitForSize().then((size) => (this.vertical ? size.height : size.width));
4669
const expand = this.getExpandNodes();
4770
if (expand.length <= 1) {
71+
this.busy = false;
72+
this.computed_initial = true;
73+
this.iterateListeners((cb) => cb.recomputed?.());
4874
return;
4975
}
5076
length = this.vertical ? this.r_dimensions.size.height : this.r_dimensions.size.width;
@@ -72,6 +98,9 @@ export class ExpandNodeModel<
7298
}
7399
}
74100
this.busy = false;
101+
this.computed_initial = true;
102+
103+
this.iterateListeners((cb) => cb.recomputed?.());
75104
}
76105

77106
getResizeDivisions(): ResizeDivision[] {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as React from 'react';
2+
import { useEffect } from 'react';
3+
import styled from '@emotion/styled';
4+
import { WorkspaceNodeWidget } from './WorkspaceNodeWidget';
5+
import { WorkspaceEngine } from '../../core/WorkspaceEngine';
6+
import { WorkspaceNodeFactory } from './WorkspaceNodeFactory';
7+
import { ResizeDimensionContainer } from './ResizeDimensionContainer';
8+
import { ExpandNodeModel } from './ExpandNodeModel';
9+
import { useForceUpdate } from '../../widgets/hooks/useForceUpdate';
10+
11+
export interface ExpandNodeWidgetProps {
12+
engine: WorkspaceEngine;
13+
factory: WorkspaceNodeFactory;
14+
model: ExpandNodeModel;
15+
generateDivider?: (divider: ResizeDimensionContainer) => React.JSX.Element;
16+
className?: any;
17+
}
18+
19+
export const ExpandNodeWidget: React.FC<ExpandNodeWidgetProps> = (props) => {
20+
const forceUpdate = useForceUpdate();
21+
useEffect(() => {
22+
return props.model.registerListener({
23+
recomputed: () => {
24+
forceUpdate();
25+
}
26+
});
27+
}, []);
28+
return <S.WorkspaceNode {...props} computed_initial={props.model.computed_initial} />;
29+
};
30+
namespace S {
31+
export const WorkspaceNode = styled(WorkspaceNodeWidget)<{ computed_initial: boolean }>`
32+
opacity: ${(p) => (p.computed_initial ? 1 : 0)};
33+
`;
34+
}

packages/core/src/entities/node/WorkspaceNodeFactory.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { WorkspaceModel } from '../../core-models/WorkspaceModel';
55
import { SubComponentModelFactory, SubComponentRenderer } from '../SubComponentModelFactory';
66
import { WorkspaceNodeWidget } from './WorkspaceNodeWidget';
77
import { WorkspaceEngine } from '../../core/WorkspaceEngine';
8+
import { ExpandNodeModel } from './ExpandNodeModel';
9+
import { ExpandNodeWidget } from './ExpandNodeWidget';
810

911
export interface RenderTitleBarEvent<T extends WorkspaceModel> {
1012
model: T;
@@ -23,7 +25,10 @@ export class WorkspaceNodeFactory<
2325
super(type);
2426
}
2527

26-
generateContent(event: WorkspaceModelFactoryEvent<T>): JSX.Element {
28+
generateContent(event: WorkspaceModelFactoryEvent<T>): React.JSX.Element {
29+
if (event.model instanceof ExpandNodeModel) {
30+
return <ExpandNodeWidget model={event.model} engine={event.engine} factory={this} />;
31+
}
2732
return <WorkspaceNodeWidget model={event.model} engine={event.engine} factory={this} />;
2833
}
2934

packages/core/src/entities/node/WorkspaceNodeWidget.tsx

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react';
2+
import { useEffect } from 'react';
23
import styled from '@emotion/styled';
34
import { WorkspaceEngine } from '../../core/WorkspaceEngine';
45
import { WorkspaceNodeFactory, WorkspaceNodePanelRenderer } from './WorkspaceNodeFactory';
@@ -7,14 +8,13 @@ import { DraggableWidget } from '../../widgets/primitives/DraggableWidget';
78
import { WorkspaceModel } from '../../core-models/WorkspaceModel';
89
import { useModelElement } from '../../widgets/hooks/useModelElement';
910
import { DirectionalLayoutWidget } from '../../widgets/layouts/DirectionalLayoutWidget';
10-
import { useEffect } from 'react';
1111
import { ResizeDimensionContainer } from './ResizeDimensionContainer';
1212

1313
export interface WorkspaceNodeWidgetProps {
1414
engine: WorkspaceEngine;
1515
factory: WorkspaceNodeFactory;
1616
model: WorkspaceNodeModel;
17-
generateDivider?: (divider: ResizeDimensionContainer) => JSX.Element;
17+
generateDivider?: (divider: ResizeDimensionContainer) => React.JSX.Element;
1818
className?: any;
1919
}
2020

@@ -24,16 +24,30 @@ export const WorkspaceNodeWidget: React.FC<WorkspaceNodeWidgetProps> = (props) =
2424
model: props.model
2525
});
2626
useEffect(() => {
27-
return props.model.r_dimensions.registerListener({
27+
const checkOverConstrain = () => {
28+
if (props.model.vertical) {
29+
props.model.setOverConstrained(ref.current.scrollHeight > props.model.r_dimensions.size.height);
30+
} else {
31+
props.model.setOverConstrained(ref.current.scrollWidth > props.model.r_dimensions.size.width);
32+
}
33+
};
34+
35+
let l1 = props.model.r_dimensions.registerListener({
2836
updated: () => {
29-
if (props.model.vertical) {
30-
props.model.setOverConstrained(ref.current.scrollHeight > props.model.r_dimensions.size.height);
31-
} else {
32-
props.model.setOverConstrained(ref.current.scrollWidth > props.model.r_dimensions.size.width);
33-
}
37+
checkOverConstrain();
3438
}
3539
});
36-
}, []);
40+
let l2 = props.model.registerListener({
41+
dimensionsInvalidated: () => {
42+
checkOverConstrain();
43+
}
44+
});
45+
46+
return () => {
47+
l1();
48+
l2();
49+
};
50+
}, [props.model]);
3751
return (
3852
<S.DirectionalLayout
3953
forwardRef={ref}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ExpandNodeModel } from './ExpandNodeModel';
2+
import { WorkspaceEngine } from '../../core/WorkspaceEngine';
3+
4+
export interface OverConstrainRecomputeBehaviorOptions {
5+
engine: WorkspaceEngine;
6+
}
7+
8+
export const overConstrainRecomputeBehavior = (options: OverConstrainRecomputeBehaviorOptions) => {
9+
const { engine } = options;
10+
11+
let l1: () => any;
12+
let l2 = engine.registerListener({
13+
layoutInvalidated: () => {
14+
l1?.();
15+
let listeners = engine.rootModel
16+
.flatten()
17+
.filter((m) => m instanceof ExpandNodeModel)
18+
.map((m: ExpandNodeModel) => {
19+
return m.registerListener({
20+
overConstrainedChanged: () => {
21+
if (m.r_overConstrained) {
22+
m.recomputeSizes();
23+
}
24+
}
25+
});
26+
});
27+
l1 = () => {
28+
listeners.forEach((l) => l());
29+
};
30+
}
31+
});
32+
33+
return () => {
34+
l1?.();
35+
l2();
36+
};
37+
};

packages/core/src/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,8 @@ export * from './entities/node/WorkspaceNodeModel';
4343
export * from './entities/node/ExpandNodeModel';
4444
export * from './entities/node/WorkspaceNodeFactory';
4545
export * from './entities/node/WorkspaceNodeWidget';
46+
export * from './entities/node/ExpandNodeWidget';
47+
export * from './entities/node/overConstrainRecomputeBehavior';
48+
4649
export * from './entities/SubComponentModelFactory';
4750
export * from './entities/SubComponentModelFactory';

packages/core/src/widgets/hooks/useBaseResizeObserver.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ export const useBaseResizeObserver = (props: UseBaseBaseResizeObserverProps) =>
3434
}, [props.dimension]);
3535

3636
const updateDebounced = useCallback(
37-
_.debounce(() => {
38-
updateLogic();
39-
}, 500),
37+
_.debounce(
38+
() => {
39+
updateLogic();
40+
},
41+
500,
42+
{ leading: true }
43+
),
4044
[props.dimension]
4145
);
4246

@@ -46,6 +50,7 @@ export const useBaseResizeObserver = (props: UseBaseBaseResizeObserverProps) =>
4650
} else {
4751
updateDebounced();
4852
}
53+
updateLogic();
4954
}, [props.dimension, props.ignoreDebounce]);
5055

5156
// listen to invalidate directives

packages/core/src/widgets/layouts/DirectionalLayoutWidget.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ namespace S {
3030
}
3131

3232
export const DirectionalLayoutWidget: React.FC<DirectionalLayoutWidgetProps> = (props) => {
33+
if (props.data.length === 0) {
34+
return <S.Container ref={props.forwardRef} className={props.className} vertical={props.vertical}></S.Container>;
35+
}
3336
const firstDivider = props.dimensionContainerForDivider(0);
3437
const generateDivider = useCallback((dimension: ResizeDimensionContainer) => {
3538
if (props.generateDivider) {

0 commit comments

Comments
 (0)