Skip to content

Improve initial render logic #64

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

Merged
merged 3 commits into from
Oct 21, 2024
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
7 changes: 7 additions & 0 deletions .changeset/eight-fishes-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@projectstorm/react-workspaces-core': minor
---

- New behavior to install auto recompute on overconstrained panels automatically
- new widget to temp hide rendering until expand computation has completed initially (for expand nodes specifically)
- some general code style and structure improvements
6 changes: 6 additions & 0 deletions demo/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ const config: StorybookConfig = {
return {
...config,
devtool: 'inline-source-map',
resolve: {
...config.resolve,
alias: {
'@emotion/react': require.resolve('@emotion/react')
}
},
module: {
...config.module,
rules: [
Expand Down
11 changes: 0 additions & 11 deletions demo/stories/helpers/complexModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,6 @@ import { WorkspaceEngine } from '@projectstorm/react-workspaces-core';

export const createComplexModel = (engine: WorkspaceEngine) => {
let model = new RootWorkspaceModel(engine);
model.registerListener({
overConstrainedChanged: () => {
console.log(`overconstrained: ${model.r_overConstrained ? 'true' : 'false'}`);

// when we overconstrained, we can use the directive below to cause the children layouts on the root model
// to be recomputed (this method exists on all ExpandNodeModels )
if (model.r_overConstrained) {
// model.recomputeInitialSizes();
}
}
});
model.setHorizontal(true);

const trayFactory = engine.getFactory<WorkspaceTrayFactory>(WorkspaceTrayModel.NAME);
Expand Down
5 changes: 3 additions & 2 deletions demo/stories/helpers/simpleModel.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { ExpandNodeModel } from '@projectstorm/react-workspaces-core';
import { genVerticalNode } from './tools';
import { DefaultWorkspacePanelModel } from '@projectstorm/react-workspaces-defaults';

export const createSimpleModel = () => {
let model = new ExpandNodeModel();
model.setHorizontal(true);
model

.setHorizontal(true)
//
//left panel
.addModel(genVerticalNode())
.addModel(genVerticalNode())
Expand Down
5 changes: 5 additions & 0 deletions demo/stories/helpers/tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'typeface-open-sans';
import {
DebugLayer,
ExpandNodeModel,
overConstrainRecomputeBehavior,
WorkspaceEngine,
WorkspaceNodeFactory,
WorkspaceNodeModel,
Expand Down Expand Up @@ -100,6 +101,10 @@ export const useEngine = (args: { DebugDividers?: boolean; DebugResizers?: boole
e.registerFactory(workspaceNodeFactory);
e.registerFactory(windowFactory);

overConstrainRecomputeBehavior({
engine: e
});

draggingItemBehavior({
engine: e,
getDropZoneForModel: (model) => {
Expand Down
43 changes: 36 additions & 7 deletions packages/core/src/entities/node/ExpandNodeModel.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { ResizeDivision, WorkspaceNodeModel, WorkspaceNodeModelSerialized } from './WorkspaceNodeModel';
import {
ResizeDivision,
WorkspaceNodeModel,
WorkspaceNodeModelListener,
WorkspaceNodeModelSerialized
} from './WorkspaceNodeModel';
import { WorkspaceModel } from '../../core-models/WorkspaceModel';
import * as _ from 'lodash';
import { WorkspaceEngine } from '../../core/WorkspaceEngine';

export interface ExpandNodeModelSerialized extends WorkspaceNodeModelSerialized {}
export interface ExpandNodeModelSerialized extends WorkspaceNodeModelSerialized {
computed_initial?: boolean;
}

export interface ExpandNodeModelListener extends WorkspaceNodeModelListener {
recomputed?: () => any;
}

/**
* This is a smarter version of the standard Node model which can work with
Expand All @@ -14,13 +25,16 @@ export interface ExpandNodeModelSerialized extends WorkspaceNodeModelSerialized
* want to expand (expandHorizontal/Vertical == false)
*/
export class ExpandNodeModel<
S extends ExpandNodeModelSerialized = ExpandNodeModelSerialized
> extends WorkspaceNodeModel<S> {
S extends ExpandNodeModelSerialized = ExpandNodeModelSerialized,
L extends ExpandNodeModelListener = ExpandNodeModelListener
> extends WorkspaceNodeModel<S, L> {
busy: boolean;
computed_initial: boolean;

constructor() {
super();
this.busy = false;
this.computed_initial = false;
}

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

toArray(): S {
return {
...super.toArray(),
computed_initial: this.computed_initial
};
}

fromArray(payload: S, engine: WorkspaceEngine) {
// we disable re-computation since the panels should have their correct sizes
this.busy = true;
this.computed_initial = payload.computed_initial ?? false;
// we disable re-computation since the panels should have their correct sizes already
if (this.computed_initial) {
this.busy = true;
}
super.fromArray(payload, engine);
this.busy = false;
}
Expand All @@ -41,10 +65,12 @@ export class ExpandNodeModel<
return;
}
this.busy = true;

let length = await this.r_dimensions.waitForSize().then((size) => (this.vertical ? size.height : size.width));
const expand = this.getExpandNodes();
if (expand.length <= 1) {
this.busy = false;
this.computed_initial = true;
this.iterateListeners((cb) => cb.recomputed?.());
return;
}
length = this.vertical ? this.r_dimensions.size.height : this.r_dimensions.size.width;
Expand Down Expand Up @@ -72,6 +98,9 @@ export class ExpandNodeModel<
}
}
this.busy = false;
this.computed_initial = true;

this.iterateListeners((cb) => cb.recomputed?.());
}

getResizeDivisions(): ResizeDivision[] {
Expand Down
34 changes: 34 additions & 0 deletions packages/core/src/entities/node/ExpandNodeWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import { useEffect } from 'react';
import styled from '@emotion/styled';
import { WorkspaceNodeWidget } from './WorkspaceNodeWidget';
import { WorkspaceEngine } from '../../core/WorkspaceEngine';
import { WorkspaceNodeFactory } from './WorkspaceNodeFactory';
import { ResizeDimensionContainer } from './ResizeDimensionContainer';
import { ExpandNodeModel } from './ExpandNodeModel';
import { useForceUpdate } from '../../widgets/hooks/useForceUpdate';

export interface ExpandNodeWidgetProps {
engine: WorkspaceEngine;
factory: WorkspaceNodeFactory;
model: ExpandNodeModel;
generateDivider?: (divider: ResizeDimensionContainer) => React.JSX.Element;
className?: any;
}

export const ExpandNodeWidget: React.FC<ExpandNodeWidgetProps> = (props) => {
const forceUpdate = useForceUpdate();
useEffect(() => {
return props.model.registerListener({
recomputed: () => {
forceUpdate();
}
});
}, []);
return <S.WorkspaceNode {...props} computed_initial={props.model.computed_initial} />;
};
namespace S {
export const WorkspaceNode = styled(WorkspaceNodeWidget)<{ computed_initial: boolean }>`
opacity: ${(p) => (p.computed_initial ? 1 : 0)};
`;
}
7 changes: 6 additions & 1 deletion packages/core/src/entities/node/WorkspaceNodeFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { WorkspaceModel } from '../../core-models/WorkspaceModel';
import { SubComponentModelFactory, SubComponentRenderer } from '../SubComponentModelFactory';
import { WorkspaceNodeWidget } from './WorkspaceNodeWidget';
import { WorkspaceEngine } from '../../core/WorkspaceEngine';
import { ExpandNodeModel } from './ExpandNodeModel';
import { ExpandNodeWidget } from './ExpandNodeWidget';

export interface RenderTitleBarEvent<T extends WorkspaceModel> {
model: T;
Expand All @@ -23,7 +25,10 @@ export class WorkspaceNodeFactory<
super(type);
}

generateContent(event: WorkspaceModelFactoryEvent<T>): JSX.Element {
generateContent(event: WorkspaceModelFactoryEvent<T>): React.JSX.Element {
if (event.model instanceof ExpandNodeModel) {
return <ExpandNodeWidget model={event.model} engine={event.engine} factory={this} />;
}
return <WorkspaceNodeWidget model={event.model} engine={event.engine} factory={this} />;
}

Expand Down
32 changes: 23 additions & 9 deletions packages/core/src/entities/node/WorkspaceNodeWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { useEffect } from 'react';
import styled from '@emotion/styled';
import { WorkspaceEngine } from '../../core/WorkspaceEngine';
import { WorkspaceNodeFactory, WorkspaceNodePanelRenderer } from './WorkspaceNodeFactory';
Expand All @@ -7,14 +8,13 @@ import { DraggableWidget } from '../../widgets/primitives/DraggableWidget';
import { WorkspaceModel } from '../../core-models/WorkspaceModel';
import { useModelElement } from '../../widgets/hooks/useModelElement';
import { DirectionalLayoutWidget } from '../../widgets/layouts/DirectionalLayoutWidget';
import { useEffect } from 'react';
import { ResizeDimensionContainer } from './ResizeDimensionContainer';

export interface WorkspaceNodeWidgetProps {
engine: WorkspaceEngine;
factory: WorkspaceNodeFactory;
model: WorkspaceNodeModel;
generateDivider?: (divider: ResizeDimensionContainer) => JSX.Element;
generateDivider?: (divider: ResizeDimensionContainer) => React.JSX.Element;
className?: any;
}

Expand All @@ -24,16 +24,30 @@ export const WorkspaceNodeWidget: React.FC<WorkspaceNodeWidgetProps> = (props) =
model: props.model
});
useEffect(() => {
return props.model.r_dimensions.registerListener({
const checkOverConstrain = () => {
if (props.model.vertical) {
props.model.setOverConstrained(ref.current.scrollHeight > props.model.r_dimensions.size.height);
} else {
props.model.setOverConstrained(ref.current.scrollWidth > props.model.r_dimensions.size.width);
}
};

let l1 = props.model.r_dimensions.registerListener({
updated: () => {
if (props.model.vertical) {
props.model.setOverConstrained(ref.current.scrollHeight > props.model.r_dimensions.size.height);
} else {
props.model.setOverConstrained(ref.current.scrollWidth > props.model.r_dimensions.size.width);
}
checkOverConstrain();
}
});
}, []);
let l2 = props.model.registerListener({
dimensionsInvalidated: () => {
checkOverConstrain();
}
});

return () => {
l1();
l2();
};
}, [props.model]);
return (
<S.DirectionalLayout
forwardRef={ref}
Expand Down
37 changes: 37 additions & 0 deletions packages/core/src/entities/node/overConstrainRecomputeBehavior.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ExpandNodeModel } from './ExpandNodeModel';
import { WorkspaceEngine } from '../../core/WorkspaceEngine';

export interface OverConstrainRecomputeBehaviorOptions {
engine: WorkspaceEngine;
}

export const overConstrainRecomputeBehavior = (options: OverConstrainRecomputeBehaviorOptions) => {
const { engine } = options;

let l1: () => any;
let l2 = engine.registerListener({
layoutInvalidated: () => {
l1?.();
let listeners = engine.rootModel
.flatten()
.filter((m) => m instanceof ExpandNodeModel)
.map((m: ExpandNodeModel) => {
return m.registerListener({
overConstrainedChanged: () => {
if (m.r_overConstrained) {
m.recomputeSizes();
}
}
});
});
l1 = () => {
listeners.forEach((l) => l());
};
}
});

return () => {
l1?.();
l2();
};
};
3 changes: 3 additions & 0 deletions packages/core/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,8 @@ export * from './entities/node/WorkspaceNodeModel';
export * from './entities/node/ExpandNodeModel';
export * from './entities/node/WorkspaceNodeFactory';
export * from './entities/node/WorkspaceNodeWidget';
export * from './entities/node/ExpandNodeWidget';
export * from './entities/node/overConstrainRecomputeBehavior';

export * from './entities/SubComponentModelFactory';
export * from './entities/SubComponentModelFactory';
11 changes: 8 additions & 3 deletions packages/core/src/widgets/hooks/useBaseResizeObserver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ export const useBaseResizeObserver = (props: UseBaseBaseResizeObserverProps) =>
}, [props.dimension]);

const updateDebounced = useCallback(
_.debounce(() => {
updateLogic();
}, 500),
_.debounce(
() => {
updateLogic();
},
500,
{ leading: true }
),
[props.dimension]
);

Expand All @@ -46,6 +50,7 @@ export const useBaseResizeObserver = (props: UseBaseBaseResizeObserverProps) =>
} else {
updateDebounced();
}
updateLogic();
}, [props.dimension, props.ignoreDebounce]);

// listen to invalidate directives
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/widgets/layouts/DirectionalLayoutWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ namespace S {
}

export const DirectionalLayoutWidget: React.FC<DirectionalLayoutWidgetProps> = (props) => {
if (props.data.length === 0) {
return <S.Container ref={props.forwardRef} className={props.className} vertical={props.vertical}></S.Container>;
}
const firstDivider = props.dimensionContainerForDivider(0);
const generateDivider = useCallback((dimension: ResizeDimensionContainer) => {
if (props.generateDivider) {
Expand Down
Loading