Skip to content

Commit ea1b8f6

Browse files
Merge pull request steam-bell-92#999 from jyotish6699/feat/playground-draft-management
feat: add playground draft management and session persistence
2 parents 62d385b + 85e82a4 commit ea1b8f6

4 files changed

Lines changed: 254 additions & 2 deletions

File tree

web-app/css/styles.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2711,6 +2711,23 @@ body.sidebar-active .sidebar-dock {
27112711
gap: 6px;
27122712
}
27132713

2714+
.draft-selector {
2715+
background: var(--bg-glass);
2716+
border: 1px solid var(--border);
2717+
color: var(--text-secondary);
2718+
padding: 6px 10px;
2719+
border-radius: 8px;
2720+
font-size: 0.75rem;
2721+
}
2722+
2723+
.draft-status {
2724+
padding: 8px 12px;
2725+
border-top: 1px solid var(--border);
2726+
font-size: 0.75rem;
2727+
color: var(--text-secondary);
2728+
background: var(--bg-glass);
2729+
}
2730+
27142731
.btn-panel-action {
27152732
background: var(--bg-glass);
27162733
border: 1px solid var(--border);

web-app/index.html

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,9 +639,39 @@ <h2 class="playground-title">🐍 Python Playground</h2>
639639
>
640640
<i aria-hidden="true" class="fas fa-stop"></i> Stop
641641
</button>
642+
<select
643+
id="draftSelector"
644+
class="draft-selector"
645+
aria-label="Select draft">
646+
<option value="">Select Draft</option>
647+
</select>
648+
649+
<button
650+
id="saveDraft"
651+
class="btn-panel-action"
652+
type="button">
653+
Save Draft
654+
</button>
655+
656+
<button
657+
id="loadDraft"
658+
class="btn-panel-action"
659+
type="button">
660+
Load Draft
661+
</button>
662+
663+
<button
664+
id="deleteDraft"
665+
class="btn-panel-action"
666+
type="button">
667+
Delete Draft
668+
</button>
669+
</div>
670+
<div id="draftStatus" class="draft-status">
671+
No draft loaded
642672
</div>
643673
</div>
644-
</section>
674+
</section>
645675
</main>
646676

647677
<!-- Project Modal -->

web-app/js/playground.js

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
var EXECUTION_TIMEOUT_MS = 5000; // 5 seconds timeout
4040
var currentTimeoutId = null;
4141
var WORKER_SCRIPT = 'js/playground-worker.js';
42+
var DRAFTS_STORAGE_KEY = 'playground_drafts';
43+
var ACTIVE_DRAFT_KEY = 'playground_active_draft';
4244

4345
/* ================================================================
4446
3. EXAMPLE CODE SNIPPETS
@@ -127,6 +129,11 @@
127129
var clearConsoleBtn = $id('clearConsole');
128130
var clearEditorBtn = $id('clearEditor');
129131
var loadExampleBtn = $id('loadExample');
132+
var draftSelector = $id('draftSelector');
133+
var saveDraftBtn = $id('saveDraft');
134+
var loadDraftBtn = $id('loadDraft');
135+
var deleteDraftBtn = $id('deleteDraft');
136+
var draftStatus = $id('draftStatus');
130137

131138
/* Guard – abort gracefully if playground HTML is absent */
132139
if (!playgroundSection || !runBtn || !stopBtn || !editorMount || !consoleEl) {
@@ -518,6 +525,47 @@
518525
});
519526
themeObserver.observe(document.documentElement, { attributes: true });
520527

528+
function getDrafts() {
529+
try {
530+
var data = localStorage.getItem(DRAFTS_STORAGE_KEY);
531+
return data ? JSON.parse(data) : {};
532+
} catch (e) {
533+
return {};
534+
}
535+
}
536+
537+
function saveDrafts(drafts) {
538+
try {
539+
localStorage.setItem(DRAFTS_STORAGE_KEY, JSON.stringify(drafts));
540+
} catch (e) {
541+
console.error('Failed to save drafts:', e);
542+
}
543+
}
544+
545+
function setDraftStatus(message) {
546+
if (!draftStatus) return;
547+
548+
draftStatus.textContent = message;
549+
}
550+
551+
function refreshDraftSelector() {
552+
if (!draftSelector) return;
553+
554+
var drafts = getDrafts();
555+
556+
draftSelector.innerHTML =
557+
'<option value="">Select Draft</option>';
558+
559+
Object.keys(drafts)
560+
.sort()
561+
.forEach(function(name) {
562+
var option = document.createElement('option');
563+
option.value = name;
564+
option.textContent = name;
565+
draftSelector.appendChild(option);
566+
});
567+
}
568+
521569
/* ================================================================
522570
6. UI HELPER FUNCTIONS
523571
================================================================ */
@@ -718,6 +766,13 @@
718766

719767
if (clearEditorBtn) {
720768
clearEditorBtn.addEventListener('click', function () {
769+
if (
770+
!confirm(
771+
'Clear editor contents? Unsaved changes will be lost.'
772+
)
773+
) {
774+
return;
775+
}
721776
setCode('');
722777
if (cmView) cmView.focus();
723778
});
@@ -731,6 +786,122 @@
731786
});
732787
}
733788

789+
if (saveDraftBtn) {
790+
saveDraftBtn.addEventListener('click', function() {
791+
792+
var draftName = prompt(
793+
'Enter a name for this draft:'
794+
);
795+
796+
if (!draftName) return;
797+
798+
draftName = draftName.trim();
799+
800+
if (!draftName) return;
801+
802+
var drafts = getDrafts();
803+
804+
drafts[draftName] = {
805+
code: getCode(),
806+
updatedAt: new Date().toISOString()
807+
};
808+
809+
saveDrafts(drafts);
810+
811+
try {
812+
localStorage.setItem(ACTIVE_DRAFT_KEY, draftName);
813+
} catch (e) {
814+
console.error('Failed to save active draft:', e);
815+
}
816+
817+
refreshDraftSelector();
818+
819+
draftSelector.value = draftName;
820+
821+
setDraftStatus(
822+
'Draft "' + draftName + '" saved'
823+
);
824+
});
825+
}
826+
827+
if (loadDraftBtn) {
828+
loadDraftBtn.addEventListener('click', function() {
829+
830+
var selectedDraft = draftSelector.value;
831+
832+
if (!selectedDraft) {
833+
setDraftStatus('Select a draft first');
834+
return;
835+
}
836+
837+
var drafts = getDrafts();
838+
839+
if (!drafts[selectedDraft]) {
840+
setDraftStatus('Draft not found');
841+
return;
842+
}
843+
844+
setCode(
845+
drafts[selectedDraft].code || ''
846+
);
847+
848+
try {
849+
localStorage.setItem(ACTIVE_DRAFT_KEY, selectedDraft);
850+
} catch (e) {
851+
console.error('Failed to save active draft:', e);
852+
}
853+
854+
setDraftStatus(
855+
'Loaded draft "' +
856+
selectedDraft +
857+
'"'
858+
);
859+
});
860+
}
861+
862+
if (deleteDraftBtn) {
863+
deleteDraftBtn.addEventListener('click', function() {
864+
865+
var selectedDraft = draftSelector.value;
866+
867+
if (!selectedDraft) {
868+
setDraftStatus('Select a draft first');
869+
return;
870+
}
871+
872+
var confirmed = confirm(
873+
'Delete draft "' +
874+
selectedDraft +
875+
'" ?'
876+
);
877+
878+
if (!confirmed) return;
879+
880+
var drafts = getDrafts();
881+
882+
delete drafts[selectedDraft];
883+
884+
saveDrafts(drafts);
885+
886+
try {
887+
var activeDraft = localStorage.getItem(ACTIVE_DRAFT_KEY);
888+
if (activeDraft === selectedDraft) {
889+
localStorage.removeItem(ACTIVE_DRAFT_KEY);
890+
}
891+
} catch (e) {
892+
console.error('Failed to remove active draft:', e);
893+
}
894+
895+
refreshDraftSelector();
896+
897+
setDraftStatus(
898+
'Deleted "' +
899+
selectedDraft +
900+
'"'
901+
);
902+
});
903+
}
904+
734905
/* ================================================================
735906
11. BOOT
736907
================================================================ */
@@ -746,4 +917,35 @@
746917
'print("Hello, World!")'
747918
);
748919

920+
/* Populate the draft selector dropdown */
921+
refreshDraftSelector();
922+
923+
/* Restore last active draft if available */
924+
try {
925+
var activeDraft = localStorage.getItem(ACTIVE_DRAFT_KEY);
926+
927+
var drafts = getDrafts();
928+
929+
if (
930+
activeDraft &&
931+
drafts[activeDraft]
932+
) {
933+
934+
draftSelector.value =
935+
activeDraft;
936+
937+
setCode(
938+
drafts[activeDraft].code || ''
939+
);
940+
941+
setDraftStatus(
942+
'Restored "' +
943+
activeDraft +
944+
'"'
945+
);
946+
}
947+
} catch (e) {
948+
console.error('Failed to restore active draft:', e);
949+
}
950+
749951
}());

web-app/js/storage.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,7 @@ if (typeof window !== 'undefined') {
2828
window.appStorage = storageHelpers;
2929
}
3030

31-
export { saveToStorage, loadFromStorage, removeFromStorage };
31+
// Support ES6 module export if needed
32+
if (typeof module !== 'undefined' && module.exports) {
33+
module.exports = { saveToStorage, loadFromStorage, removeFromStorage };
34+
}

0 commit comments

Comments
 (0)