Saving
This commit is contained in:
parent
126f918082
commit
81a2f4d063
11 changed files with 328 additions and 176 deletions
|
@ -11,10 +11,14 @@
|
|||
|
||||
const mainHeaderButtons = {
|
||||
save() {
|
||||
console.log("save");
|
||||
game.SaveHandler.save(game);
|
||||
},
|
||||
settings() {
|
||||
openModal(SettingsModal, { settings: game.Settings }, { replace: true });
|
||||
openModal(
|
||||
SettingsModal,
|
||||
{ settings: game.Settings.settings },
|
||||
{ replace: true }
|
||||
);
|
||||
},
|
||||
help() {
|
||||
console.log("help");
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
function addMessage(message: string, messageType?: MessageType) {
|
||||
dispatch("addMessage", { message, messageType });
|
||||
}
|
||||
function resetMessages() {
|
||||
dispatch("resetLog", { reset: true });
|
||||
function resetMessages(notify = true) {
|
||||
dispatch("resetLog", { notify });
|
||||
}
|
||||
|
||||
$: displayMessages = $messages.slice(-logLength).reverse();
|
||||
|
@ -48,7 +48,8 @@
|
|||
addMessage("error" + $messages.length, MessageType.error);
|
||||
}}>Add Error</button
|
||||
>
|
||||
<button on:click={resetMessages}>Clear log</button>
|
||||
<button on:click={() => resetMessages()}>Clear log; notify</button>
|
||||
<button on:click={() => resetMessages(false)}>Clear log; no-notify</button>
|
||||
<ol id="message-log">
|
||||
{#each displayMessages as message, i (message)}
|
||||
{#if i < logLength}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
export let isOpen: boolean;
|
||||
|
||||
export let settings: typeof Settings;
|
||||
export let settings: typeof Settings["settings"];
|
||||
</script>
|
||||
|
||||
<BaseModal {isOpen}>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import type { Tabs } from "../shark/Tabs";
|
||||
import type { TabHandler } from "../shark/TabHandler";
|
||||
|
||||
export let tabs: typeof Tabs.AllTabs;
|
||||
export let selectedTab: typeof Tabs.currentTab;
|
||||
export let tabs: typeof TabHandler.AllTabs;
|
||||
export let selectedTab: typeof TabHandler.currentTab;
|
||||
$: tabEntries = Object.entries(tabs);
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { AddMessageEvent } from "../shark/Message";
|
||||
import type { AddMessageEvent, ResetMessagesEvent } from "../shark/Message";
|
||||
import type { SharkGame } from "../shark/SharkGame";
|
||||
import Log from "./Log.svelte";
|
||||
import ResourceTable from "./ResourceTable/ResourceTable.svelte";
|
||||
|
@ -15,12 +15,12 @@
|
|||
function handleAddMessage(event: CustomEvent<AddMessageEvent>) {
|
||||
game.Log.addMessage(event.detail.message, event.detail.messageType);
|
||||
}
|
||||
function handleResetLog() {
|
||||
game.Log.reset();
|
||||
function handleResetLog(event: CustomEvent<ResetMessagesEvent>) {
|
||||
game.Log.reset(event.detail.notify);
|
||||
}
|
||||
|
||||
let logLength: number;
|
||||
game.Settings.subscribe((settings) => {
|
||||
game.Settings.settings.subscribe((settings) => {
|
||||
logLength = settings.layout.logLength.current;
|
||||
});
|
||||
</script>
|
||||
|
@ -57,13 +57,13 @@
|
|||
</svg>
|
||||
</div>
|
||||
<TabSelector
|
||||
tabs={game.Tabs.AllTabs}
|
||||
bind:selectedTab={game.Tabs.currentTab}
|
||||
tabs={game.TabHandler.AllTabs}
|
||||
bind:selectedTab={game.TabHandler.currentTab}
|
||||
/>
|
||||
</div>
|
||||
<div id="tab-content">
|
||||
<svelte:component
|
||||
this={game.Tabs.currentTab}
|
||||
this={game.TabHandler.currentTab}
|
||||
on:addMessage={handleAddMessage}
|
||||
on:openModal
|
||||
on:closeModal
|
||||
|
|
|
@ -9,7 +9,7 @@ export class Log extends StaticClass {
|
|||
static #maxLogLength: number;
|
||||
|
||||
static init(): void {
|
||||
Settings.subscribe((settings) => {
|
||||
Settings.settings.subscribe((settings) => {
|
||||
Log.#maxLogLength = Math.max(...settings.layout.logLength.options);
|
||||
})();
|
||||
}
|
||||
|
@ -25,9 +25,9 @@ export class Log extends StaticClass {
|
|||
);
|
||||
}
|
||||
|
||||
static reset(): void {
|
||||
static reset(notify = true): void {
|
||||
Message.even = true;
|
||||
this.messages.set([]);
|
||||
this.addMessage("Log cleared");
|
||||
if (notify) this.addMessage("Log cleared");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export type AddMessageEvent = { message: string; messageType?: MessageType };
|
||||
export type ResetMessagesEvent = { reset: true };
|
||||
export type ResetMessagesEvent = { notify?: boolean };
|
||||
|
||||
export enum MessageType {
|
||||
message = "message",
|
||||
|
|
111
src/shark/SaveHandler.ts
Normal file
111
src/shark/SaveHandler.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
import type { Message, MessageType } from "./Message";
|
||||
import type { Settings } from "./Settings";
|
||||
import type { SharkGame } from "./SharkGame";
|
||||
import { StaticClass } from "./StaticClass";
|
||||
import type { TabHandler } from "./TabHandler";
|
||||
|
||||
const __EMPTY_OBJECT = {};
|
||||
type Version0Save = typeof __EMPTY_OBJECT;
|
||||
type Version1Save = Version0Save & {
|
||||
version: 1;
|
||||
messages: {
|
||||
message: string;
|
||||
type: MessageType;
|
||||
}[];
|
||||
selectedTab: keyof typeof TabHandler["AllTabs"];
|
||||
settings: ReturnType<typeof Settings["getCurrent"]>;
|
||||
};
|
||||
|
||||
type CurrentVersionSave = Version1Save;
|
||||
type Save = Version0Save | Version1Save;
|
||||
|
||||
export class SaveHandler extends StaticClass {
|
||||
static readonly saveName = "sharg-save";
|
||||
|
||||
static async save(game: typeof SharkGame): Promise<void> {
|
||||
const messages = await new Promise<Message[]>((resolve) => {
|
||||
game.Log.messages.subscribe((messages) => {
|
||||
resolve(messages);
|
||||
})();
|
||||
});
|
||||
|
||||
const allTabsEntries = Object.entries(
|
||||
game.TabHandler.AllTabs
|
||||
) as unknown as [
|
||||
keyof typeof TabHandler["AllTabs"],
|
||||
typeof TabHandler["AllTabs"][keyof typeof TabHandler["AllTabs"]]
|
||||
][];
|
||||
const selectedTabIndex = allTabsEntries.findIndex(
|
||||
([, tab]) => tab === game.TabHandler.currentTab
|
||||
);
|
||||
const selectedTabName = allTabsEntries[selectedTabIndex][0];
|
||||
|
||||
const save: CurrentVersionSave = {
|
||||
version: 1,
|
||||
selectedTab: selectedTabName,
|
||||
settings: game.Settings.getCurrent(),
|
||||
messages: messages.map((message) => {
|
||||
return {
|
||||
message: message.message,
|
||||
type: message.type,
|
||||
};
|
||||
}),
|
||||
};
|
||||
const encodedSave = JSON.stringify(save);
|
||||
|
||||
localStorage.setItem(SaveHandler.saveName, encodedSave);
|
||||
}
|
||||
|
||||
static async load(game: typeof SharkGame): Promise<unknown> {
|
||||
const localSave = localStorage.getItem(SaveHandler.saveName);
|
||||
const loadedSave = JSON.parse(localSave ?? "{}");
|
||||
const saveVersion = loadedSave.version ?? 0;
|
||||
|
||||
const migrators = SaveHandler.migrators.slice(saveVersion);
|
||||
|
||||
let save = loadedSave;
|
||||
for (let i = 0; i < migrators.length; i++) {
|
||||
console.debug(
|
||||
`Executing save migrator ${i + 1} / ${migrators.length} (${
|
||||
saveVersion + i + 1
|
||||
})`
|
||||
);
|
||||
save = migrators[i](save);
|
||||
}
|
||||
|
||||
const fullSave = save as CurrentVersionSave;
|
||||
|
||||
game.Settings.settings.update((settings) => {
|
||||
Object.entries(settings).forEach(([categoryName, categorySettings]) => {
|
||||
Object.entries(categorySettings).forEach(([settingName, setting]) => {
|
||||
setting.current =
|
||||
fullSave.settings[`${categoryName};${settingName}`] ??
|
||||
setting.default;
|
||||
});
|
||||
});
|
||||
return settings;
|
||||
});
|
||||
|
||||
game.Log.reset(false);
|
||||
|
||||
fullSave.messages.forEach((message) => {
|
||||
game.Log.addMessage(message.message, message.type);
|
||||
});
|
||||
game.TabHandler.currentTab = game.TabHandler.AllTabs[fullSave.selectedTab];
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
static migrators: ((save: Save) => Save)[] = [
|
||||
(): Version1Save => {
|
||||
const newSave = {
|
||||
version: 1 as const,
|
||||
messages: [],
|
||||
selectedTab: "Home" as const,
|
||||
settings: {},
|
||||
};
|
||||
|
||||
return newSave;
|
||||
},
|
||||
];
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
// How to make current just be undefined here, but keep the type of defaultValue?
|
||||
// How to make current just be undefined here, but keep the type of default?
|
||||
|
||||
import { writable } from "svelte/store";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { StaticClass } from "./StaticClass";
|
||||
|
||||
export const Settings = writable({
|
||||
export class Settings extends StaticClass {
|
||||
static readonly #settings = {
|
||||
layout: {
|
||||
logLength: {
|
||||
current: 30,
|
||||
defaultValue: 30 as const,
|
||||
default: 30 as const,
|
||||
name: "Log Messages" as const,
|
||||
description: "The number of messages shown in the log" as const,
|
||||
options: [5, 10, 15, 20, 30, 60] as const,
|
||||
|
@ -15,7 +18,7 @@ export const Settings = writable({
|
|||
appearance: {
|
||||
enableThemes: {
|
||||
current: true,
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "Enable Planet-dependent Styles" as const,
|
||||
description:
|
||||
"Whether page colors should change for different planets" as const,
|
||||
|
@ -23,7 +26,7 @@ export const Settings = writable({
|
|||
},
|
||||
theme: {
|
||||
current: "marine",
|
||||
defaultValue: "marine" as const,
|
||||
default: "marine" as const,
|
||||
name: "Currently enabled theme" as const,
|
||||
description:
|
||||
"Only applied if planet-dependent styles are enabled" as const,
|
||||
|
@ -42,7 +45,7 @@ export const Settings = writable({
|
|||
other: {
|
||||
updateCheck: {
|
||||
current: true,
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "Check for updates" as const,
|
||||
description: "Whether to notify you of new updates" as const,
|
||||
options: [true, false] as const,
|
||||
|
@ -51,106 +54,135 @@ export const Settings = writable({
|
|||
// To test scrolling of the settings modal (gonna use them to test saving later, too)
|
||||
nil: {
|
||||
nil1: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil2: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil3: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil4: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil5: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil6: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil7: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil8: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil9: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil10: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil11: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil12: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil13: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil14: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil15: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil16: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
nil17: {
|
||||
defaultValue: true as const,
|
||||
default: true as const,
|
||||
name: "placeholder" as const,
|
||||
description: "placeholder." as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
static readonly settings = writable(Settings.#settings);
|
||||
|
||||
static getSettings(): SettingsRecord {
|
||||
return Object.seal(Object.assign({}, Settings.#settings));
|
||||
}
|
||||
static getCurrent(): Record<`${string};${string}`, Setting> {
|
||||
const current = this.getSettings();
|
||||
const result: Record<`${string};${string}`, Setting> = {};
|
||||
for (const [categoryName, category] of Object.entries(current)) {
|
||||
for (const [settingName, setting] of Object.entries(category)) {
|
||||
result[`${categoryName};${settingName}`] = setting.current;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export type Setting = {
|
||||
current: unknown;
|
||||
readonly default: unknown;
|
||||
readonly name: unknown;
|
||||
readonly description: unknown;
|
||||
readonly options: readonly unknown[];
|
||||
};
|
||||
export type SettingsRecord = typeof Settings["settings"] extends Writable<
|
||||
infer X
|
||||
>
|
||||
? X
|
||||
: never;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Resources } from "./data/Resources";
|
||||
import { Log } from "./Log";
|
||||
import { SaveHandler } from "./SaveHandler";
|
||||
import { Settings } from "./Settings";
|
||||
import { StaticClass } from "./StaticClass";
|
||||
import { Tabs } from "./Tabs";
|
||||
import { TabHandler } from "./TabHandler";
|
||||
|
||||
export class SharkGame extends StaticClass {
|
||||
static readonly #GAME_NAMES = [
|
||||
|
@ -58,19 +59,22 @@ export class SharkGame extends StaticClass {
|
|||
static readonly Settings = Settings;
|
||||
static readonly Log = Log;
|
||||
static readonly Resources = Resources;
|
||||
static readonly Tabs = Tabs;
|
||||
static readonly TabHandler = TabHandler;
|
||||
static readonly SaveHandler = SaveHandler;
|
||||
|
||||
static init(): void {
|
||||
Settings.update((settings) => {
|
||||
Settings.settings.update((settings) => {
|
||||
for (const settingsCategory of Object.values(settings)) {
|
||||
for (const setting of Object.values(settingsCategory)) {
|
||||
setting.current = setting.defaultValue;
|
||||
setting.current = setting.default;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
});
|
||||
Log.init();
|
||||
|
||||
SaveHandler.load(this);
|
||||
|
||||
SharkGame.title =
|
||||
SharkGame.#GAME_NAMES[
|
||||
Math.floor(Math.random() * SharkGame.#GAME_NAMES.length)
|
||||
|
|
|
@ -2,11 +2,11 @@ import Home from "../components/Tabs/Home.svelte";
|
|||
import Lab from "../components/Tabs/Lab.svelte";
|
||||
import { StaticClass } from "./StaticClass";
|
||||
|
||||
export class Tabs extends StaticClass {
|
||||
export class TabHandler extends StaticClass {
|
||||
static readonly AllTabs = {
|
||||
Home,
|
||||
Lab,
|
||||
} as const;
|
||||
static currentTab: typeof Tabs.AllTabs[keyof typeof Tabs.AllTabs] =
|
||||
Tabs.AllTabs.Home;
|
||||
static currentTab: typeof TabHandler.AllTabs[keyof typeof TabHandler.AllTabs] =
|
||||
TabHandler.AllTabs.Home;
|
||||
}
|
Reference in a new issue