Moved properties from player data to new settings and state objects

This commit is contained in:
thepaperpilot 2021-09-05 18:53:04 -05:00
parent bc808098ff
commit eaf47bb946
25 changed files with 367 additions and 285 deletions

View file

@ -11,11 +11,11 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import themes from "./data/themes";
import player from "./game/player";
import modInfo from "./data/modInfo.json"; import modInfo from "./data/modInfo.json";
import { mapState } from "./util/vue"; import themes from "./data/themes";
import settings from "./game/settings";
import "./main.css"; import "./main.css";
import { mapSettings } from "./util/vue";
export default defineComponent({ export default defineComponent({
name: "App", name: "App",
@ -23,9 +23,9 @@ export default defineComponent({
return { useHeader: modInfo.useHeader }; return { useHeader: modInfo.useHeader };
}, },
computed: { computed: {
...mapState(["showTPS"]), ...mapSettings(["showTPS"]),
theme() { theme() {
return themes[player.theme].variables; return themes[settings.theme].variables;
} }
}, },
methods: { methods: {

View file

@ -178,6 +178,7 @@ import themes from "@/data/themes";
import { ProgressDisplay, Shape } from "@/game/enums"; import { ProgressDisplay, Shape } from "@/game/enums";
import { layers } from "@/game/layers"; import { layers } from "@/game/layers";
import player from "@/game/player"; import player from "@/game/player";
import settings from "@/game/settings";
import { BoardNode, BoardNodeAction, NodeLabel, NodeType } from "@/typings/features/board"; import { BoardNode, BoardNodeAction, NodeLabel, NodeType } from "@/typings/features/board";
import { getNodeTypeProperty } from "@/util/features"; import { getNodeTypeProperty } from "@/util/features";
import { defineComponent, PropType } from "vue"; import { defineComponent, PropType } from "vue";
@ -279,18 +280,18 @@ export default defineComponent({
return getNodeTypeProperty(this.nodeType, this.node, "progress") || 0; return getNodeTypeProperty(this.nodeType, this.node, "progress") || 0;
}, },
backgroundColor(): string { backgroundColor(): string {
return themes[player.theme].variables["--background"]; return themes[settings.theme].variables["--background"];
}, },
outlineColor(): string { outlineColor(): string {
return ( return (
getNodeTypeProperty(this.nodeType, this.node, "outlineColor") || getNodeTypeProperty(this.nodeType, this.node, "outlineColor") ||
themes[player.theme].variables["--outline"] themes[settings.theme].variables["--outline"]
); );
}, },
fillColor(): string { fillColor(): string {
return ( return (
getNodeTypeProperty(this.nodeType, this.node, "fillColor") || getNodeTypeProperty(this.nodeType, this.node, "fillColor") ||
themes[player.theme].variables["--raised-background"] themes[settings.theme].variables["--raised-background"]
); );
}, },
progressColor(): string { progressColor(): string {
@ -299,7 +300,7 @@ export default defineComponent({
titleColor(): string { titleColor(): string {
return ( return (
getNodeTypeProperty(this.nodeType, this.node, "titleColor") || getNodeTypeProperty(this.nodeType, this.node, "titleColor") ||
themes[player.theme].variables["--foreground"] themes[settings.theme].variables["--foreground"]
); );
}, },
progressDisplay(): ProgressDisplay { progressDisplay(): ProgressDisplay {

View file

@ -17,6 +17,7 @@
import themes from "@/data/themes"; import themes from "@/data/themes";
import { layers } from "@/game/layers"; import { layers } from "@/game/layers";
import player from "@/game/player"; import player from "@/game/player";
import settings from "@/game/settings";
import { Infobox } from "@/typings/features/infobox"; import { Infobox } from "@/typings/features/infobox";
import { coerceComponent, InjectLayerMixin } from "@/util/vue"; import { coerceComponent, InjectLayerMixin } from "@/util/vue";
import { Component, defineComponent } from "vue"; import { Component, defineComponent } from "vue";
@ -67,7 +68,7 @@ export default defineComponent({
return player.layers[this.layer].infoboxes[this.id]; return player.layers[this.layer].infoboxes[this.id];
}, },
stacked(): boolean { stacked(): boolean {
return themes[player.theme].stackedInfoboxes; return themes[settings.theme].stackedInfoboxes;
} }
}, },
methods: { methods: {

View file

@ -1,11 +1,12 @@
<template> <template>
<label class="field"> <label class="field">
<input type="checkbox" class="toggle" :checked="value" @input="handleInput" /> <input type="checkbox" class="toggle" :checked="value" @input="handleInput" />
<span>{{ title }}</span> <component :is="display" />
</label> </label>
</template> </template>
<script lang="ts"> <script lang="ts">
import { coerceComponent } from "@/util/vue";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
// Reference: https://codepen.io/finnhvman/pen/pOeyjE // Reference: https://codepen.io/finnhvman/pen/pOeyjE
@ -16,6 +17,11 @@ export default defineComponent({
value: Boolean value: Boolean
}, },
emits: ["change"], emits: ["change"],
computed: {
display() {
return coerceComponent(this.title || "", "span");
}
},
methods: { methods: {
handleInput(e: InputEvent) { handleInput(e: InputEvent) {
this.$emit("change", (e.target as HTMLInputElement).checked); this.$emit("change", (e.target as HTMLInputElement).checked);

View file

@ -49,6 +49,7 @@ import modInfo from "@/data/modInfo.json";
import themes from "@/data/themes"; import themes from "@/data/themes";
import { layers } from "@/game/layers"; import { layers } from "@/game/layers";
import player from "@/game/player"; import player from "@/game/player";
import settings from "@/game/settings";
import { Subtab } from "@/typings/features/subtab"; import { Subtab } from "@/typings/features/subtab";
import { coerceComponent } from "@/util/vue"; import { coerceComponent } from "@/util/vue";
import { Component, defineComponent } from "vue"; import { Component, defineComponent } from "vue";
@ -76,7 +77,7 @@ export default defineComponent({
return layers[this.layer].name || this.layer; return layers[this.layer].name || this.layer;
}, },
floating(): boolean { floating(): boolean {
return themes[player.theme].floatingTabs; return themes[settings.theme].floatingTabs;
}, },
style(): Array<Partial<CSSStyleDeclaration> | undefined> { style(): Array<Partial<CSSStyleDeclaration> | undefined> {
const style = []; const style = [];

View file

@ -23,7 +23,8 @@
import themes from "@/data/themes"; import themes from "@/data/themes";
import { layers } from "@/game/layers"; import { layers } from "@/game/layers";
import player from "@/game/player"; import player from "@/game/player";
import { Microtab, MicrotabFamily } from "@/typings/features/subtab"; import settings from "@/game/settings";
import { Microtab } from "@/typings/features/subtab";
import { coerceComponent, InjectLayerMixin } from "@/util/vue"; import { coerceComponent, InjectLayerMixin } from "@/util/vue";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
@ -40,9 +41,9 @@ export default defineComponent({
inject: ["tab"], inject: ["tab"],
computed: { computed: {
floating() { floating() {
return themes[player.theme].floatingTabs; return themes[settings.theme].floatingTabs;
}, },
tabFamily(): MicrotabFamily { tabFamily() {
return layers[this.layer].microtabs![this.family]; return layers[this.layer].microtabs![this.family];
}, },
microtabs() { microtabs() {
@ -61,13 +62,13 @@ export default defineComponent({
activeMicrotab() { activeMicrotab() {
return this.id != undefined return this.id != undefined
? this.tabFamily.data[this.id] ? this.tabFamily.data[this.id]
: this.tabFamily.activeMicrotab; : this.tabFamily.activeMicrotab!;
}, },
embed() { embed() {
return this.activeMicrotab!.embedLayer; return this.activeMicrotab!.embedLayer;
}, },
display() { display() {
return this.activeMicrotab!.display && coerceComponent(this.activeMicrotab!.display!); return this.activeMicrotab!.display && coerceComponent(this.activeMicrotab!.display);
} }
}, },
methods: { methods: {

View file

@ -46,8 +46,9 @@
<script lang="ts"> <script lang="ts">
import modInfo from "@/data/modInfo.json"; import modInfo from "@/data/modInfo.json";
import player from "@/game/player"; import player from "@/game/player";
import state from "@/game/state";
import Decimal, { format } from "@/util/bignum"; import Decimal, { format } from "@/util/bignum";
import { mapState } from "@/util/vue"; import { mapPlayer, mapState } from "@/util/vue";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
export default defineComponent({ export default defineComponent({
@ -57,13 +58,14 @@ export default defineComponent({
return { discordName, discordLink, format, showSaves: false }; return { discordName, discordLink, format, showSaves: false };
}, },
computed: { computed: {
...mapState(["hasNaN", "autosave"]), ...mapPlayer(["autosave"]),
...mapState(["hasNaN"]),
path(): string | undefined { path(): string | undefined {
return player.NaNPath?.join("."); return state.NaNPath?.join(".");
}, },
previous(): any { previous(): unknown {
if (player.NaNReceiver && this.property) { if (state.NaNReceiver && this.property) {
return player.NaNReceiver[this.property]; return state.NaNReceiver[this.property];
} }
return null; return null;
}, },
@ -71,29 +73,29 @@ export default defineComponent({
return player.devSpeed === 0; return player.devSpeed === 0;
}, },
property(): string | undefined { property(): string | undefined {
return player.NaNPath?.slice(-1)[0]; return state.NaNPath?.slice(-1)[0];
} }
}, },
methods: { methods: {
setZero() { setZero() {
if (player.NaNReceiver && this.property) { if (state.NaNReceiver && this.property) {
player.NaNReceiver[this.property] = new Decimal(0); state.NaNReceiver[this.property] = new Decimal(0);
player.hasNaN = false; state.hasNaN = false;
} }
}, },
setOne() { setOne() {
if (player.NaNReceiver && this.property) { if (state.NaNReceiver && this.property) {
player.NaNReceiver[this.property] = new Decimal(1); state.NaNReceiver[this.property] = new Decimal(1);
player.hasNaN = false; state.hasNaN = false;
} }
}, },
setPrev() { setPrev() {
player.hasNaN = false; state.hasNaN = false;
}, },
ignore() { ignore() {
if (player.NaNReceiver && this.property) { if (state.NaNReceiver && this.property) {
player.NaNReceiver[this.property] = new Decimal(NaN); state.NaNReceiver[this.property] = new Decimal(NaN);
player.hasNaN = false; state.hasNaN = false;
} }
}, },
setAutosave(autosave: boolean) { setAutosave(autosave: boolean) {

View file

@ -20,18 +20,26 @@
@change="setMSDisplay" @change="setMSDisplay"
default="all" default="all"
/> />
<Toggle <Toggle title="Show TPS" :value="showTPS" @change="toggleSettingsOption('showTPS')" />
title="Offline Production"
:value="offlineProd"
@change="toggleOption('offlineProd')"
/>
<Toggle title="Autosave" :value="autosave" @change="toggleOption('autosave')" />
<Toggle title="Pause game" :value="paused" @change="togglePaused" />
<Toggle title="Show TPS" :value="showTPS" @change="toggleOption('showTPS')" />
<Toggle <Toggle
title="Hide Maxed Challenges" title="Hide Maxed Challenges"
:value="hideChallenges" :value="hideChallenges"
@change="toggleOption('hideChallenges')" @change="toggleSettingsOption('hideChallenges')"
/>
<Toggle
title="Offline Production<tooltip display='Save-specific'>*</tooltip>"
:value="offlineProd"
@change="togglePlayerOption('offlineProd')"
/>
<Toggle
title="Autosave<tooltip display='Save-specific'>*</tooltip>"
:value="autosave"
@change="togglePlayerOption('autosave')"
/>
<Toggle
title="Pause game<tooltip display='Save-specific'>*</tooltip>"
:value="paused"
@change="togglePaused"
/> />
</template> </template>
</Modal> </Modal>
@ -41,9 +49,12 @@
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import themes, { Themes } from "@/data/themes"; import themes, { Themes } from "@/data/themes";
import { camelToTitle } from "@/util/common"; import { camelToTitle } from "@/util/common";
import { mapState } from "@/util/vue"; import { mapPlayer, mapSettings } from "@/util/vue";
import player from "@/game/player"; import player from "@/game/player";
import { MilestoneDisplay } from "@/game/enums"; import { MilestoneDisplay } from "@/game/enums";
import { PlayerData } from "@/typings/player";
import settings from "@/game/settings";
import { Settings } from "@/typings/settings";
export default defineComponent({ export default defineComponent({
name: "Options", name: "Options",
@ -57,27 +68,31 @@ export default defineComponent({
label: camelToTitle(theme), label: camelToTitle(theme),
value: theme value: theme
})), })),
msDisplayOptions: ["all", "last", "configurable", "incomplete", "none"].map(option => ({ msDisplayOptions: Object.values(MilestoneDisplay).map(option => ({
label: camelToTitle(option), label: camelToTitle(option),
value: option value: option
})) }))
}; };
}, },
computed: { computed: {
...mapState(["autosave", "offlineProd", "showTPS", "hideChallenges", "theme", "msDisplay"]), ...mapSettings(["showTPS", "hideChallenges", "theme", "msDisplay"]),
...mapPlayer(["autosave", "offlineProd"]),
paused() { paused() {
return player.devSpeed === 0; return player.devSpeed === 0;
} }
}, },
methods: { methods: {
toggleOption(option: string) { togglePlayerOption(option: keyof PlayerData) {
player[option] = !player[option]; player[option] = !player[option];
}, },
toggleSettingsOption(option: keyof Settings) {
settings[option] = !settings[option];
},
setTheme(theme: Themes) { setTheme(theme: Themes) {
player.theme = theme; settings.theme = theme;
}, },
setMSDisplay(msDisplay: MilestoneDisplay) { setMSDisplay(msDisplay: MilestoneDisplay) {
player.msDisplay = msDisplay; settings.msDisplay = msDisplay;
}, },
togglePaused() { togglePaused() {
player.devSpeed = this.paused ? 1 : 0; player.devSpeed = this.paused ? 1 : 0;
@ -90,4 +105,9 @@ export default defineComponent({
.header { .header {
margin-bottom: -10px; margin-bottom: -10px;
} }
* >>> .tooltip-container {
display: inline;
margin-left: 5px;
}
</style> </style>

View file

@ -3,17 +3,19 @@
<template v-slot:header> <template v-slot:header>
<h2>Saves Manager</h2> <h2>Saves Manager</h2>
</template> </template>
<template v-slot:body v-sortable="{ update, handle: '.handle' }"> <template v-slot:body>
<save <div v-sortable="{ update, handle: '.handle' }">
v-for="(save, index) in saves" <save
:key="index" v-for="(save, index) in saves"
:save="save" :key="index"
@open="openSave(save.id)" :save="save"
@export="exportSave(save.id)" @open="openSave(save.id)"
@editSave="name => editSave(save.id, name)" @export="exportSave(save.id)"
@duplicate="duplicateSave(save.id)" @editSave="name => editSave(save.id, name)"
@delete="deleteSave(save.id)" @duplicate="duplicateSave(save.id)"
/> @delete="deleteSave(save.id)"
/>
</div>
</template> </template>
<template v-slot:footer> <template v-slot:footer>
<div class="modal-footer"> <div class="modal-footer">
@ -55,8 +57,9 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import modInfo from "@/data/modInfo.json";
import player from "@/game/player"; import player from "@/game/player";
import settings from "@/game/settings";
import state from "@/game/state";
import { PlayerData } from "@/typings/player"; import { PlayerData } from "@/typings/player";
import { getUniqueID, loadSave, newSave, save } from "@/util/save"; import { getUniqueID, loadSave, newSave, save } from "@/util/save";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
@ -86,7 +89,10 @@ export default defineComponent({
bank bank
} as { } as {
importingFailed: boolean; importingFailed: boolean;
saves: Record<string, Partial<PlayerData>>; saves: Record<
string,
Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown }
>;
saveToImport: string; saveToImport: string;
bank: Array<{ label: string; value: string }>; bank: Array<{ label: string; value: string }>;
}; };
@ -100,39 +106,36 @@ export default defineComponent({
}, },
methods: { methods: {
loadSaveData() { loadSaveData() {
try { this.saves = settings.saves.reduce(
const { saves } = JSON.parse( (
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!))) acc: Record<
); string,
this.saves = saves.reduce( Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown }
(acc: Record<string, Partial<PlayerData>>, curr: string) => { >,
try { curr: string
acc[curr] = JSON.parse( ) => {
decodeURIComponent(escape(atob(localStorage.getItem(curr)!))) try {
); const save = localStorage.getItem(curr);
if (save == null) {
acc[curr] = { error: `Save with id "${curr}" doesn't exist`, id: curr };
} else {
acc[curr] = JSON.parse(decodeURIComponent(escape(atob(save))));
acc[curr].id = curr; acc[curr].id = curr;
} catch (error) {
console.warn(`Can't load save with id "${curr}"`, error);
acc[curr] = { error, id: curr };
} }
return acc; } catch (error) {
}, console.warn(`Can't load save with id "${curr}"`, error);
{} acc[curr] = { error, id: curr };
); }
} catch (e) { return acc;
this.saves = { [player.id]: player }; },
const modData = { active: player.id, saves: [player.id] }; {}
localStorage.setItem( );
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
}
}, },
exportSave(id: string) { exportSave(id: string) {
let saveToExport; let saveToExport;
if (player.id === id) { if (player.id === id) {
save(); save();
saveToExport = player.saveToExport; saveToExport = state.saveToExport;
} else { } else {
saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(this.saves[id])))); saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(this.saves[id]))));
} }
@ -157,40 +160,18 @@ export default defineComponent({
btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))) btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
); );
const modData = JSON.parse( settings.saves.push(playerData.id);
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves.push(playerData.id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
this.saves[playerData.id] = playerData; this.saves[playerData.id] = playerData;
}, },
deleteSave(id: string) { deleteSave(id: string) {
const modData = JSON.parse( settings.saves = settings.saves.filter((save: string) => save !== id);
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves = modData.saves.filter((save: string) => save !== id);
localStorage.removeItem(id); localStorage.removeItem(id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
delete this.saves[id]; delete this.saves[id];
}, },
openSave(id: string) { openSave(id: string) {
this.saves[player.id].time = player.time; this.saves[player.id].time = player.time;
save(); save();
loadSave(this.saves[id]); loadSave(this.saves[id]);
const modData = JSON.parse(
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.active = id;
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
}, },
newSave() { newSave() {
const playerData = newSave(); const playerData = newSave();
@ -204,14 +185,7 @@ export default defineComponent({
btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))) btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
); );
const modData = JSON.parse( settings.saves.push(playerData.id);
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves.push(playerData.id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
this.saves[playerData.id] = playerData; this.saves[playerData.id] = playerData;
}, },
editSave(id: string, newName: string) { editSave(id: string, newName: string) {
@ -227,7 +201,6 @@ export default defineComponent({
} }
}, },
importSave(text: string) { importSave(text: string) {
console.log(text);
this.saveToImport = text; this.saveToImport = text;
if (text) { if (text) {
this.$nextTick(() => { this.$nextTick(() => {
@ -247,14 +220,7 @@ export default defineComponent({
this.saveToImport = ""; this.saveToImport = "";
this.importingFailed = false; this.importingFailed = false;
const modData = JSON.parse( settings.saves.push(id);
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves.push(id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
} catch (e) { } catch (e) {
this.importingFailed = true; this.importingFailed = true;
} }
@ -264,14 +230,7 @@ export default defineComponent({
} }
}, },
update(e: { newIndex: number; oldIndex: number }) { update(e: { newIndex: number; oldIndex: number }) {
const modData = JSON.parse( settings.saves.splice(e.newIndex, 0, settings.saves.splice(e.oldIndex, 1)[0]);
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves.splice(e.newIndex, 0, modData.saves.splice(e.oldIndex, 1)[0]);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
} }
} }
}); });

View file

@ -3,7 +3,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import player from "@/game/player"; import state from "@/game/state";
import Decimal, { formatWhole } from "@/util/bignum"; import Decimal, { formatWhole } from "@/util/bignum";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
@ -13,8 +13,8 @@ export default defineComponent({
tps() { tps() {
return formatWhole( return formatWhole(
Decimal.div( Decimal.div(
player.lastTenTicks.length, state.lastTenTicks.length,
player.lastTenTicks.reduce((acc, curr) => acc + curr, 0) state.lastTenTicks.reduce((acc, curr) => acc + curr, 0)
) )
); );
} }

View file

@ -18,6 +18,7 @@
import themes from "@/data/themes"; import themes from "@/data/themes";
import { layers } from "@/game/layers"; import { layers } from "@/game/layers";
import player from "@/game/player"; import player from "@/game/player";
import settings from "@/game/settings";
import { Subtab } from "@/typings/features/subtab"; import { Subtab } from "@/typings/features/subtab";
import { InjectLayerMixin } from "@/util/vue"; import { InjectLayerMixin } from "@/util/vue";
import { defineComponent, PropType } from "vue"; import { defineComponent, PropType } from "vue";
@ -36,7 +37,7 @@ export default defineComponent({
emits: ["selectTab"], emits: ["selectTab"],
computed: { computed: {
floating(): boolean { floating(): boolean {
return themes[player.theme].floatingTabs; return themes[settings.theme].floatingTabs;
}, },
style(): Array<Partial<CSSStyleDeclaration> | undefined> { style(): Array<Partial<CSSStyleDeclaration> | undefined> {
return [ return [

View file

@ -27,7 +27,7 @@
<script lang="ts"> <script lang="ts">
import modInfo from "@/data/modInfo.json"; import modInfo from "@/data/modInfo.json";
import { layers } from "@/game/layers"; import { layers } from "@/game/layers";
import { coerceComponent, mapState } from "@/util/vue"; import { coerceComponent, mapPlayer } from "@/util/vue";
import { Component, defineComponent } from "vue"; import { Component, defineComponent } from "vue";
export default defineComponent({ export default defineComponent({
@ -36,7 +36,7 @@ export default defineComponent({
return { useHeader: modInfo.useHeader }; return { useHeader: modInfo.useHeader };
}, },
computed: { computed: {
...mapState(["tabs"]), ...mapPlayer(["tabs"]),
components() { components() {
return Object.keys(layers).reduce( return Object.keys(layers).reduce(
(acc: Record<string, Component | string | false>, curr) => { (acc: Record<string, Component | string | false>, curr) => {

View file

@ -1230,11 +1230,11 @@ export default {
}, },
effect() { effect() {
if (!hasChallenge(this.layer, this.id)) return 0; if (!hasChallenge(this.layer, this.id)) return 0;
if (player.layers[this.layer].challenges![this.id] == new Decimal(1)) if (Decimal.eq(player.layers[this.layer].challenges![this.id], 1))
return 50; return 50;
if (player.layers[this.layer].challenges![this.id] == new Decimal(2)) if (Decimal.eq(player.layers[this.layer].challenges![this.id], 2))
return 60; return 60;
if (player.layers[this.layer].challenges![this.id] == new Decimal(3)) if (Decimal.eq(player.layers[this.layer].challenges![this.id], 3))
return 70; return 70;
}, },
completionLimit() { completionLimit() {

View file

@ -81,7 +81,7 @@ const main = {
<span v-if="player.points.lt('1e1e6')"> points</span> <span v-if="player.points.lt('1e1e6')"> points</span>
</div> </div>
<div v-if="Decimal.gt(pointGain, 0)"> <div v-if="Decimal.gt(pointGain, 0)">
({{ player.oompsMag != 0 ? format(player.oomps) + " OOM" + (player.oompsMag < 0 ? "^OOM" : player.oompsMag > 1 ? "^" + player.oompsMag : "") + "s" : formatSmall(pointGain) }}/sec) ({{ state.oompsMag != 0 ? format(state.oomps) + " OOM" + (state.oompsMag < 0 ? "^OOM" : state.oompsMag > 1 ? "^" + state.oompsMag : "") + "s" : formatSmall(pointGain) }}/sec)
</div> </div>
<spacer /> <spacer />
<modal :show="false"> <modal :show="false">

View file

@ -3,11 +3,7 @@ import modInfo from "@/data/modInfo.json";
import Decimal, { DecimalSource } from "@/util/bignum"; import Decimal, { DecimalSource } from "@/util/bignum";
import { layers } from "./layers"; import { layers } from "./layers";
import player from "./player"; import player from "./player";
import state from "./state";
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function updatePopups(diff: number) {
// TODO
}
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function updateParticles(diff: number) { function updateParticles(diff: number) {
@ -16,21 +12,21 @@ function updateParticles(diff: number) {
function updateOOMPS(diff: DecimalSource) { function updateOOMPS(diff: DecimalSource) {
if (player.points != undefined) { if (player.points != undefined) {
player.oompsMag = 0; state.oompsMag = 0;
if (player.points.lte(new Decimal(1e100))) { if (player.points.lte(new Decimal(1e100))) {
player.lastPoints = player.points; state.lastPoints = player.points;
return; return;
} }
let curr = player.points; let curr = player.points;
let prev = (player.lastPoints as Decimal) || new Decimal(0); let prev = (state.lastPoints as Decimal) || new Decimal(0);
player.lastPoints = curr; state.lastPoints = curr;
if (curr.gt(prev)) { if (curr.gt(prev)) {
if (curr.gte("10^^8")) { if (curr.gte("10^^8")) {
curr = curr.slog(1e10); curr = curr.slog(1e10);
prev = prev.slog(1e10); prev = prev.slog(1e10);
player.oomps = curr.sub(prev).div(diff); state.oomps = curr.sub(prev).div(diff);
player.oompsMag = -1; state.oompsMag = -1;
} else { } else {
while ( while (
curr curr
@ -38,13 +34,13 @@ function updateOOMPS(diff: DecimalSource) {
.log(10) .log(10)
.div(diff) .div(diff)
.gte("100") && .gte("100") &&
player.oompsMag <= 5 && state.oompsMag <= 5 &&
prev.gt(0) prev.gt(0)
) { ) {
curr = curr.log(10); curr = curr.log(10);
prev = prev.log(10); prev = prev.log(10);
player.oomps = curr.sub(prev).div(diff); state.oomps = curr.sub(prev).div(diff);
player.oompsMag++; state.oompsMag++;
} }
} }
} }
@ -118,11 +114,10 @@ function update() {
const trueDiff = diff; const trueDiff = diff;
// Always update UI // Always update UI
updatePopups(trueDiff);
updateParticles(trueDiff); updateParticles(trueDiff);
player.lastTenTicks.push(trueDiff); state.lastTenTicks.push(trueDiff);
if (player.lastTenTicks.length > 10) { if (state.lastTenTicks.length > 10) {
player.lastTenTicks = player.lastTenTicks.slice(1); state.lastTenTicks = state.lastTenTicks.slice(1);
} }
// Stop here if the game is paused on the win screen // Stop here if the game is paused on the win screen
@ -130,7 +125,7 @@ function update() {
return; return;
} }
// Stop here if the player had a NaN value // Stop here if the player had a NaN value
if (player.hasNaN) { if (state.hasNaN) {
return; return;
} }

View file

@ -36,6 +36,7 @@ import clone from "lodash.clonedeep";
import { isRef } from "vue"; import { isRef } from "vue";
import { ProgressDisplay, Shape } from "./enums"; import { ProgressDisplay, Shape } from "./enums";
import { default as playerProxy } from "./player"; import { default as playerProxy } from "./player";
import settings from "./settings";
export const layers: Record<string, Readonly<Layer>> = {}; export const layers: Record<string, Readonly<Layer>> = {};
export const hotkeys: Hotkey[] = []; export const hotkeys: Hotkey[] = [];
@ -217,7 +218,7 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
for (const id in layer.challenges.data) { for (const id in layer.challenges.data) {
layer.challenges.data[id].shown = function() { layer.challenges.data[id].shown = function() {
return ( return (
this.unlocked !== false && (playerProxy.hideChallenges === false || !this.maxed) this.unlocked !== false && (settings.hideChallenges === false || !this.maxed)
); );
}; };
layer.challenges.data[id].completed = function() { layer.challenges.data[id].completed = function() {
@ -376,7 +377,7 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
if (!this.unlocked) { if (!this.unlocked) {
return false; return false;
} }
switch (playerProxy.msDisplay) { switch (settings.msDisplay) {
default: default:
case "all": case "all":
return true; return true;

View file

@ -1,15 +1,12 @@
import { Themes } from "@/data/themes";
import { PlayerData } from "@/typings/player"; import { PlayerData } from "@/typings/player";
import Decimal from "@/util/bignum"; import Decimal from "@/util/bignum";
import { isPlainObject } from "@/util/common"; import { isPlainObject } from "@/util/common";
import { reactive } from "vue"; import { reactive } from "vue";
import { ImportingStatus, MilestoneDisplay } from "./enums"; import transientState from "./state";
const state = reactive<PlayerData>({ const state = reactive<PlayerData>({
id: "", id: "",
points: new Decimal(0), points: new Decimal(0),
oomps: new Decimal(0),
oompsMag: 0,
name: "", name: "",
tabs: [], tabs: [],
time: -1, time: -1,
@ -18,22 +15,11 @@ const state = reactive<PlayerData>({
offlineTime: null, offlineTime: null,
timePlayed: new Decimal(0), timePlayed: new Decimal(0),
keepGoing: false, keepGoing: false,
lastTenTicks: [],
showTPS: true,
msDisplay: MilestoneDisplay.All,
hideChallenges: false,
theme: Themes.Nordic,
subtabs: {}, subtabs: {},
minimized: {}, minimized: {},
modID: "", modID: "",
modVersion: "", modVersion: "",
justLoaded: false, justLoaded: false,
hasNaN: false,
NaNPath: [],
NaNReceiver: null,
importing: ImportingStatus.NotImporting,
saveToImport: "",
saveToExport: "",
layers: {} layers: {}
}); });
@ -65,7 +51,7 @@ const playerHandler: ProxyHandler<Record<string, any>> = {
receiver: ProxyConstructor receiver: ProxyConstructor
): boolean { ): boolean {
if ( if (
!state.hasNaN && !transientState.hasNaN &&
((typeof value === "number" && isNaN(value)) || ((typeof value === "number" && isNaN(value)) ||
(value instanceof Decimal && (value instanceof Decimal &&
(isNaN(value.sign) || isNaN(value.layer) || isNaN(value.mag)))) (isNaN(value.sign) || isNaN(value.layer) || isNaN(value.mag))))
@ -81,9 +67,9 @@ const playerHandler: ProxyHandler<Record<string, any>> = {
) )
) { ) {
state.autosave = false; state.autosave = false;
state.hasNaN = true; transientState.hasNaN = true;
state.NaNPath = [...target.__path, property]; transientState.NaNPath = [...target.__path, property];
state.NaNReceiver = (receiver as unknown) as Record<string, unknown>; transientState.NaNReceiver = (receiver as unknown) as Record<string, unknown>;
console.error( console.error(
`Attempted to set NaN value`, `Attempted to set NaN value`,
[...target.__path, property], [...target.__path, property],

72
src/game/settings.ts Normal file
View file

@ -0,0 +1,72 @@
import modInfo from "@/data/modInfo.json";
import { Themes } from "@/data/themes";
import { Settings } from "@/typings/settings";
import { isPlainObject } from "@/util/common";
import { hardReset } from "@/util/save";
import { reactive } from "vue";
import { MilestoneDisplay } from "./enums";
const state = reactive<Settings>({
active: "",
saves: [],
showTPS: true,
msDisplay: MilestoneDisplay.All,
hideChallenges: false,
theme: Themes.Nordic
});
const settingsHandler: ProxyHandler<Record<string, any>> = {
get(target: Record<string, any>, key: string): any {
if (key === "__state") {
return target[key];
}
if (target.__state[key] == undefined) {
return;
}
if (isPlainObject(target.__state[key])) {
if (target.__state[key] !== target[key]?.__state) {
target[key] = new Proxy({ __state: target.__state[key] }, settingsHandler);
}
return target[key];
}
return target.__state[key];
},
set(target: Record<string, any>, property: string, value: any): boolean {
target.__state[property] = value;
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(state)))));
return true;
},
ownKeys(target: Record<string, any>) {
return Reflect.ownKeys(target.__state);
},
has(target: Record<string, any>, key: string) {
return Reflect.has(target.__state, key);
}
};
export default window.settings = new Proxy({ __state: state }, settingsHandler) as Settings;
export function loadSettings(): void {
try {
const item: string | null = localStorage.getItem(modInfo.id);
if (item != null && item !== "") {
const settings = JSON.parse(decodeURIComponent(escape(atob(item))));
if (typeof settings === "object") {
Object.assign(state, settings);
}
}
// eslint-disable-next-line no-empty
} catch {}
}
export const hardResetSettings = (window.hardResetSettings = () => {
Object.assign(state, {
active: "",
saves: [],
showTPS: true,
msDisplay: MilestoneDisplay.All,
hideChallenges: false,
theme: Themes.Nordic
});
hardReset();
});

44
src/game/state.ts Normal file
View file

@ -0,0 +1,44 @@
import { Transient } from "@/typings/transient";
import Decimal from "@/util/bignum";
import { isPlainObject } from "@/util/common";
import { reactive } from "vue";
const state = reactive<Transient>({
lastTenTicks: [],
hasNaN: false,
NaNPath: [],
lastPoints: new Decimal(0),
saveToExport: "",
oomps: new Decimal(0),
oompsMag: 0
});
const stateHandler: ProxyHandler<Record<string, any>> = {
get(target: Record<string, any>, key: string): any {
if (key === "__state") {
return target[key];
}
if (target.__state[key] == undefined) {
return;
}
if (isPlainObject(target.__state[key])) {
if (target.__state[key] !== target[key]?.__state) {
target[key] = new Proxy({ __state: target.__state[key] }, stateHandler);
}
return target[key];
}
return target.__state[key];
},
set(target: Record<string, any>, property: string, value: any): boolean {
target.__state[property] = value;
return true;
},
ownKeys(target: Record<string, any>) {
return Reflect.ownKeys(target.__state);
},
has(target: Record<string, any>, key: string) {
return Reflect.has(target.__state, key);
}
};
export default window.state = new Proxy({ __state: state }, stateHandler) as Transient;

View file

@ -1,14 +1,19 @@
import Decimal, { DecimalSource } from "@/util/bignum"; import Decimal, { DecimalSource } from "@/util/bignum";
import { App } from "vue"; import { App } from "vue";
import { PlayerData } from "./player"; import { PlayerData } from "./player";
import { Settings } from "./settings";
import { Transient } from "./transient";
declare global { declare global {
interface Window { interface Window {
vue: App; vue: App;
save: () => void; save: () => void;
hardReset: () => void; hardReset: () => void;
hardResetSettings: () => void;
layers: Dictionary<typeof Proxy>; layers: Dictionary<typeof Proxy>;
player: PlayerData; player: PlayerData;
state: Transient;
settings: Settings;
Decimal: typeof Decimal; Decimal: typeof Decimal;
exponentialFormat: ( exponentialFormat: (
num: DecimalSource, num: DecimalSource,

View file

@ -1,21 +1,11 @@
import { Themes } from "@/data/themes"; import Decimal, { DecimalSource } from "@/util/bignum";
import { DecimalSource } from "@/lib/break_eternity";
import Decimal from "@/util/bignum";
import { BoardData } from "./features/board"; import { BoardData } from "./features/board";
import { MilestoneDisplay } from "./features/milestone";
import { State } from "./state"; import { State } from "./state";
export interface ModSaveData {
active?: string;
saves?: string[];
}
export interface PlayerData { export interface PlayerData {
id: string; id: string;
devSpeed?: DecimalSource; devSpeed?: DecimalSource;
points: Decimal; points: Decimal;
oomps: Decimal;
oompsMag: number;
name: string; name: string;
tabs: Array<string>; tabs: Array<string>;
time: number; time: number;
@ -24,11 +14,6 @@ export interface PlayerData {
offlineTime: Decimal | null; offlineTime: Decimal | null;
timePlayed: Decimal; timePlayed: Decimal;
keepGoing: boolean; keepGoing: boolean;
lastTenTicks: Array<number>;
showTPS: boolean;
msDisplay: MilestoneDisplay;
hideChallenges: boolean;
theme: Themes;
subtabs: { subtabs: {
[index: string]: { [index: string]: {
mainTabs?: string; mainTabs?: string;
@ -39,12 +24,6 @@ export interface PlayerData {
modID: string; modID: string;
modVersion: string; modVersion: string;
justLoaded: boolean; justLoaded: boolean;
hasNaN: boolean;
NaNPath?: Array<string>;
NaNReceiver?: Record<string, unknown> | null;
importing: ImportingStatus;
saveToImport: string;
saveToExport: string;
layers: Record<string, LayerSaveData>; layers: Record<string, LayerSaveData>;
[index: string]: unknown; [index: string]: unknown;
} }

13
src/typings/settings.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
import { Themes } from "@/data/themes";
import { MilestoneDisplay } from "@/game/enums";
// This is the global save data, persists between individual saves
export interface Settings {
active: string;
saves: string[];
showTPS: boolean;
msDisplay: MilestoneDisplay;
hideChallenges: boolean;
theme: Themes;
[index: string]: unknown;
}

13
src/typings/transient.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
import Decimal from "@/lib/break_eternity";
// Save data that doesn't persist between reloads
export interface Transient {
lastTenTicks: number[];
hasNaN: bool;
NaNPath?: string[];
NaNReceiver?: Record<string, unknown>;
saveToExport: string;
lastPoints: Decimal;
oomps: Decimal;
oompsMag: number;
}

View file

@ -1,9 +1,9 @@
import { fixOldSave, getInitialLayers, getStartingData } from "@/data/mod"; import { fixOldSave, getInitialLayers, getStartingData } from "@/data/mod";
import modInfo from "@/data/modInfo.json"; import modInfo from "@/data/modInfo.json";
import { Themes } from "@/data/themes";
import { ImportingStatus, MilestoneDisplay } from "@/game/enums";
import player from "@/game/player"; import player from "@/game/player";
import { ModSaveData, PlayerData } from "@/typings/player"; import settings, { loadSettings } from "@/game/settings";
import state from "@/game/state";
import { PlayerData } from "@/typings/player";
import Decimal from "./bignum"; import Decimal from "./bignum";
export function getInitialStore(playerData: Partial<PlayerData> = {}): PlayerData { export function getInitialStore(playerData: Partial<PlayerData> = {}): PlayerData {
@ -11,8 +11,6 @@ export function getInitialStore(playerData: Partial<PlayerData> = {}): PlayerDat
{ {
id: `${modInfo.id}-0`, id: `${modInfo.id}-0`,
points: new Decimal(0), points: new Decimal(0),
oomps: new Decimal(0),
oompsMag: 0,
name: "Default Save", name: "Default Save",
tabs: modInfo.initialTabs.slice(), tabs: modInfo.initialTabs.slice(),
time: Date.now(), time: Date.now(),
@ -21,61 +19,29 @@ export function getInitialStore(playerData: Partial<PlayerData> = {}): PlayerDat
offlineTime: new Decimal(0), offlineTime: new Decimal(0),
timePlayed: new Decimal(0), timePlayed: new Decimal(0),
keepGoing: false, keepGoing: false,
lastTenTicks: [],
showTPS: true,
msDisplay: MilestoneDisplay.All,
hideChallenges: false,
theme: Themes.Nordic,
subtabs: {}, subtabs: {},
minimized: {}, minimized: {},
modID: modInfo.id, modID: modInfo.id,
modVersion: modInfo.versionNumber, modVersion: modInfo.versionNumber,
layers: {}, layers: {},
justLoaded: false, justLoaded: false,
...getStartingData(), ...getStartingData()
// Values that don't get loaded/saved
hasNaN: false,
NaNPath: [],
NaNReceiver: null,
importing: ImportingStatus.NotImporting,
saveToImport: "",
saveToExport: ""
}, },
playerData playerData
) as PlayerData; ) as PlayerData;
} }
export function save(): void { export function save(): void {
/* eslint-disable @typescript-eslint/no-unused-vars */ state.saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(player.__state))));
const { localStorage.setItem(player.id, state.saveToExport);
hasNaN,
NaNPath,
NaNReceiver,
importing,
saveToImport,
saveToExport,
...playerData
} = player.__state as PlayerData;
/* eslint-enable @typescript-eslint/no-unused-vars */
player.saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(playerData))));
localStorage.setItem(player.id, player.saveToExport);
} }
export async function load(): Promise<void> { export async function load(): Promise<void> {
// Load global settings
loadSettings();
try { try {
let modData: string | ModSaveData | null = localStorage.getItem(modInfo.id); const save = localStorage.getItem(settings.active);
if (modData == null) {
await loadSave(newSave());
return;
}
modData = JSON.parse(decodeURIComponent(escape(atob(modData)))) as ModSaveData;
if (modData?.active == null) {
await loadSave(newSave());
return;
}
const save = localStorage.getItem(modData.active);
if (save == null) { if (save == null) {
await loadSave(newSave()); await loadSave(newSave());
return; return;
@ -85,7 +51,7 @@ export async function load(): Promise<void> {
await loadSave(newSave()); await loadSave(newSave());
return; return;
} }
playerData.id = modData.active; playerData.id = settings.active;
await loadSave(playerData); await loadSave(playerData);
} catch (e) { } catch (e) {
await loadSave(newSave()); await loadSave(newSave());
@ -97,21 +63,7 @@ export function newSave(): PlayerData {
const playerData = getInitialStore({ id }); const playerData = getInitialStore({ id });
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))); localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))));
const rawModData = localStorage.getItem(modInfo.id); settings.saves.push(id);
if (rawModData == null) {
const modData = { active: id, saves: [id] };
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
} else {
const modData = JSON.parse(decodeURIComponent(escape(atob(rawModData))));
modData.saves.push(id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
}
return playerData; return playerData;
} }
@ -150,6 +102,7 @@ export async function loadSave(playerData: Partial<PlayerData>): Promise<void> {
} }
} }
player.justLoaded = true; player.justLoaded = true;
settings.active = player.id;
} }
export function applyPlayerData<T extends Record<string, any>>( export function applyPlayerData<T extends Record<string, any>>(
@ -191,7 +144,4 @@ window.onbeforeunload = () => {
window.save = save; window.save = save;
export const hardReset = (window.hardReset = async () => { export const hardReset = (window.hardReset = async () => {
await loadSave(newSave()); await loadSave(newSave());
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!))));
modData.active = player.id;
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
}); });

View file

@ -1,8 +1,13 @@
import { hasWon, pointGain } from "@/data/mod"; import { hasWon, pointGain } from "@/data/mod";
import { layers } from "@/game/layers"; import { layers } from "@/game/layers";
import player from "@/game/player"; import player from "@/game/player";
import settings from "@/game/settings";
import state from "@/game/state";
import { Feature, Features, GridFeatures } from "@/typings/features/feature"; import { Feature, Features, GridFeatures } from "@/typings/features/feature";
import { Layer } from "@/typings/layer"; import { Layer } from "@/typings/layer";
import { PlayerData } from "@/typings/player";
import { Settings } from "@/typings/settings";
import { Transient } from "@/typings/transient";
import { App, Component, ComponentOptions, defineComponent, inject, PropType } from "vue"; import { App, Component, ComponentOptions, defineComponent, inject, PropType } from "vue";
import Decimal, * as numberUtils from "./bignum"; import Decimal, * as numberUtils from "./bignum";
import { import {
@ -34,7 +39,7 @@ export function setVue(vm: App): void {
// Pass in various data that the template could potentially use // Pass in various data that the template could potentially use
const data = function(): Record<string, unknown> { const data = function(): Record<string, unknown> {
return { Decimal, player, layers, hasWon, pointGain, ...numberUtils }; return { Decimal, player, state, settings, layers, hasWon, pointGain, ...numberUtils };
}; };
export function coerceComponent( export function coerceComponent(
component: string | ComponentOptions | Component, component: string | ComponentOptions | Component,
@ -95,14 +100,41 @@ export function getFiltered<T>(
return objects; return objects;
} }
export function mapState(properties: Array<string> = []): Record<string, unknown> { type OmitIndex<T> = {
return properties.reduce((acc: Record<string, unknown>, curr: string): Record< [K in keyof T as unknown extends Record<K, 1> ? never : K]: T[K];
string, };
unknown
> => { export function mapPlayer<K extends keyof OmitIndex<PlayerData>>(
properties: K[] = []
): {
[P in K]: () => PlayerData[P];
} {
return properties.reduce((acc, curr: keyof PlayerData) => {
acc[curr] = () => player[curr]; acc[curr] = () => player[curr];
return acc; return acc;
}, {}); }, {} as any);
}
export function mapSettings<K extends keyof OmitIndex<Settings>>(
properties: K[] = []
): {
[P in K]: () => Settings[P];
} {
return properties.reduce((acc, curr: keyof Settings) => {
acc[curr] = () => settings[curr];
return acc;
}, {} as any);
}
export function mapState<K extends keyof OmitIndex<Transient>>(
properties: K[] = []
): {
[P in K]: () => Transient[P];
} {
return properties.reduce((acc, curr: keyof Transient) => {
acc[curr] = () => state[curr];
return acc;
}, {} as any);
} }
export const InjectLayerMixin = { export const InjectLayerMixin = {
@ -126,9 +158,9 @@ export function FilteredFeaturesMixin<T extends Feature>(
}; };
}; };
computed: { computed: {
filtered: () => Record<string, T> | undefined; filtered: (this: { layer: string; [feature]: string[] }) => Record<string, T> | undefined;
rows: () => number | undefined; rows: (this: { layer: string }) => number | undefined;
cols: () => number | undefined; cols: (this: { layer: string }) => number | undefined;
}; };
} { } {
return { return {
@ -139,19 +171,19 @@ export function FilteredFeaturesMixin<T extends Feature>(
} }
}, },
computed: { computed: {
filtered(this: { layer: string; [feature]: string[] }) { filtered() {
return ( return (
(layers[this.layer][feature] as Features<T> | undefined) && (layers[this.layer][feature] as Features<T> | undefined) &&
getFiltered((layers[this.layer][feature] as Features<T>).data, this[feature]) getFiltered((layers[this.layer][feature] as Features<T>).data, this[feature])
); );
}, },
rows(this: { layer: string }) { rows() {
return ( return (
(layers[this.layer][feature] as Features<T> | undefined) && (layers[this.layer][feature] as Features<T> | undefined) &&
(layers[this.layer][feature] as Features<T> | GridFeatures<T>).rows (layers[this.layer][feature] as Features<T> | GridFeatures<T>).rows
); );
}, },
cols(this: { layer: string }) { cols() {
return ( return (
(layers[this.layer][feature] as Features<T> | undefined) && (layers[this.layer][feature] as Features<T> | undefined) &&
(layers[this.layer][feature] as Features<T> | GridFeatures<T>).cols (layers[this.layer][feature] as Features<T> | GridFeatures<T>).cols