Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
75017ed
feat: Add overlay option for image node
pkulkark Nov 18, 2025
06e40d3
refactor: change choices to radio single choice
pkulkark Nov 18, 2025
e35a6cf
fix: button appearance
pkulkark Nov 18, 2025
3f6f546
refactor: replace hint settings with reset option
pkulkark Jan 29, 2026
62f601b
refactor: updated studio editor
pkulkark Jan 29, 2026
8e9ab8f
feat: composite images + styling
pkulkark Jan 30, 2026
d074d83
fix: styling changes
pkulkark Feb 5, 2026
5e0a83f
feat: Add scores to each choice
pkulkark Feb 6, 2026
cbc7212
fix: update styling
pkulkark Feb 9, 2026
aa9b8a4
fix: hide choices only if no branches flag is selected
pkulkark Feb 9, 2026
e36d8ab
feat: add soft delete for nodes
pkulkark Feb 9, 2026
fdb2ad9
feat: Add save validation for fields
pkulkark Feb 10, 2026
d33cb3d
fix: disable leaf flag if choices are added
pkulkark Feb 10, 2026
982c931
feat: display unlinked nodes
pkulkark Feb 10, 2026
ab2770c
feat: replace hint settings with reset option
pkulkark Feb 6, 2026
8e5d707
fix: update styling
pkulkark Feb 6, 2026
c05093e
fix: update tests
pkulkark Feb 6, 2026
a44a971
fix: refactoring errors
pkulkark Feb 11, 2026
15e2995
fix: Prevent cyclic nodes
pkulkark Feb 12, 2026
cd5b7c6
feat: compute maximum attainable score in scenario
pkulkark Feb 13, 2026
c53152d
feat: Add grade-range slider
pkulkark Feb 13, 2026
549df1b
feat: Final grade report
pkulkark Feb 17, 2026
a46658b
fix: clean up + fix regressions
pkulkark Feb 19, 2026
f797f27
fix: Add test cases
pkulkark Feb 19, 2026
febfb7b
Merge origin/main into pooja/bb10108-implement-renpy
pkulkark Feb 19, 2026
acc3151
fix: CI errors
pkulkark Feb 19, 2026
aa3e3e7
refactor: cleanup redundant functions and improve validation flow
pkulkark Feb 27, 2026
8fbc77f
fix: minor fixes
pkulkark Mar 5, 2026
855077c
fix: Refine score handling and validation feedback
pkulkark Mar 11, 2026
ab9c996
fix: inconsistent validation errors
pkulkark Mar 11, 2026
c1540a9
fix: adjust styling
pkulkark Mar 11, 2026
b1bfc1a
fix: minor fixes
pkulkark Mar 12, 2026
2f3b7a0
fix: adjust button styling
pkulkark Mar 12, 2026
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
918 changes: 819 additions & 99 deletions branching_xblock/branching_xblock.py

Large diffs are not rendered by default.

601 changes: 559 additions & 42 deletions branching_xblock/static/css/branching_xblock.css

Large diffs are not rendered by default.

706 changes: 557 additions & 149 deletions branching_xblock/static/css/studio_editor.css

Large diffs are not rendered by default.

40 changes: 33 additions & 7 deletions branching_xblock/static/handlebars/choice-row.handlebars
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
<div class="choice-row" data-choice-idx="{{i}}">
<input class="choice-text" value="{{choice.text}}" placeholder="Choice text"/>
<select class="choice-target">
{{#each options}}
<option value="{{this.id}}" {{#if (eq this.id ../choice.target_node_id)}}selected{{/if}}>{{this.label}}</option>
{{/each}}
</select>
<button type="button" class="btn-delete-choice">x</button>
<div class="choice-col choice-col--text">
<label class="choice-col__label">Choice text</label>
<input class="choice-text" value="{{choice.text}}" placeholder="Choice text"/>
</div>
<div class="choice-col choice-col--score">
<label class="choice-col__label">Score</label>
<input
class="choice-score {{#if score_error}}is-error{{/if}}"
type="number"
min="0"
max="100"
step="1"
value="{{choice.score}}"
placeholder="0"
aria-label="Choice score"
/>
{{#if score_error}}
<div class="choice-field-error">{{score_error}}</div>
{{/if}}
</div>
<div class="choice-col choice-col--target">
<label class="choice-col__label">Destination*</label>
<select class="choice-target {{#if destination_error}}is-error{{/if}}">
<option value="" {{#unless choice.target_node_id}}selected{{/unless}}>Select node</option>
{{#each options}}
<option value="{{this.id}}" {{#if (eq this.id ../choice.target_node_id)}}selected{{/if}}>{{this.label}}</option>
{{/each}}
</select>
{{#if destination_error}}
<div class="choice-field-error">{{destination_error}}</div>
{{/if}}
</div>
<button type="button" class="btn-delete-choice" aria-label="Delete choice">×</button>
</div>
7 changes: 7 additions & 0 deletions branching_xblock/static/handlebars/node-block.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
</label>
</div>
</label>
<div class="overlay-text-control {{#unless (eq node.media.type 'image')}}is-hidden{{/unless}}">
<label class="overlay-text-toggle">
<input type="checkbox" class="overlay-text-checkbox" {{#if node.overlay_text}}checked{{/if}}>
Overlay text on image
</label>
<p class="overlay-text-help">If left unchecked, text will appear outside the image.</p>
</div>
<label>Hint:<br/>
<textarea class="node-hint">{{node.hint}}</textarea>
</label>
Expand Down
85 changes: 85 additions & 0 deletions branching_xblock/static/handlebars/node-editor.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<div class="bx-node-editor-inner" data-node-id="{{id}}">
{{#if node_error_detail}}
<div class="bx-node-error-banner" role="alert">
<span class="fa fa-exclamation-circle bx-node-error-banner__icon" aria-hidden="true"></span>
<div class="bx-node-error-banner__content">
<div class="bx-node-error-banner__title">
{{#if node_error_title}}{{node_error_title}}{{else}}You can't delete this node{{/if}}
</div>
<div class="bx-node-error-banner__detail">{{node_error_detail}}</div>
</div>
</div>
{{/if}}

<div class="bx-node-editor-header">
<h2 class="bx-step-title">Node {{number}}</h2>
{{#if is_pending_delete}}
<span class="bx-pending-delete-badge">Pending deletion</span>
{{/if}}
</div>

<label class="bx-field">
<div class="bx-field__label">Content</div>
<textarea class="bx-textarea" data-role="node-content">{{content}}</textarea>
</label>

<label class="bx-field">
<div class="bx-field__label">Media</div>
<select class="bx-select" data-role="media-type">
<option value="" {{#unless media.type}}selected{{/unless}}>None</option>
<option value="image" {{#if (eq media.type 'image')}}selected{{/if}}>Image</option>
<option value="video" {{#if (eq media.type 'video')}}selected{{/if}}>Video</option>
<option value="audio" {{#if (eq media.type 'audio')}}selected{{/if}}>Audio</option>
</select>
</label>

<div class="{{#unless is_image}}is-hidden{{/unless}}" data-role="image-url-fields">
<label class="bx-field">
<div class="bx-field__label">Left image URL</div>
<input type="text" class="bx-input {{#if left_image_url_error}}is-error{{/if}}" data-role="left-image-url" placeholder="URL" value="{{left_image_url}}" />
{{#if left_image_url_error}}
<div class="bx-field-error">{{left_image_url_error}}</div>
{{/if}}
</label>
<label class="bx-field">
<div class="bx-field__label">Right image URL</div>
<input type="text" class="bx-input" data-role="right-image-url" placeholder="URL" value="{{right_image_url}}" />
</label>
</div>

<label class="bx-field {{#unless show_media_url}}is-hidden{{/unless}}" data-role="media-url-field">
<div class="bx-field__label">URL</div>
<input type="text" class="bx-input" data-role="media-url" placeholder="URL" value="{{media.url}}" />
<div class="bx-help">Supports direct media files (.mp4/.webm/.mp3) or links from YouTube, Vimeo, Panopto.</div>
</label>

<label class="bx-field {{#unless show_transcript}}is-hidden{{/unless}}" data-role="transcript-url-field">
<div class="bx-field__label">Transcript URL</div>
<input type="text" class="bx-input" data-role="transcript-url" placeholder="https://..." value="{{transcript_url}}" />
</label>

<div class="overlay-text-control {{#unless show_overlay}}is-hidden{{/unless}}" data-role="overlay-text-control">
<label class="overlay-text-toggle">
<input type="checkbox" class="overlay-text-checkbox" data-role="overlay-text" {{#if overlay_text}}checked{{/if}}>
Overlay text on image
</label>
<p class="overlay-text-help">If left unchecked, text will appear outside the image.</p>
</div>

<label class="bx-checkbox">
<input type="checkbox" data-role="no-branches" {{#if no_branches}}checked{{/if}} {{#if has_choices}}disabled{{/if}} />
<span>This node has no branches</span>
</label>

<label class="bx-field">
<div class="bx-field__label">Hint</div>
<textarea class="bx-textarea" data-role="node-hint">{{hint}}</textarea>
</label>

<div class="bx-choices {{#if no_branches}}is-hidden{{/if}}" data-role="choices-section">
<h3 class="bx-section-title">Choices</h3>
<div class="bx-help bx-choices-help">When a learner selects a choice, the score assigned to that choice is added to the total score.</div>
<div class="choices-container" data-role="choices-container"></div>
<button type="button" class="bx-btn bx-btn--secondary" data-role="add-choice">Add Choice</button>
</div>
</div>
21 changes: 21 additions & 0 deletions branching_xblock/static/handlebars/node-list-item.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="bx-node-list-item {{#if is_selected}}is-selected{{/if}} {{#if is_pending_delete}}is-pending-delete{{/if}} {{#if has_errors}}has-errors{{/if}}" data-node-id="{{id}}">
<button type="button" class="bx-node-list-item__select" data-role="select-node" data-node-id="{{id}}">
Node {{number}}{{#if is_unlinked}} <span class="bx-node-list-item__meta">(unlinked node)</span>{{/if}}
</button>
{{#if has_errors}}
<span class="bx-node-list-item__error" aria-label="Node has errors">!</span>
{{/if}}
<button
type="button"
class="bx-node-list-item__delete {{#if is_pending_delete}}is-restore{{/if}}"
data-role="toggle-delete-node"
data-node-id="{{id}}"
aria-label="{{#if is_pending_delete}}Restore node{{else}}Delete node{{/if}}"
>
{{#if is_pending_delete}}
<span class="fa fa-undo" aria-hidden="true"></span>
{{else}}
<span class="fa fa-trash-o" aria-hidden="true"></span>
{{/if}}
</button>
</div>
12 changes: 12 additions & 0 deletions branching_xblock/static/handlebars/nodes-step.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="bx-wizard bx-nodes-step">
<div class="bx-nodes-sidebar">
<button type="button" class="bx-btn bx-btn--primary" data-role="add-node">+ Add node</button>
<div class="bx-node-limit">Max 30 nodes</div>
<div class="bx-node-list" data-role="node-list"></div>
</div>

<div class="bx-nodes-main">
<div class="bx-node-editor" data-role="node-editor"></div>
</div>
</div>

70 changes: 70 additions & 0 deletions branching_xblock/static/handlebars/settings-step.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<div class="bx-wizard bx-settings-step">
<h2 class="bx-step-title">Settings</h2>

<label class="bx-field">
<div class="bx-field__label">Display Name</div>
<input type="text" name="display_name" value="{{display_name}}" class="bx-input" />
</label>

<label class="bx-checkbox">
<input type="checkbox" name="enable_undo" {{#if enable_undo}}checked{{/if}} />
<span>Let learners try the previous node again</span>
</label>

<label class="bx-checkbox">
<input type="checkbox" name="enable_reset_activity" {{#if enable_reset_activity}}checked{{/if}} />
<span>Let learners reset the activity</span>
</label>

<label class="bx-checkbox">
<input type="checkbox" name="enable_scoring" {{#if enable_scoring}}checked{{/if}} />
<span>Include a grade report at the end of the activity</span>
</label>

<div class="bx-grade-range {{#unless enable_scoring}}is-hidden{{/unless}}" data-role="grade-range-section">
<h3 class="bx-section-title bx-grade-range__title">Specify Grade Range</h3>
<p class="bx-help bx-grade-range__help">Adjust the scale to set percentage ranges for each grade.</p>
<div class="bx-grade-range__slider" data-role="grade-range-slider"></div>
{{#if grade_ranges_error}}
<div class="bx-field-error">{{grade_ranges_error}}</div>
{{/if}}
</div>

<h3 class="bx-section-title">Background image</h3>
<p class="bx-help">
This image will appear in the background of any nodes with “Image” as the media type.
</p>

<label class="bx-field">
<div class="bx-field__label">Image URL</div>
<input
type="text"
name="background_image_url"
class="bx-input {{#if background_image_url_error}}is-error{{/if}}"
placeholder="URL"
value="{{background_image_url}}"
/>
{{#if background_image_url_error}}
<div class="bx-field-error">{{background_image_url_error}}</div>
{{/if}}
</label>

<div class="bx-field">
<div class="bx-field__label">Alt text</div>
<label class="bx-checkbox bx-checkbox--nested">
<input type="checkbox" name="background_image_is_decorative" {{#if background_image_is_decorative}}checked{{/if}} />
<span>Decorative image</span>
</label>
<input
type="text"
name="background_image_alt_text"
class="bx-input {{#if background_image_alt_text_error}}is-error{{/if}}"
placeholder="Alt text"
value="{{background_image_alt_text}}"
{{#if background_image_is_decorative}}disabled{{/if}}
/>
{{#if background_image_alt_text_error}}
<div class="bx-field-error">{{background_image_alt_text_error}}</div>
{{/if}}
</div>
</div>
88 changes: 79 additions & 9 deletions branching_xblock/static/html/branching_xblock.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,87 @@
<div data-role="hint" class="node-hint px-2 py-3"></div>
</details>
</div>
<div class="choices" data-role="choices"></div>

<div class="action-row">
<button class="undo-button action-secondary btn-secondary" data-role="undo" style="display: none;">
Go Back
</button>
<button type="button" class="reset-button action-secondary btn-secondary" data-role="reset-activity" style="display: none;">
Reset
</button>
<div class="choices" data-role="choices">
<h3 class="choices-heading" data-role="choice-heading">Choose Next Step:</h3>
<form class="choices-form" data-role="choice-form">
<div class="choices-list" data-role="choice-list"></div>
<div class="choice-actions">
<div class="choice-actions__secondary">
<button type="button" class="undo-button action-secondary btn-secondary" data-role="undo">
Go Back
</button>
<button type="button" class="reset-button action-secondary btn-secondary" data-role="reset-activity" style="display: none;">
Reset
</button>
</div>
<div class="choice-actions__primary">
<button type="button" class="show-report-button action-primary btn-primary" data-role="show-report" style="display: none;">
Show Report
</button>
<button type="submit" class="choice-submit-button action-primary btn-primary" data-role="submit-choice" disabled>
Submit
</button>
</div>
</div>
</form>
</div>

<div class="score-display" data-role="score" style="display: none;"></div>
<div class="grade-report" data-role="grade-report" hidden>
<h3 class="grade-report__title">Activity Complete!</h3>
<p class="grade-report__subtitle">Let's take a look at this report below to see how you performed.</p>

<div class="grade-report__summary-card">
<h4 class="grade-report__section-title">
<span class="grade-report__section-icon" aria-hidden="true">
<span class="fa fa-graduation-cap" aria-hidden="true"></span>
</span>
Your Grade
</h4>
<div class="grade-report__summary">
<div class="grade-report__metric grade-report__metric--score">
<div class="grade-report__metric-label">
<span class="grade-report__metric-label-icon" aria-hidden="true">
<span class="fa fa-trophy" data-role="report-score-icon" aria-hidden="true"></span>
</span>
Your Score
</div>
<div class="grade-report__metric-value" data-role="report-score">0</div>
</div>
<div class="grade-report__metric grade-report__metric--max">
<div class="grade-report__metric-label">Highest Possible Score</div>
<div class="grade-report__metric-value" data-role="report-max-score">0</div>
</div>
<div class="grade-report__percent" data-role="report-percent-pill">
<svg class="grade-report__percent-ring" viewBox="0 0 120 120" aria-hidden="true" focusable="false">
<circle class="grade-report__percent-track" cx="60" cy="60" r="48"></circle>
<circle class="grade-report__percent-progress" data-role="report-percent-circle" cx="60" cy="60" r="48"></circle>
</svg>
<div class="grade-report__percent-content">
<div class="grade-report__percent-value" data-role="report-percent">0%</div>
<div class="grade-report__percent-label" data-role="report-grade-label"></div>
</div>
</div>
</div>
</div>

<div class="grade-report__details">
<h4 class="grade-report__details-title">
<span class="grade-report__section-icon" aria-hidden="true">
<span class="fa fa-line-chart" aria-hidden="true"></span>
</span>
Detailed Score
</h4>
<div class="grade-report__details-header">
<span>Your Selections</span>
<span>Score</span>
</div>
<div class="grade-report__details-rows" data-role="report-details"></div>
</div>

<button type="button" class="action-primary btn-primary" data-role="reset-activity-report" style="display: none;">
Reset Activity
</button>
</div>
</div>
</div>
12 changes: 8 additions & 4 deletions branching_xblock/static/html/branching_xblock_edit.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<div class="wrapper-comp-settings is-active editor-with-buttons">
<!-- Editor UI placeholder -->
<div class="branching-scenario-editor"></div>
<div class="branching-editor-step" data-role="step-settings"></div>
<div class="branching-editor-step" data-role="step-nodes" hidden></div>

<!-- Error messages get injected here -->
<div class="errors" style="color:red;"></div>
</div>

<div class="xblock-actions">
<button type="button" class="action-primary save-button">Save</button>
<button type="button" class="action-secondary cancel-button">Cancel</button>
<button type="button" class="action-primary" data-role="continue">Continue</button>
<button type="button" class="action-primary" data-role="save" hidden>Save</button>
<button type="button" class="action-secondary" data-role="back" hidden>Back</button>
<button type="button" class="action-secondary" data-role="cancel">Cancel</button>
<div class="xblock-actions__error" data-role="save-validation-summary" hidden></div>
<div class="xblock-actions__warning" data-role="pending-delete-summary" hidden></div>
</div>
Loading
Loading