aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx175
1 files changed, 64 insertions, 111 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a0a64ab59..2800ea200 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { OmitKeys, numberRange } from '../../../Utils';
@@ -12,14 +12,16 @@ import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SelectionManager } from '../../util/SelectionManager';
import { DocComponent } from '../DocComponent';
-import { ObservableReactComponent } from '../ObservableReactComponent';
import { StyleProp } from '../StyleProvider';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import './CollectionFreeFormDocumentView.scss';
import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import { FieldViewProps } from './FieldView';
-export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
+/// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need
+/// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
+const freeFormPropsKeys = ['x', 'y', 'z', 'zIndex', 'rotation', 'opacity', 'backgroundColor', 'color', 'highlight', 'width', 'height', 'autoDim', 'transition'];
+interface freeFormProps {
x: number;
y: number;
z: number;
@@ -33,102 +35,17 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
opacity?: number;
highlight?: boolean;
transition?: string;
- RenderCutoffProvider: (doc: Doc) => boolean;
- CollectionFreeFormView: CollectionFreeFormView;
-}
-@observer
-export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent<CollectionFreeFormDocumentViewWrapperProps> {
- constructor(props: CollectionFreeFormDocumentViewWrapperProps) {
- super(props);
- makeObservable(this);
- }
- @observable X = this.props.x;
- @observable Y = this.props.y;
- @observable Z = this.props.z;
- @observable ZIndex = this.props.zIndex;
- @observable Rotation = this.props.rotation;
- @observable Opacity = this.props.opacity;
- @observable BackgroundColor = this.props.backgroundColor;
- @observable Color = this.props.color;
- @observable Highlight = this.props.highlight;
- @observable Width = this.props.width;
- @observable Height = this.props.height;
- @observable AutoDim = this.props.autoDim;
- @observable Transition = this.props.transition;
- CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
- RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
-
- get Document() {
- return this._props.Document;
- }
- @computed get WrapperKeys() {
- return Object.keys(this).filter(key => key.startsWith('w_')).map(key => key.replace('w_', ''))
- .map(key => ({upper:key, lower:key[0].toLowerCase() + key.substring(1)})); // prettier-ignore
- }
-
- // wrapper functions around prop fields that have been converted to observables to keep 'props' from ever changing.
- // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
- w_X = () => this.X; // prettier-ignore
- w_Y = () => this.Y; // prettier-ignore
- w_Z = () => this.Z; // prettier-ignore
- w_ZIndex = () => this.ZIndex ?? NumCast(this.Document.zIndex); // prettier-ignore
- w_Rotation = () => this.Rotation ?? NumCast(this.Document._rotation); // prettier-ignore
- w_Opacity = () => this.Opacity; // prettier-ignore
- w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); // prettier-ignore
- w_Color = () => this.Color ?? Cast(this.Document._color, 'string', null); // prettier-ignore
- w_Highlight = () => this.Highlight; // prettier-ignore
- w_Width = () => this.Width; // prettier-ignore
- w_Height = () => this.Height; // prettier-ignore
- w_AutoDim = () => this.AutoDim; // prettier-ignore
- w_Transition = () => this.Transition; // prettier-ignore
-
- PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
- PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
-
- componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>>) {
- super.componentDidUpdate(prevProps);
- this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
- }
- render() {
- const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function });
- return (
- <CollectionFreeFormDocumentView
- {...OmitKeys(this._props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
- {...layoutProps}
- PanelWidth={this.PanelWidth}
- PanelHeight={this.PanelHeight}
- />
- );
- }
}
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- w_X: () => number;
- w_Y: () => number;
- w_Z: () => number;
- w_ZIndex?: () => number;
- w_Rotation?: () => number;
- w_Color: () => string;
- w_BackgroundColor: () => string;
- w_Opacity: () => number | undefined;
- w_Highlight: () => boolean | undefined;
- w_Transition: () => string | undefined;
- w_Width: () => number;
- w_Height: () => number;
- PanelWidth: () => number;
- PanelHeight: () => number;
RenderCutoffProvider: (doc: Doc) => boolean;
CollectionFreeFormView: CollectionFreeFormView;
}
-
@observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
- constructor(props: CollectionFreeFormDocumentViewProps) {
- super(props);
- makeObservable(this);
- }
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & freeFormProps>() {
get displayName() { // this makes mobx trace() statements more descriptive
return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
} // prettier-ignore
+ public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
public static animFields: { key: string; val?: number }[] = [
{ key: 'x' },
{ key: 'y' },
@@ -145,16 +62,53 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
- get CollectionFreeFormView() {
- return this._props.CollectionFreeFormView;
+ constructor(props: CollectionFreeFormDocumentViewProps & freeFormProps) {
+ super(props);
+ makeObservable(this);
+ }
+
+ get WrapperKeys() {
+ // each of these keys is set by the CollectionView and passed via props. however, if any one of these props changes
+ // (or any other prop), then it's as if they all change.
+ // Anything that accesses these props will then invalidate unncessarily.
+ // To avoid this, we copy these prop values into local observables. Now when 'props' changes, nothing invalidates.
+ // Instead, we copy each values into its observable which ohnly triggers invalidations if the new value is different
+ // than the old value, and then only things that access that observable will invalidate.
+ return freeFormPropsKeys
+ .map(key => ({upper:key[0].toUpperCase() + key.substring(1), lower:key})); // prettier-ignore
+ }
+ @observable X = this.props.x;
+ @observable Y = this.props.y;
+ @observable Z = this.props.z;
+ @observable ZIndex = this.props.zIndex;
+ @observable Rotation = this.props.rotation;
+ @observable Opacity = this.props.opacity;
+ @observable BackgroundColor = this.props.backgroundColor;
+ @observable Color = this.props.color;
+ @observable Highlight = this.props.highlight;
+ @observable Width = this.props.width;
+ @observable Height = this.props.height;
+ @observable AutoDim = this.props.autoDim;
+ @observable Transition = this.props.transition;
+
+ componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
+ super.componentDidUpdate(prevProps);
+ this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
}
+ CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
+ // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
+ DataTransition = () => this._props.transition; // prettier-ignore
+ RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
+ PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
+ PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
+
styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
if (doc === this.layoutDoc) {
switch (property) {
- case StyleProp.Opacity: return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
- case StyleProp.BackgroundColor: return this._props.w_BackgroundColor();
- case StyleProp.Color: return this._props.w_Color();
+ case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children
+ case StyleProp.BackgroundColor: return this.BackgroundColor;
+ case StyleProp.Color: return this.Color;
} // prettier-ignore
}
return this._props.styleProvider?.(doc, props, property);
@@ -253,14 +207,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
nudge = (x: number, y: number) => {
const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
- this.Document.x = this._props.w_X() + locX;
- this.Document.y = this._props.w_Y() + locY;
+ this.Document.x = this.X + locX;
+ this.Document.y = this.Y + locY;
};
screenToLocalTransform = () =>
this._props
.ScreenToLocalTransform()
- .translate(-this._props.w_X(), -this._props.w_Y())
- .rotateDeg(-(this._props.w_Rotation?.() || 0));
+ .translate(-this.X, -this.Y)
+ .rotateDeg(-(this.Rotation || 0));
returnThis = () => this;
/// this indicates whether the doc view is activated because of its relationshop to a group
@@ -273,27 +227,26 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
};
- public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
render() {
TraceMobx();
- const passOnProps = OmitKeys(this._props, Object.keys(this._props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
+
return (
<div
className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName}
style={{
- width: this._props.PanelWidth(),
- height: this._props.PanelHeight(),
- transform: `translate(${this._props.w_X()}px, ${this._props.w_Y()}px) rotate(${NumCast(this._props.w_Rotation?.())}deg)`,
- transition: this._props.w_Transition?.() || StrCast(this.Document.dataTransition),
- zIndex: this._props.w_ZIndex?.(),
- display: this._props.w_Width?.() ? undefined : 'none',
+ width: this.PanelWidth(),
+ height: this.PanelHeight(),
+ transform: `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rotation)}deg)`,
+ transition: this.Transition || StrCast(this.Document.dataTransition),
+ zIndex: this.ZIndex,
+ display: this.Width ? undefined : 'none',
}}>
- {this._props.RenderCutoffProvider(this.Document) ? (
- <div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
+ {this.RenderCutoffProvider(this.Document) ? (
+ <div style={{ position: 'absolute', width: this.PanelWidth(), height: this.PanelHeight(), background: 'lightGreen' }} />
) : (
<DocumentView
- {...passOnProps}
- DataTransition={this._props.w_Transition}
+ {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
+ DataTransition={this.DataTransition}
CollectionFreeFormDocumentView={this.returnThis}
styleProvider={this.styleProvider}
ScreenToLocalTransform={this.screenToLocalTransform}