feat: improve playground UX and fix undo/redo across lessons
- Add dice SVG icon for random template button - Reset button now restores last loaded template in playground - Clear editor history when switching lessons (prevents cross-lesson undo) - Add playground link to goodbye lesson - Center icon buttons with flexbox 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
{
|
{
|
||||||
"id": "next-steps",
|
"id": "next-steps",
|
||||||
"title": "Keep Going!",
|
"title": "Keep Going!",
|
||||||
"description": "<strong>Great progress!</strong> You're building real web development skills.<br><br><strong>Continue learning:</strong><br>• <a href=\"https://developer.mozilla.org\" target=\"_blank\">MDN Web Docs</a> - The definitive reference<br>• <a href=\"https://css-tricks.com\" target=\"_blank\">CSS-Tricks</a> - Practical techniques<br><br><strong>Practice ideas:</strong><br>• Build your portfolio site<br>• Recreate a website you like<br>• Try the Playground to experiment freely<br><br><strong>Contribute:</strong> Code Crispies is <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">open source</a>. Add lessons, fix bugs, or translate!",
|
"description": "<strong>Great progress!</strong> You're building real web development skills.<br><br><strong>Continue learning:</strong><br>• <a href=\"https://developer.mozilla.org\" target=\"_blank\">MDN Web Docs</a> - The definitive reference<br>• <a href=\"https://css-tricks.com\" target=\"_blank\">CSS-Tricks</a> - Practical techniques<br><br><strong>Practice ideas:</strong><br>• Build your portfolio site<br>• Recreate a website you like<br>• Try the <a href=\"#playground/0\">Playground</a> to experiment freely<br><br><strong>Contribute:</strong> Code Crispies is <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">open source</a>. Add lessons, fix bugs, or translate!",
|
||||||
"task": "Type <code>Thank you!</code>",
|
"task": "Type <code>Thank you!</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 1.5rem; color: #6366f1; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 1.5rem; color: #6366f1; }",
|
||||||
|
|||||||
8
public/dice.svg
Normal file
8
public/dice.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<rect x="2" y="2" width="20" height="20" rx="3" ry="3" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<circle cx="7" cy="7" r="1.5" fill="currentColor"/>
|
||||||
|
<circle cx="12" cy="12" r="1.5" fill="currentColor"/>
|
||||||
|
<circle cx="17" cy="17" r="1.5" fill="currentColor"/>
|
||||||
|
<circle cx="17" cy="7" r="1.5" fill="currentColor"/>
|
||||||
|
<circle cx="7" cy="17" r="1.5" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 448 B |
18
src/app.js
18
src/app.js
@@ -27,7 +27,8 @@ const state = {
|
|||||||
skipResetCodeConfirmation: false
|
skipResetCodeConfirmation: false
|
||||||
},
|
},
|
||||||
showExpected: false,
|
showExpected: false,
|
||||||
animationTimeout: null
|
animationTimeout: null,
|
||||||
|
lastPlaygroundTemplate: null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track CodeMirror views for cleanup
|
// Track CodeMirror views for cleanup
|
||||||
@@ -579,9 +580,9 @@ function loadCurrentLesson() {
|
|||||||
lesson
|
lesson
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set user code in CodeMirror
|
// Set user code in CodeMirror (clear history to prevent undo/redo across lessons)
|
||||||
if (codeEditor) {
|
if (codeEditor) {
|
||||||
codeEditor.setValue(engineState.userCode);
|
codeEditor.setValueAndClearHistory(engineState.userCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Run button text based on completion status
|
// Update Run button text based on completion status
|
||||||
@@ -731,9 +732,17 @@ function resetCode() {
|
|||||||
// Reset editor to initial code for current lesson
|
// Reset editor to initial code for current lesson
|
||||||
lessonEngine.reset();
|
lessonEngine.reset();
|
||||||
const engineState = lessonEngine.getCurrentState();
|
const engineState = lessonEngine.getCurrentState();
|
||||||
|
const isPlayground = engineState.lesson?.mode === "playground";
|
||||||
track("reset_code", { module: engineState.module?.id, lesson: engineState.lessonIndex });
|
track("reset_code", { module: engineState.module?.id, lesson: engineState.lessonIndex });
|
||||||
|
|
||||||
if (codeEditor && engineState.lesson) {
|
if (codeEditor && engineState.lesson) {
|
||||||
codeEditor.setValue(engineState.lesson.initialCode || "");
|
// In playground mode, restore the last template if available
|
||||||
|
if (isPlayground && state.lastPlaygroundTemplate) {
|
||||||
|
codeEditor.setValue(state.lastPlaygroundTemplate.code);
|
||||||
|
lessonEngine.applyUserCode(state.lastPlaygroundTemplate.code, true);
|
||||||
|
} else {
|
||||||
|
codeEditor.setValue(engineState.lesson.initialCode || "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Clear hints and success indicators
|
// Clear hints and success indicators
|
||||||
clearHint();
|
clearHint();
|
||||||
@@ -755,6 +764,7 @@ function loadRandomTemplate() {
|
|||||||
const template = getRandomTemplate();
|
const template = getRandomTemplate();
|
||||||
if (codeEditor && template) {
|
if (codeEditor && template) {
|
||||||
track("playground_template", { template: template.name });
|
track("playground_template", { template: template.name });
|
||||||
|
state.lastPlaygroundTemplate = template;
|
||||||
codeEditor.setValue(template.code);
|
codeEditor.setValue(template.code);
|
||||||
// Apply the code to the preview
|
// Apply the code to the preview
|
||||||
lessonEngine.applyUserCode(template.code, true);
|
lessonEngine.applyUserCode(template.code, true);
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export class CodeEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set editor value
|
* Set editor value (preserves history)
|
||||||
*/
|
*/
|
||||||
setValue(value) {
|
setValue(value) {
|
||||||
if (!this.view) return;
|
if (!this.view) return;
|
||||||
@@ -197,6 +197,13 @@ export class CodeEditor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set editor value and clear history (for lesson switching)
|
||||||
|
*/
|
||||||
|
setValueAndClearHistory(value) {
|
||||||
|
this.init(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set editor mode (html or css)
|
* Set editor mode (html or css)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -245,7 +245,7 @@
|
|||||||
>
|
>
|
||||||
⟲
|
⟲
|
||||||
</button>
|
</button>
|
||||||
<button id="random-template-btn" class="btn btn-icon hidden" title="Load random template">🎲</button>
|
<button id="random-template-btn" class="btn btn-icon hidden" title="Load random template"><img src="./dice.svg" alt="" /></button>
|
||||||
</div>
|
</div>
|
||||||
<button id="run-btn" class="btn btn-run"><img src="./gear.svg" alt="" /><span data-i18n="run">Run</span></button>
|
<button id="run-btn" class="btn btn-run"><img src="./gear.svg" alt="" /><span data-i18n="run">Run</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
10
src/main.css
10
src/main.css
@@ -1154,6 +1154,9 @@ button.lesson-list-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
min-width: 32px;
|
min-width: 32px;
|
||||||
@@ -1168,6 +1171,13 @@ button.lesson-list-item {
|
|||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-icon img {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.editor-tools {
|
.editor-tools {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|||||||
Reference in New Issue
Block a user