diff options
Diffstat (limited to 'src/client/views/collections')
4 files changed, 387 insertions, 237 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormController.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormController.ts new file mode 100644 index 000000000..6752b46b8 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormController.ts @@ -0,0 +1,7 @@ +import { CollectionFreeFormView } from './CollectionFreeFormView'; + +export class TutorialController { + public static startTutorial(kind: 'links' | 'pins' | 'presentation') { + CollectionFreeFormView.showTutorial(kind); + } +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 437888ef2..8ed3e8e30 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { SettingsManager } from '../../../util/SettingsManager'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import './CollectionFreeFormView.scss'; +import { Button } from '../../../util/CurrentUserUtils'; /** * An Fsa Arc. The first array element is a test condition function that will be observed. @@ -17,16 +18,26 @@ export type infoArc = [() => unknown, (res?: unknown) => infoState]; export const StateMessage = Symbol('StateMessage'); export const StateMessageGIF = Symbol('StateMessageGIF'); export const StateEntryFunc = Symbol('StateEntryFunc'); +export const StateMessageButton = Symbol('StateMessageButton'); export class infoState { [StateMessage]: string = ''; [StateMessageGIF]?: string = ''; + [StateMessageButton]?: Button[]; [StateEntryFunc]?: () => unknown; [key: string]: infoArc; - constructor(message: string, arcs: { [key: string]: infoArc }, messageGif?: string, entryFunc?: () => unknown) { + + constructor( + message: string, + arcs: { [key: string]: infoArc }, + messageGif?: string, + buttons?: Button[], + entryFunc?: () => unknown + ) { this[StateMessage] = message; Object.assign(this, arcs); this[StateMessageGIF] = messageGif; this[StateEntryFunc] = entryFunc; + this[StateMessageButton] = buttons; } } @@ -44,14 +55,15 @@ export function InfoState( msg: string, // arcs: { [key: string]: infoArc }, gif?: string, + button?: Button[], entryFunc?: () => unknown ) { - return new infoState(msg, arcs, gif, entryFunc); + return new infoState(msg, arcs, gif, button, entryFunc); } export interface CollectionFreeFormInfoStateProps { infoState: infoState; - next: (state: infoState) => unknown; + next: (state: infoState) => unknown; // Ensure it's properly defined close: () => void; } @@ -68,6 +80,10 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec get State() { return this._props.infoState; } + + set State(value: infoState) { + this._props.infoState = value; + } get Arcs() { return Object.keys(this.State ?? []).map(key => this.State?.[key]); } @@ -97,6 +113,9 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec render() { const gif = this.State?.[StateMessageGIF]; + const buttons = this.State?.[StateMessageButton]; + console.log("Rendering CollectionFreeFormInfoState with state:", this.props.infoState); + console.log(buttons) return ( <div className="collectionFreeform-infoUI"> <p className="collectionFreeform-infoUI-msg"> @@ -110,11 +129,36 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec {this._expanded ? 'Less...' : 'More...'} </button> </p> + <div className={'collectionFreeform-' + (!this._expanded || !gif ? 'hidden' : 'infoUI-gif-container')}> <img src={`/assets/${gif}`} alt="state message gif" /> </div> + + {/* Render the buttons for skipping */} + <div className={'collectionFreeform-' + (!buttons || buttons.length === 0 ? 'hidden' : 'infoUI-button-container')}> + {buttons?.map((button, index) => ( + <button + key={index} + type="button" + className="collectionFreeform-infoUI-skip-button" + onClick={action(() => { + console.log("Attempting transition to:", button.targetState); + this.props.next(button.targetState as infoState); // ✅ Use the prop instead + })}> + {button.title} + </button> + ))} + </div> + <div className="collectionFreeform-infoUI-close"> - <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(() => this.props.close())} /> + <IconButton + icon="x" + color={SettingsManager.userColor} + size={Size.XSMALL} + type={Type.TERT} + background={SettingsManager.userBackgroundColor} + onClick={action(() => this.props.close())} + /> </div> </div> ); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 89d2bf2c3..efc22f523 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -1,284 +1,343 @@ 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 { Doc } from '../../../../fields/Doc'; 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 './CollectionFreeFormView.scss'; +import { CollectionFreeFormInfoState, infoState } from './CollectionFreeFormInfoState'; +import { Button } from '../../../util/CurrentUserUtils'; +import { InfoState } from './CollectionFreeFormInfoState'; +import { DocButtonState } from '../../nodes/DocumentLinksButton'; +import { InkTool } from '../../../../fields/InkField'; +import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; +import { CollectionFreeFormView } from '.'; +import { DocListCast } from '../../../../fields/Doc'; +import { ButtonType } from '../../nodes/FontIconBox/FontIconBox'; export interface CollectionFreeFormInfoUIProps { - Doc: Doc; - layoutDoc: Doc; + Document: Doc; + LayoutDoc: Doc; childDocs: () => Doc[]; close: () => void; } @observer export class CollectionFreeFormInfoUI extends ObservableReactComponent<CollectionFreeFormInfoUIProps> { + _originalBackground: string | undefined; + private tutorialStates: { [key: string]: infoState } = {}; + public static Init() { - CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => ( - // - <CollectionFreeFormInfoUI Doc={doc} layoutDoc={layout} childDocs={childDocs} close={close} /> - )); + CollectionFreeFormView.SetInfoUICreator( + (doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => + <CollectionFreeFormInfoUI + Document={doc} + LayoutDoc={layout} + childDocs={childDocs} + close={close} + /> + ); } - _firstDocPos = { x: 0, y: 0 }; constructor(props: CollectionFreeFormInfoUIProps) { super(props); makeObservable(this); - this._currState = this.setupStates(); + this.tutorialStates = {}; // Initialize an empty object + this.currState = this.setupStates(); // Call setupStates() here } - _originalbackground: string | undefined; + @observable _currState: infoState | undefined = undefined; - get currState() { return this._currState; } // prettier-ignore - set currState(val) { runInAction(() => {this._currState = val;}); } // prettier-ignore + @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; + this._props.Document.backgroundColor = this._originalBackground; } - setCurrState = (state: infoState) => { - if (state) { - this.currState = state; - this.currState[StateEntryFunc]?.(); + skipToState = (newState: infoState) => { + runInAction(() => { + if (!this._currState) { + this._currState = newState; // Assign directly if undefined + } else { + this._currState = 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<FieldType>; - let docY: FieldResult<FieldType>; - - 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 - docCreated: [() => numDocs() === 2, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], + createNextButton = (newState: ReturnType<typeof InfoState>) => { + return { + title: "Next", + toolTip: "Next", + btnType: ButtonType.ClickButton, + scripts: { + onClick: `this.skipToState(${newState})` }, - 'dash-colon-menu.gif', - () => TopBar.Instance.FlipDocumentationIcon() - ); // prettier-ignore + targetState: newState + }; + }; - const multipleDocs = InfoState( - 'Let\'s create a new link. Click the link icon on one of your documents.', - { + setupStates = () => { + let docCounter = this._props.childDocs().length; + let lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 1] + let linkCounter = Doc.Links(lastDocCreated)?.length; + let presentationCounter = DocListCast(Doc.ActivePresentation?.data).length + this._originalBackground = this._props.Document.backgroundColor as string; + + this.tutorialStates.multipleDocs = InfoState("Let's create a new link! Click the link icon on one document and connect it to another.", { + // eslint-disable-next-line no-use-before-define + linkStarted: [() => DocumentLinksButton.StartLink, () => { + linkCounter = Doc.Links(lastDocCreated).length // eslint-disable-next-line no-use-before-define - linkStarted: [() => linkStart(), () => startedLink], - docRemoved: [() => numDocs() < 2, () => oneDoc], + return startedLink}], + // 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.makePresentation}] }, 'dash-create-link-board.gif' - ); // prettier-ignore + ); - const startedLink = InfoState( - 'Now click the highlighted link icon on your other document.', - { - linkUnstart: [() => linkUnstart(), () => multipleDocs], + this.tutorialStates.presentDocs = InfoState("Select a document then click the 'pin' button in the top left to create your presentation.", { // eslint-disable-next-line no-use-before-define - linkCreated: [() => numDocLinks(), () => madeLink], - docRemoved: [() => numDocs() < 2, () => oneDoc], - }, - 'dash-create-link-board.gif' - ); // prettier-ignore - - const madeLink = InfoState( - 'You made your first link! You can view your links by selecting the blue dot.', - { - linkCreated: [() => !numDocLinks(), () => multipleDocs], - linkViewed: [() => linkMenuOpen(), () => { - alert(numDocLinks() + " cheer for " + numDocLinks() + " link!"); - // eslint-disable-next-line no-use-before-define - return viewedLink; - }], - }, - 'dash-following-link.gif' - ); // prettier-ignore - - const viewedLink = InfoState( - 'Great work. You are now ready to create your own hypermedia world. Click the ? icon in the top right corner to learn more.', - { - linkDeleted: [() => !numDocLinks(), () => multipleDocs], - docRemoved: [() => numDocs() < 2, () => oneDoc], - docCreated: [() => numDocs() === 3, () => { - trail = pin().length; - // eslint-disable-next-line no-use-before-define - return presentDocs; - }], + docPinned: [() => DocListCast(Doc.ActivePresentation?.data).length > presentationCounter, () => { + presentationCounter++ // eslint-disable-next-line no-use-before-define - activePen: [() => activeTool() === InkTool.Ink, () => penMode], + return pinnedDoc}], }, - 'documentation.png', - () => TopBar.Instance.FlipDocumentationIcon() - ); // prettier-ignore - - const presentDocs = InfoState( - 'Another document! You could make a presentation. Click the pin icon in the top left corner.', - { - docPinned: [ - () => pin().length > trail, - () => { - trail = pin().length; - // eslint-disable-next-line no-use-before-define - return pinnedDoc1; - }, - ], - docRemoved: [() => numDocs() < 3, () => viewedLink], - }, - '/assets/dash-pin-with-view.gif' - ); + 'pin-explanation.gif'); - const penMode = InfoState('You\'re in pen mode. Click and drag to draw your first masterpiece.', { - // activePen: [() => activeTool() === InkTool.Eraser, () => eraserMode], - activePen: [() => activeTool() !== InkTool.Ink, () => viewedLink], - }); // prettier-ignore - - // const eraserMode = InfoState('You\'re in eraser mode. Say goodbye to your first masterpiece.', { - // docsRemoved: [() => numDocs() == 3, () => demos], - // }); // prettier-ignore - - const pinnedDoc1 = InfoState('You just pinned your doc.', { - docPinned: [ - () => pin().length > trail, - () => { - trail = pin().length; - // eslint-disable-next-line no-use-before-define - return pinnedDoc2; - }, - ], - // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], - // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + 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 - autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], - docRemoved: [() => numDocs() < 3, () => viewedLink], - }); - - const pinnedDoc2 = InfoState(`You pinned another doc.`, { - docPinned: [ - () => pin().length > trail, - () => { - trail = pin().length; - // eslint-disable-next-line no-use-before-define - return pinnedDoc3; - }, - ], - // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], - // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], + 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 - autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], - docRemoved: [() => numDocs() < 3, () => viewedLink], - }); + 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 pinnedDoc3 = InfoState(`You pinned yet another doc.`, { - docPinned: [ - () => pin().length > trail, - () => { - trail = pin().length; - return pinnedDoc2; - }, - ], - // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], - // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], - // eslint-disable-next-line no-use-before-define - autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], - docRemoved: [() => numDocs() < 3, () => viewedLink], - }); + const ending = InfoState("If you have any more questions, feel free to ask Dash's AI Bot!") - // const openedTrail = InfoState('This is your trails tab.', { - // trailView: [() => presentationMode() === 'edit', () => editPresentationMode], - // }); + // Traditional tutorial - // const editPresentationMode = InfoState('You are editing your presentation.', { - // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], - // autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], - // docRemoved: [() => numDocs() < 3, () => demos], - // docCreated: [() => numDocs() == 4, () => completed], - // }); + 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 manualPresentationMode = InfoState("You're in manual presentation mode.", { - // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], - // eslint-disable-next-line no-use-before-define - autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode], - docRemoved: [() => numDocs() < 3, () => viewedLink], - // eslint-disable-next-line no-use-before-define - docCreated: [() => numDocs() === 4, () => completed], + 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 non-presentation doc and try deleting the presentation.", { + docRemoved: [() => this._props.childDocs().length < docCounter, () => { + docCounter -= 1 + return activatePresentation}], + }, + 'onclick-node.gif'); + + const trailedPresentation = InfoState("Try linking your presentation to the last doc you created (now highlighted).", { + linkAdd: [() => Doc.Links(lastDocCreated)?.length > linkCounter, () => { + linkCounter += 1 + return deletePresentation + }], + docAdded: [() => this._props.childDocs().length > docCounter, () => { + docCounter += 1 + // Last doc that is not the presentation + lastDocCreated = this._props.childDocs()[this.props.childDocs().length - 2] + linkCounter = Doc.Links(lastDocCreated)?.length + return deletePresentation}] + }, + 'link-presentation.gif'); + + const pinnedPresentation = InfoState("Want to see something cool? Zoom out, 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 autoplay to the right to show your presentation!", { + autoPresentation: [() => Doc.ActivePresentation?.presentation_status === "auto", () => pinnedPresentation], }); - const autoPresentationMode = InfoState("You're in auto presentation mode.", { - // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode], - manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode], - docRemoved: [() => numDocs() < 3, () => viewedLink], + const pinnedDoc = InfoState("You just pinned your doc. Pin another doc to add to the presentation!", { // eslint-disable-next-line no-use-before-define - docCreated: [() => numDocs() === 4, () => completed], + 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 completed = InfoState( - 'Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', - { docRemoved: [() => numDocs() === 1, () => oneDoc] }, - 'documentation.png', - () => TopBar.Instance.FlipDocumentationIcon() - ); // prettier-ignore - - return start; + + 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, () => { + docCounter = this._props.childDocs().length + return 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 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]) + + // 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() { - return !this.currState ? null : <CollectionFreeFormInfoState next={this.setCurrState} close={this._props.close} infoState={this.currState} />; + if (!this.currState) return null; + + return ( + <CollectionFreeFormInfoState + next={this.skipToState} // This ensures skipToState is passed correctly + close={this._props.close} + infoState={this.currState} + /> + ); } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6e9e503f4..a447a6ae4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -93,6 +93,8 @@ export interface collectionFreeformViewProps { @observer export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() { + private static _infoUIInstance: CollectionFreeFormInfoUI | null = null; + public get displayName() { return 'CollectionFreeFormView(' + (this.Document.title?.toString() ?? '') + ')'; } // this makes mobx trace() statements more descriptive @@ -1754,11 +1756,49 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection static SetInfoUICreator(func: (doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => JSX.Element) { CollectionFreeFormView._infoUI = func; } - infoUI = () => + /** + * Called from TutorialTool in Agent system + */ + public static showTutorial(kind: 'links' | 'pins' | 'presentation') { + const ui = CollectionFreeFormView._infoUIInstance; + if (!ui) return; + switch (kind) { + case 'links': + ui.skipToState((ui).tutorialStates.multipleDocs); + ui._nextState + break; + case 'pins': + ui.skipToState((ui).tutorialStates.presentDocs); + ui._nextState + break; + case 'presentation': + ui.skipToState((ui).tutorialStates.makePresentation); + ui._nextState + break; + } + } + + infoUI = () => { Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null // : CollectionFreeFormView._infoUI?.(this.Document, this.layoutDoc, this.childDocsFunc, this.closeInfo) || null; + if (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth) { + return null; + } + const creator = CollectionFreeFormView._infoUI; + if (!creator) return null; + const element = creator(this.Document, this.layoutDoc, this.childDocsFunc, this.closeInfo); + // attach ref so we can call skipToState(...) later + return React.isValidElement(element) + ? React.cloneElement(element, { + ref: (r: CollectionFreeFormInfoUI) => { + CollectionFreeFormView._infoUIInstance = r; + } + }) + : element; + + }; componentDidMount() { this._props.setContentViewBox?.(this); super.componentDidMount?.(); |
