diff options
Diffstat (limited to 'src/client/views/nodes/CollectionFreeFormDocumentView.tsx')
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 222 |
1 files changed, 125 insertions, 97 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 085f9f023..421d431b3 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; @@ -6,10 +6,9 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { numberRange } from '../../../Utils'; +import { numberRange, OmitKeys } from '../../../Utils'; import { DocumentManager } from '../../util/DocumentManager'; import { SelectionManager } from '../../util/SelectionManager'; -import { Transform } from '../../util/Transform'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { DocComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; @@ -17,26 +16,113 @@ import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import React = require('react'); -export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; rotation?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; - sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined; - renderCutoffProvider: (doc: Doc) => boolean; +export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps { + x: number; + y: number; + z: number; + width: number; + height: number; zIndex?: number; + rotation?: number; + color?: string; + backgroundColor?: string; + opacity?: number; + highlight?: boolean; + transition?: string; dataTransition?: string; - replica: string; + RenderCutoffProvider: (doc: Doc) => boolean; + CollectionFreeFormView: CollectionFreeFormView; +} +@observer +export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps>() implements CollectionFreeFormDocumentViewProps { + @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 Transition = this.props.transition; + @observable DataTransition = this.props.dataTransition; + CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking + RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking + + @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.props.Document.zIndex); // prettier-ignore + w_Rotation = () => this.Rotation ?? NumCast(this.props.Document._rotation); // prettier-ignore + w_Opacity = () => this.Opacity; // prettier-ignore + w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.props.Document._backgroundColor, 'string', null); // prettier-ignore + w_Color = () => this.Color ?? Cast(this.props.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_Transition = () => this.Transition; // prettier-ignore + w_DataTransition = () => this.DataTransition; // prettier-ignore + + PanelWidth = () => this.Width || this.props.PanelWidth?.(); // prettier-ignore + PanelHeight = () => this.Height || this.props.PanelHeight?.(); // prettier-ignore + @action + componentDidUpdate() { + this.WrapperKeys.forEach(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 { + 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; + w_DataTransition: () => string | undefined; + PanelWidth: () => number; + PanelHeight: () => number; + RenderCutoffProvider: (doc: Doc) => boolean; CollectionFreeFormView: CollectionFreeFormView; } @observer -export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() { +export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() { + get displayName() { // this makes mobx trace() statements more descriptive + return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')'; + } // prettier-ignore public static animFields: { key: string; val?: number }[] = [ - { key: '_height' }, - { key: '_width' }, { key: 'x' }, { key: 'y' }, + { key: 'opacity', val: 1 }, + { key: '_height' }, + { key: '_width' }, { key: '_rotation', val: 0 }, { key: '_layout_scrollTop' }, - { key: 'opacity', val: 1 }, { key: '_currentFrame' }, { key: 'freeform_scale', val: 1 }, { key: 'freeform_panX' }, @@ -44,52 +130,18 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF ]; // 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 - @observable _animPos: number[] | undefined = undefined; - @observable _contentView: DocumentView | undefined | null; - get displayName() { - // this makes mobx trace() statements more descriptive - return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')'; - } - get transform() { - return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rot, this.Rot)}deg)`; - } - get X() { - return this.dataProvider?.x ?? NumCast(this.Document.x); - } - get Y() { - return this.dataProvider?.y ?? NumCast(this.Document.y); - } - get ZInd() { - return this.dataProvider?.zIndex ?? NumCast(this.Document.zIndex); - } - get Rot() { - return this.dataProvider?.rotation ?? NumCast(this.Document._rotation); - } - get Opacity() { - return this.dataProvider?.opacity; - } - get BackgroundColor() { - return this.dataProvider?.backgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); - } - get Color() { - return this.dataProvider?.color ?? Cast(this.Document._color, 'string', null); - } - @computed get dataProvider() { - return this.props.dataProvider?.(this.props.Document, this.props.replica); - } - @computed get sizeProvider() { - return this.props.sizeProvider?.(this.props.Document, this.props.replica); + get CollectionFreeFormView() { + return this.props.CollectionFreeFormView; } styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { if (doc === this.layoutDoc) { - // prettier-ignore switch (property) { - 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; - } + 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(); + } // prettier-ignore } return this.props.styleProvider?.(doc, props, property); }; @@ -126,21 +178,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF }); } - public static setupZoom(doc: Doc, targDoc: Doc) { - const width = new List<number>(); - const height = new List<number>(); - const top = new List<number>(); - const left = new List<number>(); - width.push(NumCast(targDoc._width)); - height.push(NumCast(targDoc._height)); - top.push(NumCast(targDoc._height) / -2); - left.push(NumCast(targDoc._width) / -2); - doc['viewfinder-width-indexed'] = width; - doc['viewfinder-height-indexed'] = height; - doc['viewfinder-top-indexed'] = top; - doc['viewfinder-left-indexed'] = left; - } - public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) { docs.forEach(doc => { if (doc.appearFrame === undefined) doc.appearFrame = currTimecode; @@ -182,16 +219,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF } }; - dragEnding = () => this.props.CollectionFreeFormView?.dragEnding(); - dragStarting = () => this.props.CollectionFreeFormView?.dragStarting(false, true); - nudge = (x: number, y: number) => { - this.props.Document.x = NumCast(this.props.Document.x) + x; - this.props.Document.y = NumCast(this.props.Document.y) + y; + const [locX, locY] = this.props.ScreenToLocalTransform().transformDirection(x, y); + this.props.Document.x = this.props.w_X() + locX; + this.props.Document.y = this.props.w_Y() + locY; }; - panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.(); - panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.(); - screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y); + screenToLocalTransform = () => + this.props + .ScreenToLocalTransform() + .translate(-this.props.w_X(), -this.props.w_Y()) + .rotateDeg(-(this.props.w_Rotation?.() || 0)); returnThis = () => this; /// this indicates whether the doc view is activated because of its relationshop to a group @@ -200,38 +237,29 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF // 'inactive' - this is a group child but it is not active // undefined - this is not activated by a group isGroupActive = () => { - if (this.props.CollectionFreeFormView.isAnyChildContentActive()) return undefined; + if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined; const isGroup = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.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 divProps: DocumentViewProps = { - ...this.props, - CollectionFreeFormDocumentView: this.returnThis, - styleProvider: this.styleProvider, - ScreenToLocalTransform: this.screenToLocalTransform, - PanelWidth: this.panelWidth, - PanelHeight: this.panelHeight, - isGroupActive: this.isGroupActive, - }; + const passOnProps = OmitKeys(this.props, Object.keys(this.props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore return ( <div - className="collectionFreeFormDocumentView-container" + className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName} style={{ - width: this.panelWidth(), - height: this.panelHeight(), - transform: this.transform, - transformOrigin: '50% 50%', - transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)), - zIndex: this.ZInd, - display: this.sizeProvider?.width ? undefined : 'none', - pointerEvents: 'none', + 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?.() ?? (this.props.w_DataTransition?.() || this.props.w_Transition?.()), + zIndex: this.props.w_ZIndex?.(), + display: this.props.w_Width?.() ? undefined : 'none', }}> - {this.props.renderCutoffProvider(this.props.Document) ? ( - <div style={{ position: 'absolute', width: this.panelWidth(), height: this.panelHeight(), background: 'lightGreen' }} /> + {this.props.RenderCutoffProvider(this.props.Document) ? ( + <div style={{ position: 'absolute', width: this.props.PanelWidth(), height: this.props.PanelHeight(), background: 'lightGreen' }} /> ) : ( - <DocumentView {...divProps} ref={action((r: DocumentView | null) => (this._contentView = r))} /> + <DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} /> )} </div> ); |