This commit is contained in:
Tobias Berger 2021-09-30 13:37:20 +02:00
parent 126f918082
commit 81a2f4d063
11 changed files with 328 additions and 176 deletions

View file

@ -11,10 +11,14 @@
const mainHeaderButtons = { const mainHeaderButtons = {
save() { save() {
console.log("save"); game.SaveHandler.save(game);
}, },
settings() { settings() {
openModal(SettingsModal, { settings: game.Settings }, { replace: true }); openModal(
SettingsModal,
{ settings: game.Settings.settings },
{ replace: true }
);
}, },
help() { help() {
console.log("help"); console.log("help");

View file

@ -25,8 +25,8 @@
function addMessage(message: string, messageType?: MessageType) { function addMessage(message: string, messageType?: MessageType) {
dispatch("addMessage", { message, messageType }); dispatch("addMessage", { message, messageType });
} }
function resetMessages() { function resetMessages(notify = true) {
dispatch("resetLog", { reset: true }); dispatch("resetLog", { notify });
} }
$: displayMessages = $messages.slice(-logLength).reverse(); $: displayMessages = $messages.slice(-logLength).reverse();
@ -48,7 +48,8 @@
addMessage("error" + $messages.length, MessageType.error); addMessage("error" + $messages.length, MessageType.error);
}}>Add Error</button }}>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"> <ol id="message-log">
{#each displayMessages as message, i (message)} {#each displayMessages as message, i (message)}
{#if i < logLength} {#if i < logLength}

View file

@ -5,7 +5,7 @@
export let isOpen: boolean; export let isOpen: boolean;
export let settings: typeof Settings; export let settings: typeof Settings["settings"];
</script> </script>
<BaseModal {isOpen}> <BaseModal {isOpen}>

View file

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import type { Tabs } from "../shark/Tabs"; import type { TabHandler } from "../shark/TabHandler";
export let tabs: typeof Tabs.AllTabs; export let tabs: typeof TabHandler.AllTabs;
export let selectedTab: typeof Tabs.currentTab; export let selectedTab: typeof TabHandler.currentTab;
$: tabEntries = Object.entries(tabs); $: tabEntries = Object.entries(tabs);
</script> </script>

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { AddMessageEvent } from "../shark/Message"; import type { AddMessageEvent, ResetMessagesEvent } from "../shark/Message";
import type { SharkGame } from "../shark/SharkGame"; import type { SharkGame } from "../shark/SharkGame";
import Log from "./Log.svelte"; import Log from "./Log.svelte";
import ResourceTable from "./ResourceTable/ResourceTable.svelte"; import ResourceTable from "./ResourceTable/ResourceTable.svelte";
@ -15,12 +15,12 @@
function handleAddMessage(event: CustomEvent<AddMessageEvent>) { function handleAddMessage(event: CustomEvent<AddMessageEvent>) {
game.Log.addMessage(event.detail.message, event.detail.messageType); game.Log.addMessage(event.detail.message, event.detail.messageType);
} }
function handleResetLog() { function handleResetLog(event: CustomEvent<ResetMessagesEvent>) {
game.Log.reset(); game.Log.reset(event.detail.notify);
} }
let logLength: number; let logLength: number;
game.Settings.subscribe((settings) => { game.Settings.settings.subscribe((settings) => {
logLength = settings.layout.logLength.current; logLength = settings.layout.logLength.current;
}); });
</script> </script>
@ -57,13 +57,13 @@
</svg> </svg>
</div> </div>
<TabSelector <TabSelector
tabs={game.Tabs.AllTabs} tabs={game.TabHandler.AllTabs}
bind:selectedTab={game.Tabs.currentTab} bind:selectedTab={game.TabHandler.currentTab}
/> />
</div> </div>
<div id="tab-content"> <div id="tab-content">
<svelte:component <svelte:component
this={game.Tabs.currentTab} this={game.TabHandler.currentTab}
on:addMessage={handleAddMessage} on:addMessage={handleAddMessage}
on:openModal on:openModal
on:closeModal on:closeModal

View file

@ -9,7 +9,7 @@ export class Log extends StaticClass {
static #maxLogLength: number; static #maxLogLength: number;
static init(): void { static init(): void {
Settings.subscribe((settings) => { Settings.settings.subscribe((settings) => {
Log.#maxLogLength = Math.max(...settings.layout.logLength.options); 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; Message.even = true;
this.messages.set([]); this.messages.set([]);
this.addMessage("Log cleared"); if (notify) this.addMessage("Log cleared");
} }
} }

View file

@ -1,5 +1,5 @@
export type AddMessageEvent = { message: string; messageType?: MessageType }; export type AddMessageEvent = { message: string; messageType?: MessageType };
export type ResetMessagesEvent = { reset: true }; export type ResetMessagesEvent = { notify?: boolean };
export enum MessageType { export enum MessageType {
message = "message", message = "message",

111
src/shark/SaveHandler.ts Normal file
View 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;
},
];
}

View file

@ -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 { 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: { layout: {
logLength: { logLength: {
current: 30, current: 30,
defaultValue: 30 as const, default: 30 as const,
name: "Log Messages" as const, name: "Log Messages" as const,
description: "The number of messages shown in the log" as const, description: "The number of messages shown in the log" as const,
options: [5, 10, 15, 20, 30, 60] as const, options: [5, 10, 15, 20, 30, 60] as const,
@ -15,7 +18,7 @@ export const Settings = writable({
appearance: { appearance: {
enableThemes: { enableThemes: {
current: true, current: true,
defaultValue: true as const, default: true as const,
name: "Enable Planet-dependent Styles" as const, name: "Enable Planet-dependent Styles" as const,
description: description:
"Whether page colors should change for different planets" as const, "Whether page colors should change for different planets" as const,
@ -23,7 +26,7 @@ export const Settings = writable({
}, },
theme: { theme: {
current: "marine", current: "marine",
defaultValue: "marine" as const, default: "marine" as const,
name: "Currently enabled theme" as const, name: "Currently enabled theme" as const,
description: description:
"Only applied if planet-dependent styles are enabled" as const, "Only applied if planet-dependent styles are enabled" as const,
@ -42,7 +45,7 @@ export const Settings = writable({
other: { other: {
updateCheck: { updateCheck: {
current: true, current: true,
defaultValue: true as const, default: true as const,
name: "Check for updates" as const, name: "Check for updates" as const,
description: "Whether to notify you of new updates" as const, description: "Whether to notify you of new updates" as const,
options: [true, false] 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) // To test scrolling of the settings modal (gonna use them to test saving later, too)
nil: { nil: {
nil1: { nil1: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil2: { nil2: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil3: { nil3: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil4: { nil4: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil5: { nil5: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil6: { nil6: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil7: { nil7: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil8: { nil8: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil9: { nil9: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil10: { nil10: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil11: { nil11: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil12: { nil12: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil13: { nil13: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil14: { nil14: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil15: { nil15: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil16: { nil16: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] as const, options: [true, false] as const,
}, },
nil17: { nil17: {
defaultValue: true as const, default: true as const,
name: "placeholder" as const, name: "placeholder" as const,
description: "placeholder." as const, description: "placeholder." as const,
options: [true, false] 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;

View file

@ -1,8 +1,9 @@
import { Resources } from "./data/Resources"; import { Resources } from "./data/Resources";
import { Log } from "./Log"; import { Log } from "./Log";
import { SaveHandler } from "./SaveHandler";
import { Settings } from "./Settings"; import { Settings } from "./Settings";
import { StaticClass } from "./StaticClass"; import { StaticClass } from "./StaticClass";
import { Tabs } from "./Tabs"; import { TabHandler } from "./TabHandler";
export class SharkGame extends StaticClass { export class SharkGame extends StaticClass {
static readonly #GAME_NAMES = [ static readonly #GAME_NAMES = [
@ -58,19 +59,22 @@ export class SharkGame extends StaticClass {
static readonly Settings = Settings; static readonly Settings = Settings;
static readonly Log = Log; static readonly Log = Log;
static readonly Resources = Resources; static readonly Resources = Resources;
static readonly Tabs = Tabs; static readonly TabHandler = TabHandler;
static readonly SaveHandler = SaveHandler;
static init(): void { static init(): void {
Settings.update((settings) => { Settings.settings.update((settings) => {
for (const settingsCategory of Object.values(settings)) { for (const settingsCategory of Object.values(settings)) {
for (const setting of Object.values(settingsCategory)) { for (const setting of Object.values(settingsCategory)) {
setting.current = setting.defaultValue; setting.current = setting.default;
} }
} }
return settings; return settings;
}); });
Log.init(); Log.init();
SaveHandler.load(this);
SharkGame.title = SharkGame.title =
SharkGame.#GAME_NAMES[ SharkGame.#GAME_NAMES[
Math.floor(Math.random() * SharkGame.#GAME_NAMES.length) Math.floor(Math.random() * SharkGame.#GAME_NAMES.length)

View file

@ -2,11 +2,11 @@ import Home from "../components/Tabs/Home.svelte";
import Lab from "../components/Tabs/Lab.svelte"; import Lab from "../components/Tabs/Lab.svelte";
import { StaticClass } from "./StaticClass"; import { StaticClass } from "./StaticClass";
export class Tabs extends StaticClass { export class TabHandler extends StaticClass {
static readonly AllTabs = { static readonly AllTabs = {
Home, Home,
Lab, Lab,
} as const; } as const;
static currentTab: typeof Tabs.AllTabs[keyof typeof Tabs.AllTabs] = static currentTab: typeof TabHandler.AllTabs[keyof typeof TabHandler.AllTabs] =
Tabs.AllTabs.Home; TabHandler.AllTabs.Home;
} }