11import { writable , get } from 'svelte/store' ;
22import { parseStateFromHash } from '$lib/utils/decode' ;
33import type { OutputLine } from '$lib/utils/output' ;
4+ import { DEFAULT_FILENAME , PLAYGROUND_STORAGE_KEY } from '$lib/constants' ;
45
56// Re-export for backwards compatibility
67export type { OutputLine } ;
@@ -12,6 +13,11 @@ export interface PlaygroundState {
1213 isRunning : boolean ;
1314}
1415
16+ interface PersistedPlaygroundState {
17+ files : Record < string , string > ;
18+ activeFile : string ;
19+ }
20+
1521// Default initial code
1622const defaultCode = `-- Welcome to the Luau Playground!
1723-- Write your code here and click Run
3137print("Sum:", sum)
3238` ;
3339
40+ function loadFromStorage ( ) : { files : Record < string , string > ; activeFile : string } | null {
41+ if ( typeof window === 'undefined' ) return null ;
42+
43+ try {
44+ const stored = localStorage . getItem ( PLAYGROUND_STORAGE_KEY ) ;
45+ if ( ! stored ) return null ;
46+
47+ const parsed = JSON . parse ( stored ) as Partial < PersistedPlaygroundState > ;
48+ if ( ! parsed . files || typeof parsed . files !== 'object' ) return null ;
49+ if ( ! parsed . activeFile || typeof parsed . activeFile !== 'string' ) return null ;
50+
51+ const fileNames = Object . keys ( parsed . files ) ;
52+ if ( fileNames . length === 0 ) return null ;
53+ if ( ! ( parsed . activeFile in parsed . files ) ) return null ;
54+ return { files : parsed . files as Record < string , string > , activeFile : parsed . activeFile } ;
55+ } catch {
56+ return null ;
57+ }
58+ }
59+
3460// Load initial state from URL if available
3561function getInitialState ( ) : { files : Record < string , string > ; activeFile : string } {
36- const defaultState = { files : { 'main.luau' : defaultCode } , activeFile : 'main.luau' } ;
62+ const defaultState = { files : { [ DEFAULT_FILENAME ] : defaultCode } , activeFile : DEFAULT_FILENAME } ;
3763
3864 if ( typeof window === 'undefined' ) {
3965 return defaultState ;
4066 }
4167
4268 const state = parseStateFromHash ( window . location . hash ) ;
43- if ( ! state || Object . keys ( state . files ) . length === 0 ) {
44- return defaultState ;
69+ if ( state && Object . keys ( state . files ) . length > 0 && state . active in state . files ) {
70+ return { files : state . files , activeFile : state . active } ;
4571 }
46-
47- const active = state . active in state . files ? state . active : Object . keys ( state . files ) [ 0 ] ;
48- return { files : state . files , activeFile : active } ;
72+
73+ return loadFromStorage ( ) ?? defaultState ;
4974}
5075
5176const initialState = getInitialState ( ) ;
@@ -62,6 +87,45 @@ export function setExecutionTime(ms: number | null) {
6287 executionTime . set ( ms ) ;
6388}
6489
90+ function debounce ( fn : ( ) => void , ms : number ) : ( ) => void {
91+ let t : ReturnType < typeof setTimeout > | null = null ;
92+ return ( ) => {
93+ if ( t ) clearTimeout ( t ) ;
94+ t = setTimeout ( ( ) => {
95+ t = null ;
96+ fn ( ) ;
97+ } , ms ) ;
98+ } ;
99+ }
100+
101+ function saveToStorage ( ) : void {
102+ if ( typeof window === 'undefined' ) return ;
103+
104+ try {
105+ const f = get ( files ) ;
106+ const a = get ( activeFile ) ;
107+ const fileNames = Object . keys ( f ) ;
108+ if ( fileNames . length === 0 ) return ;
109+ if ( ! ( a in f ) ) return ;
110+
111+ const state : PersistedPlaygroundState = {
112+ files : f ,
113+ activeFile : a ,
114+ } ;
115+
116+ localStorage . setItem ( PLAYGROUND_STORAGE_KEY , JSON . stringify ( state ) ) ;
117+ } catch {
118+ // Ignore storage errors
119+ }
120+ }
121+
122+ // Persist editor state (files + active file) between reloads
123+ if ( typeof window !== 'undefined' ) {
124+ const scheduleSave = debounce ( saveToStorage , 250 ) ;
125+ files . subscribe ( ( ) => scheduleSave ( ) ) ;
126+ activeFile . subscribe ( ( ) => scheduleSave ( ) ) ;
127+ }
128+
65129// Actions
66130export function addFile ( name : string , content : string = '' ) {
67131 files . update ( ( f ) => ( { ...f , [ name ] : content } ) ) ;
0 commit comments