Save and load

main
Ashelyn Dawn 4 years ago
parent ca264d4e6b
commit 73613b1a0b

5
package-lock.json generated

@ -8169,6 +8169,11 @@
}
}
},
"luxon": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.24.1.tgz",
"integrity": "sha512-CgnIMKAWT0ghcuWFfCWBnWGOddM0zu6c4wZAWmD0NN7MZTnro0+833DF6tJep+xlxRPg4KtsYEHYLfTMBQKwYg=="
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",

@ -12,6 +12,7 @@
"@types/react-dom": "^16.9.8",
"auto-bind": "^4.0.0",
"immer": "^7.0.5",
"luxon": "^1.24.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-markdown": "^4.3.1",

@ -2,7 +2,8 @@ import React, {useEffect, useState} from 'react';
import useWindowSize from '../../hooks/useWindowSize'
import styles from './App.module.css';
import Screen from '../Screen/Screen';
import {Provider} from '../../hooks/useGameState'
import {Provider as StateProvider} from '../../hooks/useGameState'
import {Provider as LoadProvider} from '../../hooks/useLoadGame'
import useLocalStorage from '../../hooks/useLocalStorage'
import backgroundURL from './background.png'
@ -15,10 +16,7 @@ function App({promptVisible, onCommand, game}) {
const scaleY = height / 400
const scale = 0 || Math.min(scaleX, scaleY)
const [effects] = useLocalStorage('video')
console.log(effects)
const {fuzzing, flickering, scanLines, imageBackground} = effects
const [{fuzzing, flickering, scanLines, imageBackground}] = useLocalStorage('video')
useEffect(() => {
game.onChange(setState)
@ -27,15 +25,17 @@ function App({promptVisible, onCommand, game}) {
}, [game])
return (
<Provider value={state}>
<div style={{transform: `scale(${scale})`, overflow: 'hidden'}} className={styles.screen + `${flickering && ' flickering' || ''}${fuzzing && ' fuzzing' || ''}`}>
<Screen promptVisible={promptVisible} handleCommand={onCommand} showReflection={imageBackground}/>
{imageBackground && <div className={styles.overlay}>
<img alt="" src={backgroundURL}/>
</div>}
{scanLines && <div className="scan"></div>}
</div>
</Provider>
<StateProvider value={state}>
<LoadProvider value={game.loadGame.bind(game)}>
<div style={{transform: `scale(${scale})`, overflow: 'hidden'}} className={styles.screen + `${(flickering && ' flickering') || ''}${(fuzzing && ' fuzzing') || ''}`}>
<Screen promptVisible={promptVisible} handleCommand={onCommand} showReflection={imageBackground}/>
{imageBackground && <div className={styles.overlay}>
<img alt="" src={backgroundURL}/>
</div>}
{scanLines && <div className="scan"></div>}
</div>
</LoadProvider>
</StateProvider>
);
}

@ -1,20 +1,23 @@
import React from 'react'
import useGameState from '../../hooks/useGameState'
import useLoadGame from '../../hooks/useLoadGame'
import useLocalStorage from '../../hooks/useLocalStorage'
import {DateTime} from 'luxon'
import styles from './Options.module.css'
import useSharedState from '../../hooks/useSharedState'
export default function () {
const [currentTab, setCurrentTab] = useSharedState('optionsTab', 'video')
const [currentTab, setCurrentTab] = useSharedState('optionsTab', 'data')
const [, setCurrentMenu] = useSharedState('currentMenu')
const gameState = useGameState()
const saveFile1 = useLocalStorage('save1', null)
const saveFile2 = useLocalStorage('save2', null)
const saveFile3 = useLocalStorage('save3', null)
const loadGame = useLoadGame()
const [videoOptions, setVideoOptions] = useLocalStorage('video')
console.log(videoOptions)
function OptionButton({name}) {
return (
@ -44,12 +47,51 @@ export default function () {
)
}
function SaveFile({slot: [data, saveData], name}){
function save() {
if(data) {
const proceed = window.confirm(`Save over ${name}?`)
if(!proceed) return;
}
saveData(serializeState(gameState))
setCurrentMenu(null)
}
function load() {
const newData = deserializeState(data)
const proceed = window.confirm("Load save from " + name + "?")
if(!proceed) return;
loadGame(newData)
setCurrentMenu(null)
}
return (
<div className={styles.saveFile}>
<div className={styles.saveInfo}>
<p><u>{name}:</u></p>
{data ? (
<p>{DateTime.fromISO(data.saved).toLocal().toLocaleString(DateTime.DATETIME_SHORT)}</p>
) : (
<p>No data</p>
)}
</div>
<button onClick={save} className={styles.saveButton}>Save</button>
<button disabled={!data} onClick={load} className={styles.saveButton}>Load</button>
</div>
)
}
return (
<>
<div className={styles.container}>
<div className={styles.sidebar}>
<OptionButton name="data"/>
<OptionButton name="video"/>
<OptionButton name="save / load"/>
</div>
<div className={styles.settings}>
{currentTab === 'video' && (
@ -62,11 +104,45 @@ export default function () {
<OptionSetting name="imageBackground" label="Image Background"/>
</>
)}
{currentTab === 'save / load' && (
<p>Save / load</p>
{currentTab === 'data' && (
<>
<h3>Data Files</h3>
<SaveFile slot={saveFile1} name="File 1"/>
<SaveFile slot={saveFile2} name="File 2"/>
<SaveFile slot={saveFile3} name="File 3"/>
</>
)}
</div>
</div>
</>
)
}
function serializeState(state) {
return JSON.parse(JSON.stringify({...state, saved: DateTime.local().toISO()}, (key, value) => {
if(value instanceof Map)
return Array.from(value).reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {_serializedType: 'map'});
if(value instanceof Set)
throw new Error("Do not know how to safely serialize sets")
return value
}))
}
function deserializeState(state) {
return JSON.parse(JSON.stringify(state), (key, value) => {
if(value._serializedType === 'map') {
const map = new Map()
for(const item in value)
if(item !== '_serializedType')
map.set(item, value[item])
return map
}
return value
})
}

@ -35,3 +35,33 @@
position: relative;
top: 2px;
}
.saveFile:not(:first-child) {
margin-top: 20px;
}
.saveFile {
display: flex;
flex-direction: row;
}
.saveInfo {
flex: 1;
}
.saveFile p {
margin: 0;
}
.saveButton {
background: transparent;
color: white;
border: solid 1px white;
margin: 5px;
cursor: pointer;
}
.saveButton:disabled {
opacity: 0;
pointer-events: none;
}

@ -27,7 +27,7 @@ export default function Text({promptVisible, currentInput, currentScroll}) {
})}
</div>
<div className={styles.input + (!promptVisible ? ' ' + styles.hidden : '')}>
<input tabIndex="-1" value={currentInput}/>
<input readOnly tabIndex="-1" value={currentInput}/>
</div>
</div>
)

@ -37,6 +37,15 @@ export default class Game {
this.saveDraft()
}
loadGame(newState : GameState) {
this.gameState = newState
// Just to confirm there's no drafts lying around, and to notify everything
// of the change
this.beginDraft()
this.saveDraft()
}
outputCommand(commandString: string) {
const state = this.getState()
state.messages.push(new GameEventCommand(commandString))

@ -0,0 +1,9 @@
import React, {useContext} from 'react'
const SaveGameCtx = React.createContext()
export default function useSaveGame() {
return useContext(SaveGameCtx)
}
export const Provider = SaveGameCtx.Provider

@ -1,4 +1,4 @@
import {useState, useEffect} from 'react'
import {useEffect} from 'react'
import useSharedState from './useSharedState'
export default function useLocalStorage(key, initialValue) {
@ -19,7 +19,7 @@ export default function useLocalStorage(key, initialValue) {
} catch (error) {
console.log(error);
}
}, [storedValue])
}, [storedValue, key])
return [storedValue, setStoredValue];
}

@ -10,7 +10,7 @@ const updateSharedState = (key, state) => {
}
export default function useSharedState(key, initial) {
const [localState, setLocalState] = useState(initial)
const [localState, setLocalState] = useState(sharedState[key] || initial)
useEffect(() => {
const updateLocal = () => setLocalState(sharedState[key])

Loading…
Cancel
Save