Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions .github/PULL_REQUEST_DRAFT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# feat: Add Interactive First Project Tutorial with Step Validation

## Summary

This PR introduces an **interactive step-by-step tutorial** that guides new users through creating their first Music Blocks project. Unlike the existing static help tour where users simply click through slides, this tutorial **validates user actions** and only allows progression when each step is completed.

## Problem

New users often struggle with Music Blocks' learning curve:
- They don't understand the block-based programming paradigm
- They can't find where to start
- They get overwhelmed by the many palettes and options
- The existing help tour shows information but doesn't ensure users actually perform the actions

## Solution

An interactive tutorial system featuring:

### Core Features
- **10 guided steps** - From finding the Start block to playing your first melody
- **Action validation** - The "Next" button only enables when the user completes the required action
- **Visual highlighting** - Spotlight effect focuses attention on the relevant UI element
- **Real-time feedback** - Status indicators show pending (⏳) vs completed (✅) actions
- **Smart overlay** - Reduced opacity during drag-and-drop steps to allow full canvas/palette interaction

### Tutorial Flow
| Step | Task | Validation |
|------|------|------------|
| 1 | Find the Start Block | Auto-complete (observation) |
| 2 | Open Rhythm Palette | Detects palette is open |
| 3 | Drag a Note Block | Counts new blocks on canvas |
| 4 | Connect Note to Start | Checks block connections |
| 5 | Press Play | Detects play button click |
| 6 | Open Pitch Palette | Detects palette is open |
| 7 | Add Pitch to Note | Traverses block hierarchy |
| 8 | Play Again | Detects play button click |
| 9 | Add More Notes | Optional, auto-complete |
| 10 | Congratulations | Tutorial complete |

## Changes

### New Files
- `js/tutorial/FirstProjectTutorial.js` - The main interactive tutorial class with:
- Step definitions with validators
- Overlay/spotlight/tooltip management
- Action detection methods for palettes, blocks, and connections

### Modified Files
- `js/widgets/help.js` - Added "🚀 Start Interactive Tutorial" button on the First Project card
- `js/turtledefs.js` - Added tutorial introduction cards to HELPCONTENT
- `index.html` - Added script reference for FirstProjectTutorial.js

## Screenshots

<!--
TODO: Add screenshots showing:
1. The tutorial tooltip with spotlight highlighting
2. Step 3 with the reduced overlay for dragging
3. The completion indicator (✅) after an action
4. The final congratulations step
-->

## Demo

<!--
TODO: Add a GIF or video showing the tutorial in action
You can use tools like ScreenToGif or LICEcap to record
-->

## Technical Details

### Validator Architecture
Each tutorial step has a `validator` function that returns `true` when the action is complete:

```javascript
{
title: _("Step 3: Drag a Note Block"),
content: _("Find the 'Note' block and drag it onto the canvas."),
target: () => this._getCanvas(),
validator: () => this._hasMoreBlocks(),
allowInteraction: true, // Reduces overlay for drag-and-drop
onStart: () => { this._initialNoteCount = this._countBlocksByName("newnote"); }
}
```

### Key Methods
- `_isPaletteOpen(paletteName)` - Checks if a specific palette is currently open
- `_hasMoreBlocks()` - Detects if new blocks were added since step started
- `_isBlockConnectedToStart(blockName)` - Verifies block connections
- `_hasPitchInNote()` - Traverses block hierarchy to find pitch inside note

### Interaction Handling
Steps that require dragging blocks set `allowInteraction: true`, which:
- Reduces overlay opacity from 60% to 20%
- Ensures spotlight doesn't block mouse events
- Allows users to interact with both palettes and canvas

## Testing

### Manual Testing Checklist
- [x] Step 1: Start block is visible on canvas
- [x] Step 2: Clicking Rhythm palette enables Next
- [x] Step 3: Dragging Note block to canvas enables Next
- [x] Step 4: Connecting Note to Start enables Next
- [x] Step 5: Clicking Play enables Next
- [x] Step 6: Clicking Pitch palette enables Next
- [x] Step 7: Dragging Pitch inside Note enables Next
- [x] Step 8: Clicking Play again enables Next
- [x] Step 9: Auto-completes (optional step)
- [x] Step 10: Shows congratulations, Finish closes tutorial

### Edge Cases
- [x] Tutorial can be closed at any time via X button
- [x] Back button navigates to previous steps
- [x] Validators work with existing blocks on canvas
- [ ] Mobile/tablet touch interactions (needs testing)

## Accessibility

- Uses semantic HTML for buttons
- Clear visual indicators for current step
- Close button available at all times
- Future improvement: Add keyboard navigation (Esc to close, arrow keys)

## Browser Compatibility

Tested on:
- [x] Chrome (latest)
- [ ] Firefox (needs testing)
- [ ] Safari (needs testing)
- [ ] Edge (needs testing)

## Related Issues

This PR addresses the need for better new user onboarding in Music Blocks.

## Checklist

- [x] Code follows project style guidelines
- [x] Self-reviewed the code
- [x] Added comments for complex logic
- [x] No console errors during tutorial
- [ ] Added screenshots/demo
- [ ] Tested on multiple browsers

---

**Note:** This is a draft PR. Please add screenshots and complete the testing checklist before marking as ready for review.
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
decoding and drawing GIF frames onto the turtle's overlay canvas.
Canvas refreshing is handled separately by CreateJS Ticker. -->
<script src="js/gif-animator.js" defer></script>
<!-- FirstProjectTutorial.js provides interactive step-by-step onboarding
tutorial that guides new users through creating their first project. -->
<script src="js/tutorial/FirstProjectTutorial.js" defer></script>
<script defer>
document.addEventListener("DOMContentLoaded", function () {
if (window.hljs) hljs.highlightAll();
Expand Down
117 changes: 117 additions & 0 deletions js/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -3033,6 +3033,103 @@ class Activity {
this.update = true;
};

/*
* Makes initial "start up" note for a brand new MB project
*/
this.__makeNewNote = (octave, solf) => {
const newNote = [
[
0,
"newnote",
300 - this.blocksContainer.x,
300 - this.blocksContainer.y,
[null, 1, 4, 8]
],
[1, "divide", 0, 0, [0, 2, 3]],
[
2,
[
"number",
{
value: 1
}
],
0,
0,
[1]
],
[
3,
[
"number",
{
value: 4
}
],
0,
0,
[1]
],
[4, "vspace", 0, 0, [0, 5]],
[5, "pitch", 0, 0, [4, 6, 7, null]],
[
6,
[
"solfege",
{
value: solf
}
],
0,
0,
[5]
],
[
7,
[
"number",
{
value: octave
}
],
0,
0,
[5]
],
[8, "hidden", 0, 0, [0, null]]
];

this.blocks.loadNewBlocks(newNote);
if (this.blocks.activeBlock !== null) {
// Connect the newly created block to the active block (if
// it is a hidden block at the end of a new note block).
const bottom = this.blocks.findBottomBlock(this.blocks.activeBlock);
if (
this.blocks.blockList[bottom].name === "hidden" &&
this.blocks.blockList[this.blocks.blockList[bottom].connections[0]].name ===
"newnote"
) {
// The note block macro creates nine blocks.
const newlyCreatedBlock = this.blocks.blockList.length - 9;

// Set last connection of active block to the
// newly created block.
const lastConnection = this.blocks.blockList[bottom].connections.length - 1;
this.blocks.blockList[bottom].connections[lastConnection] = newlyCreatedBlock;

// Set first connection of the newly created block to
// the active block.
this.blocks.blockList[newlyCreatedBlock].connections[0] = bottom;
// Adjust the dock positions to realign the stack.
this.blocks.adjustDocks(bottom, true);
}
}

// Set new hidden block at the end of the newly created
// note block to the active block.
this.blocks.activeBlock = this.blocks.blockList.length - 1;
};

//To create a sampler widget
this.makeSamplerWidget = (sampleName, sampleData) => {
const samplerStack = [
Expand Down Expand Up @@ -6325,6 +6422,14 @@ class Activity {
new HelpWidget(this, false);
};

/**
* Open the First Project Tutorial directly (starts at card 37)
* This can be called from anywhere to launch the tutorial
*/
this.openFirstProjectTutorial = () => {
HelpWidget.openFirstProjectTutorial(this);
};

/*
* Shows about page
*/
Expand Down Expand Up @@ -7663,6 +7768,18 @@ class Activity {
}

const activity = new Activity();
// Expose activity globally for tutorials and widgets
window.activity = activity;

// Global function to open First Project Tutorial (starts at card 37)
// Can be called from console or anywhere: openFirstProjectTutorial()
window.openFirstProjectTutorial = function () {
if (activity && activity.openFirstProjectTutorial) {
activity.openFirstProjectTutorial();
} else if (typeof HelpWidget !== "undefined") {
HelpWidget.openFirstProjectTutorial(activity);
}
};

// Execute initialization once all RequireJS modules are loaded AND DOM is ready
define(["domReady!"].concat(MYDEFINES), doc => {
Expand Down
Loading
Loading