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 = {
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");

View file

@ -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}

View file

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

View file

@ -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>

View file

@ -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

View file

@ -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");
}
}

View file

@ -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
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 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;

View file

@ -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)

View file

@ -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;
}