@@ -67,8 +67,10 @@ import { ModeManager } from "./ui/ModeManager.js";
6767import { MouseHandler } from "./ui/MouseHandler.js" ;
6868import { SelectionManager } from "./ui/SelectionManager.js" ;
6969import { SettingsDialog } from "./ui/SettingsDialog.js" ;
70+ import { TutorialWizard } from "./ui/TutorialWizard.js" ;
7071import { UserGroupEditor } from "./ui/UserGroupEditor.js" ;
7172import { PermissionEditor } from "./ui/PermissionEditor.js" ;
73+ import { tutorialSteps , mixerStepIndex } from "./core/TutorialSteps.js" ;
7274
7375const ScoreLibrary = lazy ( ( ) => {
7476 return import ( "./ui/ScoreLibrary.js" ) . then ( ( m ) => {
@@ -123,6 +125,7 @@ export class App extends UIComponent<{}, IAppState> {
123125 private userGroupEditorRef = createRef < UserGroupEditor > ( ) ;
124126 private permissionEditorRef = createRef < PermissionEditor > ( ) ;
125127 private printDialogRef = createRef < PrintDialog > ( ) ;
128+ private tutorialWizardRef = createRef < TutorialWizard > ( ) ;
126129 private valueDialogRef = createRef < ValueDialog > ( ) ;
127130 private confirmDialogRef = createRef < ConfirmDialog > ( ) ;
128131
@@ -146,6 +149,8 @@ export class App extends UIComponent<{}, IAppState> {
146149 private statsItem ?: IStatusBarItem ;
147150 private notificationItem ?: IStatusBarItem ;
148151
152+ private currentTutorialStep = 0 ;
153+
149154 public constructor ( props : { } ) {
150155 super ( props ) ;
151156
@@ -366,6 +371,7 @@ export class App extends UIComponent<{}, IAppState> {
366371 imageOnly
367372 className = "du-btn-ghost"
368373 data-tooltip = "Display Options"
374+ data-tutorial = "display-options"
369375 onClick = { this . handleDisplayOptionsClick }
370376 >
371377 < Icon
@@ -378,6 +384,7 @@ export class App extends UIComponent<{}, IAppState> {
378384 imageOnly
379385 className = "du-btn-ghost"
380386 data-tooltip = "Score Library"
387+ data-tutorial = "score-library"
381388 onClick = { this . handleScoreLibraryClick }
382389 >
383390 < Icon
@@ -391,6 +398,7 @@ export class App extends UIComponent<{}, IAppState> {
391398 imageOnly
392399 className = "du-btn-ghost"
393400 data-tooltip = "Print / Export to PDF"
401+ data-tutorial = "print"
394402 onClick = { this . handlePrintClick }
395403 >
396404 < Icon
@@ -441,6 +449,7 @@ export class App extends UIComponent<{}, IAppState> {
441449 dataModel = { this . dataModel }
442450 services = { this . services }
443451 undoManager = { this . undoManager ! }
452+ data-tutorial = "playback"
444453 />
445454 < Container
446455 id = "arrangementPalette"
@@ -489,6 +498,14 @@ export class App extends UIComponent<{}, IAppState> {
489498 < TooltipProvider />
490499 < ValueDialog ref = { this . valueDialogRef } />
491500 < SettingsDialog ref = { this . settingsDialogRef } />
501+ < TutorialWizard
502+ ref = { this . tutorialWizardRef }
503+ steps = { tutorialSteps }
504+ tutorialEnabled = { AppStorage . loadUISettings ( ) ?. tutorialEnabled ?? true }
505+ onTutorialEnabledChange = { this . handleTutorialEnabledChange }
506+ onStepChange = { this . handleTutorialStepChange }
507+ onClose = { this . handleTutorialClose }
508+ />
492509 < BackendDisconnectedDialog
493510 ref = { this . backendDisconnectedDialogRef }
494511 onReconnected = { ( ) => {
@@ -819,6 +836,25 @@ export class App extends UIComponent<{}, IAppState> {
819836
820837 const params = new URL ( window . location . href ) . searchParams ;
821838 const hasBananaDrum = params . has ( "a" ) || params . has ( "a2" ) ;
839+ const hasScoreParam = params . has ( "score" ) ;
840+
841+ const showTutorial = ! hasBananaDrum && ! hasScoreParam
842+ && ( AppStorage . loadUISettings ( ) ?. tutorialEnabled ?? true ) ;
843+
844+ if ( showTutorial ) {
845+ this . initAppState ( ) ;
846+ this . setState ( { phase : AppPhase . Running } , ( ) => {
847+ this . tutorialWizardRef . current ?. open ( ) ;
848+ } ) ;
849+
850+ return ;
851+ }
852+
853+ await this . loadInitialScore ( params ) ;
854+ }
855+
856+ private async loadInitialScore ( params : URLSearchParams ) : Promise < void > {
857+ const hasBananaDrum = params . has ( "a" ) || params . has ( "a2" ) ;
822858
823859 let pendingWarning : string | undefined ;
824860
@@ -862,6 +898,40 @@ export class App extends UIComponent<{}, IAppState> {
862898 } ) ;
863899 }
864900
901+ private handleTutorialClose = ( completed : boolean ) : void => {
902+ this . tutorialWizardRef . current ?. close ( completed ) ;
903+ this . loadScorebook ( undefined ) ;
904+ } ;
905+
906+ private handleTutorialStepChange = ( stepIndex : number ) : void => {
907+ const prevStep = this . currentTutorialStep ;
908+ this . currentTutorialStep = stepIndex ;
909+
910+ if ( stepIndex === mixerStepIndex ) {
911+ this . toggleMixerIf ( ! this . isMixerExpanded ( ) ) ;
912+ }
913+
914+ if ( prevStep === mixerStepIndex && stepIndex !== mixerStepIndex ) {
915+ this . toggleMixerIf ( this . isMixerExpanded ( ) ) ;
916+ }
917+ } ;
918+
919+ private isMixerExpanded ( ) : boolean {
920+ return document . querySelector ( ".trackControlsList" ) ?. classList . contains ( "expanded" ) ?? false ;
921+ }
922+
923+ private toggleMixerIf ( condition : boolean ) : void {
924+ if ( ! condition ) {
925+ return ;
926+ }
927+
928+ document . querySelector < HTMLElement > ( ".trackControlsToggle" ) ?. click ( ) ;
929+ }
930+
931+ private handleTutorialEnabledChange = ( enabled : boolean ) : void => {
932+ AppStorage . saveSetting ( "tutorialEnabled" , enabled ) ;
933+ } ;
934+
865935 private handleGithubClick = ( ) => {
866936 window . open ( "https://github.com/mike-lischke/animada-score-book" , "_blank" ) ;
867937 } ;
@@ -1317,7 +1387,12 @@ export class App extends UIComponent<{}, IAppState> {
13171387 return true ;
13181388 } ;
13191389
1320- private loadScorebook ( source ?: URLSearchParams | ISbDmScore ) {
1390+ private initAppState ( ) : void {
1391+ this . undoManager = new UndoManager ( this . dataModel ) ;
1392+ this . arrangementPlayer = new ArrangementPlayer ( this . dataModel ) ;
1393+ }
1394+
1395+ private loadScorebook ( source ?: IArrangementSnapshot | URLSearchParams | ISbDmScore ) {
13211396 let resolvedSource : IArrangementSnapshot | URLSearchParams | ISbDmScore | undefined ;
13221397
13231398 if ( source ) {
@@ -1362,7 +1437,8 @@ export class App extends UIComponent<{}, IAppState> {
13621437 document . title = arrangement . title + " - Animada Score Book" ;
13631438 }
13641439
1365- AppStorage . saveSetting ( "currentScore" , stringifyPackedArrangement ( ( arrangement as Arrangement ) . toSnapshot ( ) ) ,
1440+ AppStorage . saveSetting ( "currentScore" ,
1441+ stringifyPackedArrangement ( ( arrangement as Arrangement ) . toSnapshot ( ) ) ,
13661442 ) ;
13671443
13681444 this . forceUpdate ( ) ;
0 commit comments