diff options
Diffstat (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 191 |
1 files changed, 79 insertions, 112 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index a0a64ab59..0d0a7c623 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, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { OmitKeys, numberRange } from '../../../Utils'; @@ -12,14 +12,17 @@ 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'; +import { TransitionTimer } from '../../../fields/DocSymbols'; -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,15 +36,48 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView opacity?: number; highlight?: boolean; transition?: string; +} +export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { RenderCutoffProvider: (doc: Doc) => boolean; CollectionFreeFormView: CollectionFreeFormView; } @observer -export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent<CollectionFreeFormDocumentViewWrapperProps> { - constructor(props: CollectionFreeFormDocumentViewWrapperProps) { +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' }, + { key: 'opacity', val: 1 }, + { key: '_height' }, + { key: '_width' }, + { key: '_rotation', val: 0 }, + { key: '_layout_scrollTop' }, + { key: '_currentFrame' }, + { key: 'freeform_scale', val: 1 }, + { key: 'freeform_panX' }, + { key: 'freeform_panY' }, + ]; // fields that are configured to be animatable using animation frames + 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 + + 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; @@ -55,106 +91,35 @@ export class CollectionFreeFormDocumentViewWrapper extends ObservableReactCompon @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 + componentDidMount(): void { + if (this.props.transition && !this.Document[TransitionTimer]) { + const num = Number(this.props.transition.match(/([0-9.]+)s/)?.[1]) * 1000 || Number(this.props.transition.match(/([0-9.]+)ms/)?.[1]); + this.Document[TransitionTimer] = setTimeout( + action(() => (this.Document[TransitionTimer] = this.Transition = undefined)), + num + ); + } } - // 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 }>>) { + 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]))); } - 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); - } - get displayName() { // this makes mobx trace() statements more descriptive - return 'CollectionFreeFormDocumentView(' + this.Document.title + ')'; - } // prettier-ignore - public static animFields: { key: string; val?: number }[] = [ - { key: 'x' }, - { key: 'y' }, - { key: 'opacity', val: 1 }, - { key: '_height' }, - { key: '_width' }, - { key: '_rotation', val: 0 }, - { key: '_layout_scrollTop' }, - { key: '_currentFrame' }, - { key: 'freeform_scale', val: 1 }, - { key: 'freeform_panX' }, - { key: 'freeform_panY' }, - ]; // fields that are configured to be animatable using animation frames - 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; - } + 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.Transition || StrCast(this.Document.dataTransition); // 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(); + switch (property.split(':')[0]) { + 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 +218,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 @@ -270,34 +235,36 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF // undefined - this is not activated by a group isGroupActive = () => { if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined; - const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent'); + const backColor = this.BackgroundColor; + const isGroup = this.dataDoc.isGroup && (!backColor || backColor === '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.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} isGroupActive={this.isGroupActive} + PanelWidth={this.PanelWidth} + PanelHeight={this.PanelHeight} /> )} </div> |