diff options
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx | 73 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx | 115 |
2 files changed, 108 insertions, 80 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 9090d0ea5..3af5a6c4b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -3,30 +3,79 @@ import { observer } from 'mobx-react'; import './CollectionFreeFormView.scss'; import React = require('react'); -export type infoArc = { - events: () => any; - actions: (arg?: any) => any; -}; -export type infoState = { - Message: string; - Arcs: infoArc[]; -}; +/** + * An Fsa Arc. The first array element is a test condition function that will be observed. + * The second array element is a function that will be invoked when the first test function + * returns a truthy value + */ +export type infoArc = [() => any, (res?: any) => infoState]; + +export const StateMessage = Symbol('StateMessage'); +export const StateEntryFunc = Symbol('StateEntryFunc'); +export class infoState { + [StateMessage]: string = ''; + [StateEntryFunc]?: () => any; + [key: string]: infoArc; + constructor(message: string, arcs: { [key: string]: infoArc }, entryFunc?: () => any) { + this[StateMessage] = message; + Object.assign(this, arcs); + this[StateEntryFunc] = entryFunc; + } +} + +/** + * Create an FSA state. + * @param msg the message displayed when in this state + * @param arcs an object with fields containing @infoArcs (an object with field names indicating the arc transition and + * field values being a tuple of an arc transition trigger function (that returns a truthy value when the arc should fire), + * and an arc transition action function (that sets the next state) + * @param entryFunc a function to call when entering the state + * @returns an FSA state + */ +export function InfoState( + msg: string, // + arcs: { [key: string]: infoArc }, + entryFunc?: () => any +) { + return new infoState(msg, arcs, entryFunc); +} export interface CollectionFreeFormInfoStateProps { - state: infoState; + infoState: infoState; + next: (state: infoState) => any; } @observer export class CollectionFreeFormInfoState extends React.Component<CollectionFreeFormInfoStateProps> { _disposers: IReactionDisposer[] = []; + get State() { + return this.props.infoState; + } + get Arcs() { + return Object.keys(this.State).map(key => this.State[key]); + } + clearState = () => this._disposers.map(disposer => disposer()); - initState = () => (this._disposers = this.props.state.Arcs.map(arc => reaction(arc.events, arc.actions, { fireImmediately: true }))); + initState = () => + (this._disposers = this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(arc => { + return reaction( + // + arc.test, + res => { + if (res) { + const next = arc.act(res); + this.props.next(next); + } + }, + { fireImmediately: true } + ); + })); componentDidMount(): void { this.initState(); } - componentWillUpdate() { + componentDidUpdate() { this.clearState(); this.initState(); } @@ -34,6 +83,6 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF this.clearState(); } render() { - return <div className="infoUI">{this.props.state.Message}</div>; + return <div className="infoUI">{this.State[StateMessage]}</div>; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 57d2b7ba1..daf1bd8a7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -1,4 +1,4 @@ -import { IReactionDisposer, computed, observable, reaction } from 'mobx'; +import { IReactionDisposer, computed, observable, reaction, action, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; @@ -12,7 +12,9 @@ import { InkTool } from '../../../../fields/InkField'; import { LinkDocPreview } from '../../nodes/LinkDocPreview'; import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { DocumentManager } from '../../../util/DocumentManager'; -import { CollectionFreeFormInfoState, infoState } from './CollectionFreeFormInfoState'; +import { CollectionFreeFormInfoState, infoState, StateMessage, infoArc, StateEntryFunc, InfoState } from './CollectionFreeFormInfoState'; +import { string32 } from 'pdfjs-dist/types/src/shared/util'; +import { any } from 'bluebird'; export interface CollectionFreeFormInfoUIProps { Document: Doc; @@ -23,77 +25,54 @@ export interface CollectionFreeFormInfoUIProps { export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeFormInfoUIProps> { private _disposers: { [name: string]: IReactionDisposer } = {}; + @observable currState!: infoState; constructor(props: any) { super(props); - this.currState = this.state0; + this.setCurrState(this.setupStates()); } - @computed get first_doc() { - return this.props.Freeform.childDocs.lastElement(); - } - @observable currState: infoState; - state0: infoState = { - Message: 'Click to create Object', - Arcs: [ - { - events: () => this.props.Freeform.childDocs, - actions: (docs: Doc[]) => { - if (docs.length === 1) this.currState = this.state1; - if (docs.length > 1) this.currState = this.state2; - }, - }, - ], - }; - state1: infoState = { - Message: 'Create a second doc', - Arcs: [ - { - events: () => this.props.Freeform.childDocs, - actions: (docs: Doc[]) => { - if (docs.length === 0) this.currState = this.state0; - if (docs.length === 2) this.currState = this.state2; - }, - }, - ], - }; - state2: infoState = { - Message: 'Create a link', - Arcs: [ - { - events: () => LinkManager.Instance.getAllDirectLinks(this.first_doc), - actions: links => links?.length && (this.currState = this.state3), - }, - { - events: () => this.props.Freeform.childDocs, - actions: docs => { - if (docs.length === 0) this.currState = this.state0; - if (docs.length === 1) this.currState = this.state1; - }, - }, - ], + setCurrState = (state: infoState) => { + if (state) { + runInAction(() => (this.currState = state)); + this.currState[StateEntryFunc]?.(); + } }; - state3: infoState = { - Message: 'View links', - Arcs: [ - { - events: () => LinkManager.Instance.getAllDirectLinks(this.first_doc), - actions: links => links?.length === 0 && (this.currState = this.state2), - }, - { - events: () => DocumentLinksButton.LinkEditorDocView, - actions: viewingLinks => viewingLinks && (this.currState = this.state4), - }, - ], - }; - state4: infoState = { - Message: 'You did it!', - Arcs: [ - { - events: () => false, - actions: arc => {}, - }, - ], + setupStates = () => { + // state entry functions + const setBackground = (col: string) => () => (this.props.Freeform.layoutDoc.backgroundColor = col); + // arc transition trigger conditions + const firstDoc = () => this.props.Freeform.childDocs.lastElement(); + const numDocs = () => this.props.Freeform.childDocs.length; + const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length; + const linkMenuOpen = () => DocumentLinksButton.LinkEditorDocView; + + // set of states. + const start = InfoState('Click to create Object', { + docCreated: [() => numDocs(), () => oneDoc], + }, setBackground("blue")); // prettier-ignore + + const oneDoc = InfoState('Create a second doc', { + docCreated: [() => numDocs() > 1, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + }, setBackground("green")); // prettier-ignore + + const multipleDocs = InfoState('Create a link', { + linkCreated: [() => numDocLinks(), () => madeLink], + docsRemoved: [() => numDocs() < 2, () => oneDoc], + }, setBackground("orange")); // prettier-ignore + + const madeLink = InfoState('View links', { + linkCreated: [() => !numDocLinks(), () => multipleDocs], + linksViewed: [() => linkMenuOpen(), (res) => { alert("Yay"+ res); return completed;}], + }, setBackground("yellow")); // prettier-ignore + + const completed = InfoState('You did it!', { + linkDeleted: [() => !numDocLinks(), () => multipleDocs], + docsRemoved: [() => numDocs() < 2, () => oneDoc], + }, setBackground("white")); // prettier-ignore + + return start; }; /* @@ -189,6 +168,6 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm firstDocPos = { x: 0, y: 0 }; render() { - return <CollectionFreeFormInfoState state={this.currState} />; + return <CollectionFreeFormInfoState next={this.setCurrState} infoState={this.currState} />; } } |