Modals, settings

This commit is contained in:
Tobias Berger 2021-09-29 22:49:39 +02:00
parent a385305127
commit 7b4be2f1e2
12 changed files with 308 additions and 356 deletions

View file

@ -46,6 +46,7 @@
"sirv-cli": "^1.0.0", "sirv-cli": "^1.0.0",
"svelte": "^3.0.0", "svelte": "^3.0.0",
"svelte-check": "^2.0.0", "svelte-check": "^2.0.0",
"svelte-modals": "^1.0.4",
"svelte-preprocess": "^4.0.0", "svelte-preprocess": "^4.0.0",
"tslib": "^2.0.0", "tslib": "^2.0.0",
"typescript": "^4.0.0" "typescript": "^4.0.0"

View file

@ -1,12 +1,81 @@
<script lang="ts" context="module">
async function getCurrentHash(): Promise<string> {
const response = await fetch(
"https://api.github.com/repos/Toby222/svelte-sharg/commits/main",
{
mode: "cors",
}
);
if (response.status < 200 || response.status > 299) {
throw new Error(response.statusText);
} else {
const result = (await response.json()).sha;
return result;
}
}
let CURRENT_HASH: string;
getCurrentHash().then((hash) => (CURRENT_HASH = hash));
</script>
<script lang="ts"> <script lang="ts">
import Footer from "./components/Footer.svelte"; import Footer from "./components/Footer.svelte";
import Header from "./components/Header.svelte"; import Header from "./components/Header.svelte";
import Wrapper from "./components/Wrapper.svelte"; import Wrapper from "./components/Wrapper.svelte";
import { SharkGame } from "./shark/SharkGame"; import { SharkGame } from "./shark/SharkGame";
import { Modals, closeModal } from "svelte-modals";
import { onDestroy, onMount } from "svelte";
import type { Unsubscriber } from "svelte/store";
SharkGame.init(); SharkGame.init();
let root: HTMLElement;
let unsubscribeSettings: Unsubscriber;
let updateInterval: ReturnType<typeof setInterval> | undefined;
onMount(() => {
root = document.documentElement;
SharkGame.Settings.subscribe((settings) => {
root.classList.toggle("no-theme", !settings.enableThemes.current);
settings.theme.options.forEach((theme) => {
root.classList.toggle(theme, theme === settings.theme.current);
});
if (settings.updateCheck.current && updateInterval === undefined) {
updateInterval = setInterval(async () => {
if (
CURRENT_HASH !== undefined &&
CURRENT_HASH !== (await getCurrentHash())
) {
console.log("Updoot");
}
}, 6 * 60 * 1000);
} else if (
!settings.updateCheck.current &&
updateInterval !== undefined
) {
clearInterval(updateInterval);
}
});
});
onDestroy(() => {
unsubscribeSettings();
});
function handleKeyUp(event: KeyboardEvent) {
if (event.key === "Escape") {
closeModal();
}
}
</script> </script>
<Header title={SharkGame.title} /> <svelte:body on:keyup={handleKeyUp} />
<Modals>
<div slot="backdrop" id="modal-backdrop" on:click={closeModal} />
</Modals>
<Header game={SharkGame} title={SharkGame.title} />
<Wrapper game={SharkGame} /> <Wrapper game={SharkGame} />
<Footer /> <Footer />

View file

@ -1,5 +1,11 @@
<script lang="ts"> <script lang="ts">
import { openModal } from "svelte-modals";
import type { SharkGame } from "../shark/SharkGame";
import SettingsModal from "./Modals/SettingsModal.svelte";
export let title: string; export let title: string;
export let game: typeof SharkGame;
const discordLink = "https://discord.gg/eYqApFkFPY"; const discordLink = "https://discord.gg/eYqApFkFPY";
@ -8,7 +14,7 @@
console.log("save"); console.log("save");
}, },
settings() { settings() {
console.log("settings"); openModal(SettingsModal, { settings: game.Settings }, { replace: true });
}, },
help() { help() {
console.log("help"); console.log("help");

View file

@ -0,0 +1,58 @@
<script lang="ts">
import { closeModal } from "svelte-modals";
// provided by Modals
export let isOpen: boolean;
</script>
{#if isOpen}
<div role="dialog" class="modal">
<button class="close-modal" on:click={closeModal} />
<div class="modal-content">
<slot />
</div>
</div>
{/if}
<style lang="scss">
.modal {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1;
/* allow click-through to backdrop */
pointer-events: none;
> .close-modal {
position: fixed;
top: 5em;
right: 5em;
height: 4em;
width: 4em;
padding: 0;
pointer-events: auto;
&:before {
content: "✕";
font-size: 3em;
}
}
> .modal-content {
box-shadow: 0 0 5px 5px var(--color-darker);
border-radius: 6px;
padding: 16px;
background: var(--color-darker);
pointer-events: auto;
}
}
</style>

View file

@ -0,0 +1,76 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import type { Settings } from "../../shark/Settings";
import BaseModal from "./BaseModal.svelte";
export let isOpen: boolean;
type SettingsUnkownOptions = typeof Settings extends Writable<infer X>
? Writable<
Record<
keyof X,
Omit<X[keyof X], "options" | "current"> & {
current: unknown;
options: readonly unknown[];
}
>
>
: never;
export let settings: typeof Settings;
$: usableSettings = settings as SettingsUnkownOptions;
</script>
<BaseModal {isOpen}>
<table id="settings-modal">
<colgroup>
<col style="text-align: left;" />
<col />
</colgroup>
<tbody>
{#each Object.entries($usableSettings) as [settingName, setting] (settingName)}
<tr>
<td>
{setting.name}<br />
<span class="setting-description">({setting.description}) </span>
</td>
{#each setting.options as option}
<td
><button
disabled={setting.current === option}
on:click={() => {
setting.current = option;
}}>{option}</button
></td
>
{/each}
</tr>
{/each}</tbody
>
</table>
</BaseModal>
<style lang="scss">
#settings-modal {
display: flex;
flex-direction: column;
> tbody > tr {
> td:nth-child(1) {
text-align: left;
> .setting-description {
color: var(--color-lighter);
font-size: 80%;
}
}
> td > button {
font-size: 1em;
width: 6.5em;
height: 2em;
}
}
}
</style>

View file

@ -6,7 +6,6 @@
import TabSelector from "./TabSelector.svelte"; import TabSelector from "./TabSelector.svelte";
export let game: typeof SharkGame; export let game: typeof SharkGame;
$: Game = game;
let sidebarExpanded = false; let sidebarExpanded = false;
@ -14,19 +13,28 @@
sidebarExpanded = to ?? !sidebarExpanded; sidebarExpanded = to ?? !sidebarExpanded;
} }
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() {
Game.Log.reset(); game.Log.reset();
} }
let logLength: number;
game.Settings.subscribe((settings) => {
logLength = settings.logLength.current;
});
</script> </script>
<main> <main>
<div id="left-column" class={sidebarExpanded ? "expanded" : "collapsed"}> <div
<ResourceTable bind:resources={Game.Resources.Resources} /> id="left-column"
class:expanded={sidebarExpanded}
class:collapsed={!sidebarExpanded}
>
<ResourceTable resources={game.Resources.Resources} />
<Log <Log
bind:logLength={Game.Settings.logLength.current} {logLength}
bind:messages={Game.Log.messages} messages={game.Log.messages}
on:addMessage={handleAddMessage} on:addMessage={handleAddMessage}
on:resetLog={handleResetLog} on:resetLog={handleResetLog}
/> />
@ -35,6 +43,7 @@
<div id="right-top"> <div id="right-top">
<div <div
role="button" role="button"
name="sidebar toggle"
id="sidebar-toggle" id="sidebar-toggle"
on:click={(ev) => { on:click={(ev) => {
ev.cancelBubble = true; ev.cancelBubble = true;
@ -42,26 +51,22 @@
}} }}
> >
<svg width="1em" height="1em"> <svg width="1em" height="1em">
<line id="top" x1="0" y1="1px" x2="1em" y2="1px" /> <line x1="0" y1="0" x2="1em" y2="0" />
<line id="middle" x1="0" y1="0.5em" x2="1em" y2="0.5em" /> <line x1="0" y1="0.5em" x2="1em" y2="0.5em" />
<line <line x1="0" y1="1em" x2="1em" y2="1em" />
id="bottom"
x1="0"
y1="calc(1em - 1px)"
x2="1em"
y2="calc(1em - 1px)"
/>
</svg> </svg>
</div> </div>
<TabSelector <TabSelector
bind:tabs={Game.Tabs.AllTabs} tabs={game.Tabs.AllTabs}
bind:selectedTab={Game.Tabs.currentTab} bind:selectedTab={game.Tabs.currentTab}
/> />
</div> </div>
<div id="tab-content"> <div id="tab-content">
<svelte:component <svelte:component
this={Game.Tabs.currentTab} this={game.Tabs.currentTab}
on:addMessage={handleAddMessage} on:addMessage={handleAddMessage}
on:openModal
on:closeModal
/> />
</div> </div>
</div> </div>

View file

@ -6,6 +6,13 @@ import { StaticClass } from "./StaticClass";
export class Log extends StaticClass { export class Log extends StaticClass {
static messages = writable<Message[]>([]); static messages = writable<Message[]>([]);
static #maxLogLength: number;
static init(): void {
Settings.subscribe((settings) => {
Log.#maxLogLength = Math.max(...settings.logLength.options);
})();
}
static addMessage( static addMessage(
message: string, message: string,
@ -13,7 +20,7 @@ export class Log extends StaticClass {
): void { ): void {
this.messages.update((oldMessages) => this.messages.update((oldMessages) =>
[...oldMessages, new Message(message, messageType)].slice( [...oldMessages, new Message(message, messageType)].slice(
-Math.max(...Settings.logLength.options) -Log.#maxLogLength
) )
); );
} }

View file

@ -1,342 +1,47 @@
// 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 defaultValue?
export const Settings = { import { writable } from "svelte/store";
// Internal / No category
buyAmount: {
current: 1,
defaultValue: 1 as const,
options: [1, 10, 100, -3, -2, -1, "custom"] as const,
},
grottoMode: {
current: "simple",
defaultValue: "simple" as const,
options: ["simple", "advanced"] as const,
},
showPercentages: {
current: "absolute",
defaultValue: "absolute" as const,
options: ["absolute", "percentage"] as const,
},
// PERFORMANCE
framerate: {
current: 20,
defaultValue: 20 as const,
name: "Framerate/TPS" as const,
desc: "How fast to update the game." as const,
category: "PERFORMANCE" as const,
options: [1, 2, 5, 10, 20, 30] as const,
onChange(): void {
// main.applyFramerate();
console.warn("TODO");
},
},
showAnimations: {
current: true,
defaultValue: true as const,
name: "Show Animations" as const,
desc: "Whether to show animated transitions." as const,
category: "PERFORMANCE" as const,
options: [true, false] as const, // might remove this option? could be a pain to continue supporting it
},
// LAYOUT
minimizedTopbar: {
current: true,
defaultValue: true as const,
name: "Minimized Title Bar" as const,
desc: "Whether to minimize the title bar at the top." as const,
category: "LAYOUT" as const,
options: [true, false] as const,
onChange(): void {
// if (SharkGame.Settings.current["minimizedTopbar"]) {
// document.querySelector("body").classList.add("top-bar");
// $("#wrapper").removeClass("notMinimized");
// } else {
// document.querySelector("body").classList.remove("top-bar");
// $("#wrapper").addClass("notMinimized");
// }
console.warn("TODO");
},
},
groupResources: {
current: true,
defaultValue: true as const,
name: "Group Resources" as const,
desc: "Whether to categorize resources in the table." as const,
category: "LAYOUT" as const,
options: [true, false] as const,
onChange(): void {
// res.rebuildTable = true;
console.warn("TODO");
},
},
smallTable: {
current: false,
defaultValue: false as const,
name: "Smaller Table" as const,
desc: "Whether to make the stuff table smaller." as const,
category: "LAYOUT" as const,
options: [true, false] as const,
onChange(): void {
// res.rebuildTable = true;
console.warn("TODO");
},
},
buttonDisplayType: {
current: "pile",
defaultValue: "pile" as const,
name: "Home Sea Button Display" as const,
desc: "How to arrange buttons." as const,
category: "LAYOUT" as const,
options: ["list", "pile"] as const,
onChange(): void {
// SharkGame.TabHandler.changeTab(SharkGame.Tabs.current);
console.warn("TODO");
},
},
export const Settings = writable({
logLength: { logLength: {
current: 30, current: 30,
defaultValue: 30 as const, defaultValue: 30 as const,
name: "Max Log Messages" as const, name: "Max Log Messages" as const,
desc: "Max number of messages kept in the log." as const, description: "Max number of messages kept in the log." as const,
category: "LAYOUT" as const, category: "LAYOUT" as const,
options: [5, 10, 15, 20, 30, 60] as const, options: [5, 10, 15, 20, 30, 60] as const,
onChange(): void {
// log.correctLogLength();
},
}, },
sidebarWidth: {
current: "30%",
defaultValue: "30%" as const,
name: "Sidebar Width" as const,
desc: "How much screen space the sidebar should take." as const,
category: "LAYOUT" as const,
options: ["25%", "30%", "35%"] as const,
onChange(): void {
// const sidebar = $("#sidebar");
// if (SharkGame.Settings.current.showAnimations) {
// sidebar.animate(
// { width: SharkGame.Settings.current.sidebarWidth },
// 100
// );
// } else {
// sidebar.width(SharkGame.Settings.current.sidebarWidth);
// }
console.warn("TODO");
},
},
// APPEARANCE
colorCosts: {
current: "color",
defaultValue: "color" as const,
name: "Color Resource Names" as const,
desc: "How to color names of resources." as const,
category: "APPEARANCE" as const,
options: ["color", "bright", "none"] as const,
onChange(): void {
// res.rebuildTable = true;
// stats.recreateIncomeTable = true;
console.warn("TODO");
},
},
boldCosts: {
current: true,
defaultValue: true as const,
name: "Bold Resource Names" as const,
desc: "Should resource names be bolded?" as const,
options: [true, false] as const,
category: "APPEARANCE" as const,
onChange(): void {
// res.rebuildTable = true;
// stats.recreateIncomeTable = true;
console.warn("TODO");
},
},
alwaysSingularTooltip: {
current: false,
defaultValue: false as const,
name: "Tooltip Always Singular" as const,
desc: "Should the tooltip only show what one of each thing produces?" as const,
category: "APPEARANCE" as const,
options: [true, false] as const,
},
tooltipQuantityReminders: {
current: true,
defaultValue: true as const,
name: "Tooltip Amount Reminder" as const,
desc: "Should tooltips tell you much you own of stuff?" as const,
category: "APPEARANCE" as const,
options: [true, false] as const,
},
enableThemes: { enableThemes: {
current: true, current: true,
defaultValue: true as const, defaultValue: true as const,
name: "Enable Planet-dependent Styles" as const, name: "Enable Planet-dependent Styles" as const,
desc: "Should page colors change for different planets?" as const, description: "Should page colors change for different planets?" as const,
options: [true, false] as const, options: [true, false] as const,
category: "APPEARANCE" as const, category: "APPEARANCE" as const,
onChange(): void {
// if (SharkGame.Settings.enableThemes.current) {
// document.querySelector("body").classList.remove("no-theme");
// } else {
// document.querySelector("body").classList.add("no-theme");
// }
console.warn("TODO");
},
}, },
theme: {
showIcons: { current: "marine",
current: true, defaultValue: "marine" as const,
defaultValue: true as const, name: "Currently enabled theme" as const,
name: "Show Action Button icons" as const, description: "Changes the colors of the game" as const,
desc: "Show button icons?" as const, options: [
"abandoned",
"chaotic",
"frigid",
"haven",
"marine",
"shrouded",
"tempestuous",
"violent",
],
category: "APPEARANCE" as const, category: "APPEARANCE" as const,
options: [true, false] as const,
}, },
showTabImages: {
current: true,
defaultValue: true as const,
name: "Show Tab Header Images" as const,
desc: "Show art?" as const,
category: "APPEARANCE" as const,
options: [true, false] as const,
onChange(): void {
// SharkGame.TabHandler.changeTab(SharkGame.Tabs.current);
console.warn("TODO");
},
},
// ACCESSIBILITY
doAspectTable: {
current: "tree",
defaultValue: "tree" as const,
name: "Aspect Table or Tree" as const,
desc: "Draw a visual aspect tree or a more accessible aspect table?" as const,
category: "ACCESSIBILITY" as const,
options: ["tree", "table"] as const,
},
verboseTokenDescriptions: {
current: false,
defaultValue: false as const,
name: "Verbose Token" as const,
desc: "Should tokens display text saying where they are?" as const,
category: "ACCESSIBILITY" as const,
options: [true, false] as const,
onChange(): void {
// res.tokens.updateTokenDescriptions();
console.warn("TODO");
},
},
minuteHandEffects: {
current: true,
defaultValue: true as const,
name: "Minute Hand Special Effects" as const,
desc: "Should the minute hand glow a ton?" as const,
category: "ACCESSIBILITY" as const,
options: [true, false] as const,
onChange(): void {
// res.minuteHand.updatePowers();
console.warn("TODO");
},
},
// OTHER
idleEnabled: {
current: true,
defaultValue: true as const,
name: "Stored Offline Progress" as const,
desc: "Should the game store idle progress for later use? (otherwise, it will not go idle and will have real offline progress)" as const,
category: "OTHER" as const,
options: [true, false] as const,
onChange(): void {
// res.minuteHand.init();
console.warn("TODO");
},
},
showTooltips: {
current: true,
defaultValue: true as const,
name: "Tooltips" as const,
desc: "Whether to show informational tooltips when hovering over certain stuff." as const,
category: "OTHER" as const,
options: [true, false] as const,
},
updateCheck: { updateCheck: {
current: true, current: true,
defaultValue: true as const, defaultValue: true as const,
name: "Check for updates" as const, name: "Check for updates" as const,
desc: "Whether to notify you of new updates." as const, description: "Whether to notify you of new updates." as const,
category: "OTHER" as const,
options: [true, false] as const,
onChange(): void {
// clearInterval(SharkGame.Main.checkForUpdateHandler);
// if (SharkGame.Settings.current.updateCheck) {
// SharkGame.Main.checkForUpdateHandler = setInterval(
// main.checkForUpdates,
// 300000
// );
// }
console.warn("TODO");
},
},
offlineModeActive: {
current: true,
defaultValue: true as const,
name: "Offline Progress" as const,
desc: "Should there be ANY offline progress?" as const,
category: "OTHER" as const, category: "OTHER" as const,
options: [true, false] as const, options: [true, false] as const,
}, },
});
// SAVES (Needs to come last due to hard-coded import/export/wipe buttons at the bottom)
autosaveFrequency: {
// times given in minutes
current: 5,
defaultValue: 5 as const,
name: "Autosave Frequency" as const,
desc: "Number of minutes between autosaves." as const,
category: "SAVES" as const,
options: [1, 2, 5, 10, 30] as const,
onChange(): void {
// clearInterval(main.autosaveHandler);
// main.autosaveHandler = setInterval(
// main.autosave,
// SharkGame.Settings.current.autosaveFrequency * 60000
// );
// log.addMessage(
// "Now autosaving every " +
// SharkGame.Settings.current.autosaveFrequency +
// " minute" +
// sharktext.plural(SharkGame.Settings.current.autosaveFrequency) +
// "."
// );
console.warn("TODO");
},
},
};

View file

@ -61,9 +61,13 @@ export class SharkGame extends StaticClass {
static readonly Tabs = Tabs; static readonly Tabs = Tabs;
static init(): void { static init(): void {
for (const setting of Object.values(Settings)) { Settings.update((settings) => {
setting.current = setting.defaultValue; for (const setting of Object.values(settings)) {
} setting.current = setting.defaultValue;
}
return settings;
});
Log.init();
SharkGame.title = SharkGame.title =
SharkGame.#GAME_NAMES[ SharkGame.#GAME_NAMES[

View file

@ -35,13 +35,29 @@ button {
&:hover { &:hover {
box-shadow: 0 0 2px 2px var(--color-lighter); box-shadow: 0 0 2px 2px var(--color-lighter);
} }
&:active { &:active,
&:disabled {
background-color: var(--color-dark); background-color: var(--color-dark);
color: var(--color-light); color: var(--color-light);
box-shadow: 0 3px 7px 7px var(--color-darker) inset; box-shadow: 0 3px 7px 7px var(--color-darker) inset;
} }
&:disabled {
pointer-events: none;
border-color: var(--color-med);
}
&:not(:disabled) { &:not(:disabled) {
cursor: pointer; cursor: pointer;
} }
} }
#modal-backdrop {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: rgba(0, 0, 0, 0.3);
cursor: pointer;
}

View file

@ -27,11 +27,11 @@ $color-dark-haven: #053247;
$color-darker-haven: #001d2b; $color-darker-haven: #001d2b;
// marine theme (default) // marine theme (default)
$color-lighter: #8e9fc0; $color-lighter-marine: #8e9fc0;
$color-light: #536892; $color-light-marine: #536892;
$color-med: #2e4372; $color-med-marine: #2e4372;
$color-dark: #152a55; $color-dark-marine: #152a55;
$color-darker: #061639; $color-darker-marine: #061639;
//shrouded theme //shrouded theme
$color-lighter-shrouded: #22bec3; $color-lighter-shrouded: #22bec3;
@ -77,19 +77,19 @@ $color-discovery-dark4: darken(#ace3d1, 65%);
$color-text: white; $color-text: white;
:root { :root {
--color-lighter: #{$color-lighter}; --color-lighter: #{$color-lighter-marine};
--color-light: #{$color-light}; --color-light: #{$color-light-marine};
--color-med: #{$color-med}; --color-med: #{$color-med-marine};
--color-dark: #{$color-dark}; --color-dark: #{$color-dark-marine};
--color-darker: #{$color-darker}; --color-darker: #{$color-darker-marine};
--color-title: #{rgba( --color-title: #{rgba(
red($color-med), red($color-med-marine),
green($color-med), green($color-med-marine),
blue($color-med), blue($color-med-marine),
0.2 0.2
)}; )};
:not(.no-theme) { &:not(.no-theme) {
&.abandoned { &.abandoned {
--color-lighter: #{$color-lighter-abandoned}; --color-lighter: #{$color-lighter-abandoned};
--color-light: #{$color-light-abandoned}; --color-light: #{$color-light-abandoned};

View file

@ -1688,6 +1688,11 @@ svelte-check@^2.0.0:
svelte-preprocess "^4.0.0" svelte-preprocess "^4.0.0"
typescript "*" typescript "*"
svelte-modals@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/svelte-modals/-/svelte-modals-1.0.4.tgz#d1e7b2301f274417e03897ea6b76e3ddfb4be4fc"
integrity sha512-nwYEF7PlxX4uqKU1zRCi+fcECxlEOEFGNGcazdyGsFK0LMngrxwqq3q77xBabGNU7DXYxGyDXq/9YHzyHyCAFg==
svelte-preprocess@^4.0.0: svelte-preprocess@^4.0.0:
version "4.9.5" version "4.9.5"
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-4.9.5.tgz#e11bdf3fcdacbd90188cdf29a7371030991f9eba" resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-4.9.5.tgz#e11bdf3fcdacbd90188cdf29a7371030991f9eba"