import { makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, FieldResult, FieldType } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { StrCast } from '../../../../fields/Types'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { TopBar } from '../../topbar/TopBar'; import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState'; import { CollectionFreeFormView } from './CollectionFreeFormView'; import { Button } from '@dash/components'; import { ButtonType } from '../../nodes/FontIconBox/FontIconBox'; import './CollectionFreeFormView.scss'; export interface CollectionFreeFormInfoUIProps { Doc: Doc; layoutDoc: Doc; childDocs: () => Doc[]; close: () => void; } @observer export class CollectionFreeFormInfoUI extends ObservableReactComponent { _originalBackground: string | undefined; public tutorialStates: { [key: string]: infoState } = {}; public static Init() { CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => ( // )); } constructor(props: CollectionFreeFormInfoUIProps) { super(props); makeObservable(this); this.tutorialStates = {}; // Initialize an empty object this.currState = this.setupStates(); // Call setupStates() here } @observable _currState: infoState | undefined = undefined; @observable _nextState: infoState | undefined = undefined; // Track next state get currState() { return this._currState; } set currState(val) { runInAction(() => { this._currState = val; }); } componentWillUnmount(): void { this._props.Doc.$backgroundColor = this._originalbackground; } skipToState = (newState: infoState) => { runInAction(() => { console.log('Transitioning to next state:', newState); if (!this._currState) { this._currState = newState; // Assign directly if undefined } else { this._currState = newState; } }); }; createNextButton = (newState: ReturnType) => { return { title: 'Next', toolTip: 'Next', btnType: ButtonType.ClickButton, scripts: { onClick: `this.skipToState(${newState})`, }, targetState: newState, }; }; setupStates = () => { this._originalbackground = StrCast(this._props.Doc.$backgroundColor); // state entry functions // const setBackground = (colour: string) => () => {this._props.Doc.$backgroundColor = colour;} // prettier-ignore // const setOpacity = (opacity: number) => () => {this._props.layoutDoc.opacity = opacity;} // prettier-ignore // arc transition trigger conditions const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined); const numDocs = () => this._props.childDocs().length; let docX: FieldResult; let docY: FieldResult; const docNewX = () => firstDoc()?.x; const docNewY = () => firstDoc()?.y; const linkStart = () => DocumentLinksButton.StartLink; const linkUnstart = () => !DocumentLinksButton.StartLink; const numDocLinks = () => Doc.Links(firstDoc())?.length; const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView; const activeTool = () => Doc.ActiveTool; const pin = () => DocListCast(Doc.ActivePresentation?.data); let trail: number; const presentationMode = () => Doc.ActivePresentation?.presentation_status; // set of states const start = InfoState( 'Click anywhere and begin typing to create your first text document.', { docCreated: [() => numDocs(), () => { docX = firstDoc()?.x; docY = firstDoc()?.y; // eslint-disable-next-line no-use-before-define return oneDoc; }], } ); // prettier-ignore const oneDoc = InfoState( 'Hello world! You can drag and drop to move your document around.', { // docCreated: [() => numDocs() > 1, () => multipleDocs], docDeleted: [() => numDocs() < 1, () => start], docMoved: [() => (docX && docX !== docNewX()) || (docY && docY !== docNewY()), () => { docX = firstDoc()?.x; docY = firstDoc()?.y; // eslint-disable-next-line no-use-before-define return movedDoc; }], } ); // prettier-ignore const movedDoc = InfoState( 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (":")', { // eslint-disable-next-line no-use-before-define linkStarted: [ () => DocumentLinksButton.StartLink, () => { linkCounter = Doc.Links(lastDocCreated).length; // eslint-disable-next-line no-use-before-define return startedLink; }, ], }, 'dash-create-link-board.gif' ); this.tutorialStates.presentDocs = InfoState( 'Click the pin icon in the top left corner while clicking a doc to create your presentation.', { // eslint-disable-next-line no-use-before-define docPinned: [ () => DocListCast(Doc.ActivePresentation?.data).length > presentationCounter, () => { presentationCounter++; // eslint-disable-next-line no-use-before-define return pinnedDoc; }, ], }, 'pin-explanation.gif' ); this.tutorialStates.nestedCollections = InfoState( "Want to learn how to create a nested collection? Click the : button and add a 'collection' doc", { // eslint-disable-next-line no-use-before-define docCreated: [ () => this._props.childDocs().length > docCounter, () => { docCounter += 1; lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; // eslint-disable-next-line no-use-before-define return marqueeSelection; }, ], }, 'dash-nested-collection.gif' ); this.tutorialStates.makePresentation = InfoState('Add a new document to create a presentation!', { docCreated: [ () => this._props.childDocs().length > docCounter, () => { docCounter += 1; lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; // eslint-disable-next-line no-use-before-define return this.tutorialStates.presentDocs; }, ], }); const skipToLinksButton: Button = { title: 'Links Tutorial', toolTip: 'Skip', btnType: ButtonType.ClickButton, scripts: { onClick: 'this.skipToState(this.tutorialStates.multipleDocs)', }, targetState: this.tutorialStates.multipleDocs, }; const skipToPinsButton: Button = { title: 'Pins Tutorial', toolTip: 'Skip', btnType: ButtonType.ClickButton, scripts: { onClick: 'this.skipToState(this.tutorialStates.makePresentation)', }, targetState: this.tutorialStates.makePresentation, }; const skipToPresentationButton: Button = { title: 'Collections Tutorial', toolTip: 'Skip', btnType: ButtonType.ClickButton, scripts: { onClick: 'this.skipToState(this.tutorialStates.nestedCollections)', }, targetState: this.tutorialStates.nestedCollections, }; const ending = InfoState("If you have any more questions, feel free to ask Dash's AI Bot!", {}); // Traditional tutorial const completed = InfoState('Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', { docRemoved: [() => this._props.childDocs().length === 1, () => this.tutorialStates.start] }, 'documentation.png'); const penMode = InfoState("You're in pen mode! Click and drag to draw your first masterpiece, then click the Ink button once you're done.", { activePen: [() => Doc.ActiveTool !== InkTool.Ink, () => completed], }); const briefArtisticFeature = InfoState("Finally, want to explore the art feature of Dash? Click the 'Ink' button on the hotbar then click the pen button.", { penModeActivated: [() => Doc.ActiveTool === InkTool.Ink, () => penMode], }); const activatePresentation = InfoState('Lastly, click the linked node and start the presentation!', { presentation: [() => Doc.ActivePresentation?.presentation_status === 'auto', () => briefArtisticFeature], }); const deletePresentation = InfoState( "Cool! Click 'setOnClick to follow primary link' for your linked doc and try deleting the presentation.", { docRemoved: [ () => this._props.childDocs().length < docCounter, () => { docCounter -= 1; return activatePresentation; }, ], }, 'onclick-node.gif' ); const trailedPresentation = InfoState( 'See the new dragged-in presentation? Try linking it to the highlighted doc.', { linkAdd: [ () => Doc.Links(lastDocCreated)?.length > linkCounter, () => { linkCounter += 1; return deletePresentation; }, ], }, 'link-presentation.gif' ); const pinnedPresentation = InfoState( 'Want to see something cool? Click the trail button on the presentation and drag it inside the canvas.', { docAdded: [ () => this._props.childDocs().length > docCounter, () => { docCounter += 1; // Last doc that is not the presentation lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 2]; Doc.HighlightDoc(lastDocCreated); linkCounter = Doc.Links(lastDocCreated)?.length; return trailedPresentation; }, ], }, 'dash-trail-explanation.gif' ); const pinnedDoc2 = InfoState('You pinned another doc. Press play to the right to show your presentation!', { autoPresentation: [() => Doc.ActivePresentation?.presentation_status === 'auto', () => pinnedPresentation], }); const pinnedDoc = InfoState('You just pinned your doc. Pin another doc to add to the presentation!', { // eslint-disable-next-line no-use-before-define addedDoc: [ () => this._props.childDocs().length > docCounter, () => { docCounter += 1; lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; return pinnedDoc; }, ], docPinned: [ () => DocListCast(Doc.ActivePresentation?.data).length > presentationCounter, () => { presentationCounter++; // eslint-disable-next-line no-use-before-define return pinnedDoc2; }, ], }); const editLink = InfoState( "Want to make your link visible? Click 'show link'.", { docCreated: [ () => this._props.childDocs().length > docCounter, () => { docCounter += 1; lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; return this.tutorialStates.makePresentation; }, ], }, 'show-link.gif' ); const madeLink = InfoState( 'You made your first link! You can view your links by selecting the blue dot.', { linkViewed: [() => DocButtonState.Instance.LinkEditorDocView, () => editLink], }, 'dash-following-link.gif' ); const startedLink = InfoState( 'Now click the highlighted link icon on your other document.', { linkAdd: [ () => Doc.Links(lastDocCreated)?.length > linkCounter, () => { linkCounter += 1; return madeLink; }, ], }, 'dash-create-link-board.gif' ); this.tutorialStates.movedDoc = InfoState("Great moves! Try creating a second document.", { docCreated: [() => this._props.childDocs().length > docCounter, () => { docCounter += 1 lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] return this.tutorialStates.multipleDocs}], }, 'dash-colon-menu.gif'); // prettier-ignore this.tutorialStates.start = InfoState( 'Welcome to Dash! Click anywhere and begin typing to create your first text document.', { docCreated: [ () => this._props.childDocs().length > docCounter, () => { docCounter += 1; lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; return this.tutorialStates.movedDoc; }, ], }, undefined, [skipToLinksButton, skipToPinsButton, skipToPresentationButton] ); // Information on created nested collections const createdMarquee = InfoState( 'Next, right click and drag a square to create the collection', { // eslint-disable-next-line no-use-before-define marqueeMade: [ () => this._props.childDocs().length < docCounter, () => { docCounter -= 1; return ending; }, ], }, 'dash-create-collection-marquee.gif' ); const marqueeSelection = InfoState('Want an easier way to make a collection of docs? First add two docs you want to make a collection of', { marqueeMade: [ () => this._props.childDocs().length > docCounter, () => { docCounter += 1; lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1]; return createdMarquee; }, ], }); // Explanation of importing const easierImport = InfoState('Or, for easier access, you can drag any of the accepted file types from your computer or a webpage and drop it into your dashboard. This includes images, videos, audio, pdfs, and more!', {}, 'dash-', [ this.createNextButton(ending), ]); this.tutorialStates.importFile = InfoState('Want to learn how to import a file? Import using the import menu on the left hand side', {}, 'dash-import.gif', [this.createNextButton(easierImport)]); // Editing documents // Accessed by right-clicking anywhere on the target document or selecting the three bars menu at the bottom of the document chrome const extraContentsOfDoc = InfoState('Lastly, all documents also have a context-sensitive toolbar. The toolbar contents vary depending on the document type.', {}, 'context-toolbar.png', [this.createNextButton(ending)]); const contentsofDoc = InfoState('You can access the context of a doc through right-clicking anywhere on the target document or selecting the three bars menu at the bottom of the document chrome', {}, 'dash-context-menu.gif', [ this.createNextButton(extraContentsOfDoc), ]); const propertiesofDoc = InfoState('You can also access the properties of a doc through the double arrows in the top right or the single arrow on the right edge of the screen', {}, 'dash-properties-pane.gif', [ this.createNextButton(contentsofDoc), ]); this.tutorialStates.editingDocuments = InfoState('Want to learn how to edit a document? Either left or right click the document', {}, 'document-chrome.png', [this.createNextButton(propertiesofDoc)]); return this.tutorialStates.start; }; render() { if (!this.currState) return null; return ( ); } }