1+ import { effect , path , signal } from "@nisoku/sairin" ;
2+ import type { State } from "./types" ;
3+ import { defaultCss , defaultHtml , defaultJs } from "./defaultContent" ;
4+ import { editors } from "./editor" ;
5+ import { showError , switchOutput , switchTab } from "./ui" ;
6+ import { runCode } from "./runner" ;
7+
8+ function readPersistedState ( ) : Partial < State > | null {
9+ try {
10+ const savedState = localStorage . getItem ( "htmlRunnerState" ) ;
11+ if ( ! savedState ) {
12+ return null ;
13+ }
14+
15+ return JSON . parse ( savedState ) as Partial < State > ;
16+ } catch {
17+ return null ;
18+ }
19+ }
20+
21+ const persistedState = readPersistedState ( ) ;
22+
23+ const darkModeInitial =
24+ persistedState ?. darkMode ?? localStorage . getItem ( "darkMode" ) === "true" ;
25+ const autoRunInitial =
26+ persistedState ?. autoRun ?? localStorage . getItem ( "autoRun" ) === "true" ;
27+ const htmlInitial = persistedState ?. html ?? defaultHtml ;
28+ const cssInitial = persistedState ?. css ?? defaultCss ;
29+ const jsInitial = persistedState ?. js ?? defaultJs ;
30+ const activeTabInitial = persistedState ?. activeTab ?? "html" ;
31+ const activeOutputInitial = persistedState ?. activeOutput ?? "preview" ;
32+ const splitSizesInitial =
33+ Array . isArray ( persistedState ?. splitSizes ) && persistedState . splitSizes . length === 2
34+ ? persistedState . splitSizes
35+ : [ 50 , 50 ] ;
36+ const STORAGE_KEY = "htmlRunnerState" ;
37+
38+ export const htmlState = signal ( path ( "htmlrunner" , "editor" , "html" ) , htmlInitial ) ;
39+ export const cssState = signal ( path ( "htmlrunner" , "editor" , "css" ) , cssInitial ) ;
40+ export const jsState = signal ( path ( "htmlrunner" , "editor" , "js" ) , jsInitial ) ;
41+ export const activeTabState = signal ( path ( "htmlrunner" , "ui" , "activeTab" ) , activeTabInitial ) ;
42+ export const activeOutputState = signal ( path ( "htmlrunner" , "ui" , "activeOutput" ) , activeOutputInitial ) ;
43+ export const splitSizesState = signal ( path ( "htmlrunner" , "layout" , "splitSizes" ) , splitSizesInitial ) ;
44+ export const darkModeState = signal ( path ( "htmlrunner" , "ui" , "darkMode" ) , darkModeInitial ) ;
45+ export const autoRunState = signal ( path ( "htmlrunner" , "editor" , "autoRun" ) , autoRunInitial ) ;
46+ export const stateHydrated = signal ( path ( "htmlrunner" , "meta" , "stateHydrated" ) , false ) ;
47+
48+ effect ( ( ) => {
49+ if ( ! stateHydrated . get ( ) ) {
50+ return ;
51+ }
52+
53+ localStorage . setItem ( "htmlRunnerState" , JSON . stringify ( createStateSnapshot ( ) ) ) ;
54+ } ) ;
55+
56+ export function createStateSnapshot ( ) : State {
57+ return {
58+ html : htmlState . get ( ) ,
59+ css : cssState . get ( ) ,
60+ js : jsState . get ( ) ,
61+ activeTab : activeTabState . get ( ) ,
62+ activeOutput : activeOutputState . get ( ) ,
63+ splitSizes : splitSizesState . get ( ) ,
64+ darkMode : darkModeState . get ( ) ,
65+ autoRun : autoRunState . get ( ) ,
66+ } ;
67+ }
68+
69+ export function applyStateSnapshot ( snapshot : Partial < State > ) : void {
70+ if ( typeof snapshot . html === "string" ) {
71+ htmlState . set ( snapshot . html ) ;
72+ }
73+
74+ if ( typeof snapshot . css === "string" ) {
75+ cssState . set ( snapshot . css ) ;
76+ }
77+
78+ if ( typeof snapshot . js === "string" ) {
79+ jsState . set ( snapshot . js ) ;
80+ }
81+
82+ if ( typeof snapshot . activeTab === "string" ) {
83+ activeTabState . set ( snapshot . activeTab ) ;
84+ }
85+
86+ if ( typeof snapshot . activeOutput === "string" ) {
87+ activeOutputState . set ( snapshot . activeOutput ) ;
88+ }
89+
90+ if (
91+ Array . isArray ( snapshot . splitSizes ) &&
92+ snapshot . splitSizes . length === 2 &&
93+ snapshot . splitSizes . every ( ( size ) => typeof size === "number" )
94+ ) {
95+ splitSizesState . set ( snapshot . splitSizes ) ;
96+ }
97+
98+ if ( typeof snapshot . darkMode === "boolean" ) {
99+ darkModeState . set ( snapshot . darkMode ) ;
100+ }
101+
102+ if ( typeof snapshot . autoRun === "boolean" ) {
103+ autoRunState . set ( snapshot . autoRun ) ;
104+ }
105+ }
106+
107+ export function markStateHydrated ( ) : void {
108+ stateHydrated . set ( true ) ;
109+ }
110+
111+ export function loadState ( ) : void {
112+ try {
113+ const savedState = localStorage . getItem ( STORAGE_KEY ) ;
114+ if ( savedState ) {
115+ const parsed = JSON . parse ( savedState ) as Partial < State > ;
116+ applyStateSnapshot ( parsed ) ;
117+
118+ editors . html . view . dispatch ( {
119+ changes : {
120+ from : 0 ,
121+ to : editors . html . view . state . doc . length ,
122+ insert : htmlState . get ( ) ,
123+ } ,
124+ } ) ;
125+ editors . css . view . dispatch ( {
126+ changes : {
127+ from : 0 ,
128+ to : editors . css . view . state . doc . length ,
129+ insert : cssState . get ( ) ,
130+ } ,
131+ } ) ;
132+ editors . js . view . dispatch ( {
133+ changes : {
134+ from : 0 ,
135+ to : editors . js . view . state . doc . length ,
136+ insert : jsState . get ( ) ,
137+ } ,
138+ } ) ;
139+
140+ if ( [ "html" , "css" , "js" ] . includes ( activeTabState . get ( ) ) ) {
141+ switchTab ( activeTabState . get ( ) ) ;
142+ }
143+ if ( [ "preview" , "console" ] . includes ( activeOutputState . get ( ) ) ) {
144+ switchOutput ( activeOutputState . get ( ) ) ;
145+ }
146+
147+ runCode ( ) ;
148+ markStateHydrated ( ) ;
149+ } else {
150+ resetCode ( true ) ;
151+ markStateHydrated ( ) ;
152+ }
153+ } catch ( e : any ) {
154+ showError ( "Failed to load state: " + e . message ) ;
155+ resetCode ( true ) ;
156+ markStateHydrated ( ) ;
157+ }
158+ }
159+
160+ export function resetCode ( skipConfirmation : boolean = false ) : void {
161+ if ( skipConfirmation || confirm ( "Are you sure you want to reset all code?" ) ) {
162+ editors . html . view . dispatch ( {
163+ changes : {
164+ from : 0 ,
165+ to : editors . html . view . state . doc . length ,
166+ insert : defaultHtml ,
167+ } ,
168+ } ) ;
169+
170+ editors . css . view . dispatch ( {
171+ changes : {
172+ from : 0 ,
173+ to : editors . css . view . state . doc . length ,
174+ insert : defaultCss ,
175+ } ,
176+ } ) ;
177+
178+ editors . js . view . dispatch ( {
179+ changes : {
180+ from : 0 ,
181+ to : editors . js . view . state . doc . length ,
182+ insert : defaultJs ,
183+ } ,
184+ } ) ;
185+
186+ runCode ( ) ;
187+ markStateHydrated ( ) ;
188+ }
189+ }
0 commit comments