Fixing more issues

This commit is contained in:
thepaperpilot 2022-01-24 22:25:34 -06:00
parent 15a460bf42
commit 90e49e196f
60 changed files with 1380 additions and 781 deletions

View file

@ -18,7 +18,8 @@ module.exports = {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off", "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"vue/script-setup-uses-vars": "warn", "vue/script-setup-uses-vars": "warn",
"vue/no-mutating-props": "off" "vue/no-mutating-props": "off",
"vue/multi-word-component-names": "off"
}, },
globals: { globals: {
defineProps: "readonly", defineProps: "readonly",

38
package-lock.json generated
View file

@ -14,10 +14,10 @@
"vue": "^3.2.26", "vue": "^3.2.26",
"vue-next-select": "^2.10.2", "vue-next-select": "^2.10.2",
"vue-panzoom": "^1.1.6", "vue-panzoom": "^1.1.6",
"vue-sortable": "github:Netbel/vue-sortable#master-fix",
"vue-textarea-autosize": "^1.1.1", "vue-textarea-autosize": "^1.1.1",
"vue-toastification": "^2.0.0-rc.1", "vue-toastification": "^2.0.0-rc.1",
"vue-transition-expand": "^0.1.0" "vue-transition-expand": "^0.1.0",
"vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@ivanv/vue-collapse-transition": "^1.0.2", "@ivanv/vue-collapse-transition": "^1.0.2",
@ -11483,14 +11483,6 @@
"panzoom": "^9.4.1" "panzoom": "^9.4.1"
} }
}, },
"node_modules/vue-sortable": {
"version": "0.1.3",
"resolved": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7",
"license": "MIT",
"dependencies": {
"sortablejs": "^1.4.2"
}
},
"node_modules/vue-style-loader": { "node_modules/vue-style-loader": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@ -11549,6 +11541,17 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==" "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
}, },
"node_modules/vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"dependencies": {
"sortablejs": "1.14.0"
},
"peerDependencies": {
"vue": "^3.0.1"
}
},
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz",
@ -20849,13 +20852,6 @@
"panzoom": "^9.4.1" "panzoom": "^9.4.1"
} }
}, },
"vue-sortable": {
"version": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7",
"from": "vue-sortable@github:Netbel/vue-sortable#master-fix",
"requires": {
"sortablejs": "^1.4.2"
}
},
"vue-style-loader": { "vue-style-loader": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@ -20916,6 +20912,14 @@
} }
} }
}, },
"vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"requires": {
"sortablejs": "1.14.0"
}
},
"watchpack": { "watchpack": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz",

View file

@ -14,10 +14,10 @@
"vue": "^3.2.26", "vue": "^3.2.26",
"vue-next-select": "^2.10.2", "vue-next-select": "^2.10.2",
"vue-panzoom": "^1.1.6", "vue-panzoom": "^1.1.6",
"vue-sortable": "github:Netbel/vue-sortable#master-fix",
"vue-textarea-autosize": "^1.1.1", "vue-textarea-autosize": "^1.1.1",
"vue-toastification": "^2.0.0-rc.1", "vue-toastification": "^2.0.0-rc.1",
"vue-transition-expand": "^0.1.0" "vue-transition-expand": "^0.1.0",
"vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@ivanv/vue-collapse-transition": "^1.0.2", "@ivanv/vue-collapse-transition": "^1.0.2",

View file

@ -21,20 +21,46 @@
</Tooltip> </Tooltip>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import { GenericAchievement } from "@/features/achievement"; import { CoercableComponent, Visibility } from "@/features/feature";
import { FeatureComponent } from "@/features/feature";
import { coerceComponent } from "@/util/vue"; import { coerceComponent } from "@/util/vue";
import { computed, toRefs } from "vue"; import { computed, defineComponent, PropType, StyleValue, toRefs } from "vue";
import LinkNode from "../system/LinkNode.vue"; import LinkNode from "../system/LinkNode.vue";
import MarkNode from "./MarkNode.vue"; import MarkNode from "./MarkNode.vue";
import { Visibility } from "@/features/feature";
const props = toRefs(defineProps<FeatureComponent<GenericAchievement>>()); export default defineComponent({
props: {
visibility: {
type: Object as PropType<Visibility>,
required: true
},
display: [Object, String] as PropType<CoercableComponent>,
tooltip: [Object, String] as PropType<CoercableComponent>,
earned: {
type: Boolean,
required: true
},
image: String,
style: Object as PropType<StyleValue>,
classes: Object as PropType<Record<string, boolean>>,
mark: [Boolean, String],
id: {
type: String,
required: true
}
},
setup(props) {
const { display } = toRefs(props);
const component = computed(() => { return {
const display = props.display.value; component: computed(() => {
return display && coerceComponent(display); return display.value && coerceComponent(display.value);
}),
LinkNode,
MarkNode,
Visibility
};
}
}); });
</script> </script>

View file

@ -30,38 +30,74 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import { Direction, GenericBar } from "@/features/bar"; import { Direction } from "@/features/bar";
import { FeatureComponent, Visibility } from "@/features/feature"; import { CoercableComponent, Visibility } from "@/features/feature";
import Decimal from "@/util/bignum"; import Decimal, { DecimalSource } from "@/util/bignum";
import { coerceComponent } from "@/util/vue"; import { coerceComponent } from "@/util/vue";
import { computed, CSSProperties, toRefs, unref } from "vue"; import { computed, CSSProperties, defineComponent, PropType, StyleValue, toRefs, unref } from "vue";
import LinkNode from "../system/LinkNode.vue"; import LinkNode from "../system/LinkNode.vue";
import MarkNode from "./MarkNode.vue"; import MarkNode from "./MarkNode.vue";
const props = toRefs(defineProps<FeatureComponent<GenericBar>>()); export default defineComponent({
props: {
progress: {
type: Object as PropType<DecimalSource>,
required: true
},
width: {
type: Number,
required: true
},
height: {
type: Number,
required: true
},
direction: {
type: Object as PropType<Direction>,
required: true
},
display: [Object, String] as PropType<CoercableComponent>,
visibility: {
type: Object as PropType<Visibility>,
required: true
},
style: Object as PropType<StyleValue>,
classes: Object as PropType<Record<string, boolean>>,
borderStyle: Object as PropType<StyleValue>,
textStyle: Object as PropType<StyleValue>,
baseStyle: Object as PropType<StyleValue>,
fillStyle: Object as PropType<StyleValue>,
mark: [Boolean, String],
id: {
type: String,
required: true
}
},
setup(props) {
const { progress, width, height, direction, display } = toRefs(props);
const normalizedProgress = computed(() => { const normalizedProgress = computed(() => {
let progress = let progressNumber =
props.progress.value instanceof Decimal progress.value instanceof Decimal
? props.progress.value.toNumber() ? progress.value.toNumber()
: Number(props.progress.value); : Number(progress.value);
return (1 - Math.min(Math.max(progress, 0), 1)) * 100; return (1 - Math.min(Math.max(progressNumber, 0), 1)) * 100;
}); });
const barStyle = computed(() => { const barStyle = computed(() => {
const barStyle: Partial<CSSProperties> = { const barStyle: Partial<CSSProperties> = {
width: unref(props.width) + 0.5 + "px", width: unref(width) + 0.5 + "px",
height: unref(props.height) + 0.5 + "px" height: unref(height) + 0.5 + "px"
}; };
switch (unref(props.direction)) { switch (unref(direction)) {
case Direction.Up: case Direction.Up:
barStyle.clipPath = `inset(${normalizedProgress.value}% 0% 0% 0%)`; barStyle.clipPath = `inset(${normalizedProgress.value}% 0% 0% 0%)`;
barStyle.width = unref(props.width) + 1 + "px"; barStyle.width = unref(width) + 1 + "px";
break; break;
case Direction.Down: case Direction.Down:
barStyle.clipPath = `inset(0% 0% ${normalizedProgress.value}% 0%)`; barStyle.clipPath = `inset(0% 0% ${normalizedProgress.value}% 0%)`;
barStyle.width = unref(props.width) + 1 + "px"; barStyle.width = unref(width) + 1 + "px";
break; break;
case Direction.Right: case Direction.Right:
barStyle.clipPath = `inset(0% ${normalizedProgress.value}% 0% 0%)`; barStyle.clipPath = `inset(0% ${normalizedProgress.value}% 0% 0%)`;
@ -74,11 +110,22 @@ const barStyle = computed(() => {
break; break;
} }
return barStyle; return barStyle;
}); });
const component = computed(() => { const component = computed(() => {
const display = props.display.value; const currDisplay = unref(display);
return display && coerceComponent(display); return currDisplay && coerceComponent(unref(currDisplay));
});
return {
normalizedProgress,
barStyle,
component,
MarkNode,
LinkNode,
Visibility
};
}
}); });
</script> </script>

View file

@ -18,61 +18,113 @@
{{ buttonText }} {{ buttonText }}
</button> </button>
<component v-if="component" :is="component" /> <component v-if="component" :is="component" />
<default-challenge-display v-else :id="id" />
<MarkNode :mark="mark" /> <MarkNode :mark="mark" />
<LinkNode :id="id" /> <LinkNode :id="id" />
</div> </div>
</template> </template>
<script setup lang="tsx"> <script lang="tsx">
import { GenericChallenge } from "@/features/challenge"; import { GenericChallenge } from "@/features/challenge";
import { FeatureComponent, Visibility } from "@/features/feature"; import { StyleValue, Visibility } from "@/features/feature";
import { coerceComponent, isCoercableComponent } from "@/util/vue"; import { coerceComponent, isCoercableComponent } from "@/util/vue";
import { computed, toRefs } from "vue"; import { computed, defineComponent, PropType, toRefs, UnwrapRef } from "vue";
import LinkNode from "../system/LinkNode.vue";
import MarkNode from "./MarkNode.vue";
const props = toRefs(defineProps<FeatureComponent<GenericChallenge>>()); export default defineComponent({
props: {
const buttonText = computed(() => { active: {
if (props.active.value) { type: Boolean,
return props.canComplete.value ? "Finish" : "Exit Early"; required: true
},
maxed: {
type: Boolean,
required: true
},
canComplete: {
type: Boolean,
required: true
},
display: Object as PropType<UnwrapRef<GenericChallenge["display"]>>,
visibility: {
type: Object as PropType<Visibility>,
required: true
},
style: Object as PropType<StyleValue>,
classes: Object as PropType<Record<string, boolean>>,
completed: {
type: Boolean,
required: true
},
canStart: {
type: Boolean,
required: true
},
mark: [Boolean, String],
id: {
type: String,
required: true
},
toggle: {
type: Function as PropType<VoidFunction>,
required: true
} }
if (props.maxed.value) { },
setup(props) {
const { active, maxed, canComplete, display } = toRefs(props);
const buttonText = computed(() => {
if (active.value) {
return canComplete.value ? "Finish" : "Exit Early";
}
if (maxed.value) {
return "Completed"; return "Completed";
} }
return "Start"; return "Start";
}); });
const component = computed(() => { const component = computed(() => {
const display = props.display.value; const currDisplay = display.value;
if (display == null) { if (currDisplay == null) {
return null; return null;
} }
if (isCoercableComponent(display)) { if (isCoercableComponent(currDisplay)) {
return coerceComponent(display); return coerceComponent(currDisplay);
} }
return ( return (
<span> <span>
<template v-if={display.title}> <template v-if={currDisplay.title}>
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
<component v-is={coerceComponent(display.title!, "h3")} /> <component v-is={coerceComponent(currDisplay.title!, "h3")} />
</template> </template>
<component v-is={coerceComponent(display.description, "div")} /> <component v-is={coerceComponent(currDisplay.description, "div")} />
<div v-if={display.goal}> <div v-if={currDisplay.goal}>
<br /> <br />
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
Goal: <component v-is={coerceComponent(display.goal!)} /> Goal: <component v-is={coerceComponent(currDisplay.goal!)} />
</div> </div>
<div v-if={display.reward}> <div v-if={currDisplay.reward}>
<br /> <br />
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
Reward: <component v-is={coerceComponent(display.reward!)} /> Reward: <component v-is={coerceComponent(currDisplay.reward!)} />
</div> </div>
<div v-if={display.effectDisplay}> <div v-if={currDisplay.effectDisplay}>
Currently: {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} Currently:{" "}
<component v-is={coerceComponent(display.effectDisplay!)} /> {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
<component v-is={coerceComponent(currDisplay.effectDisplay!)} />
</div> </div>
</span> </span>
); );
});
return {
buttonText,
component,
MarkNode,
LinkNode,
Visibility
};
}
}); });
</script> </script>

View file

@ -13,7 +13,7 @@
:class="{ :class="{
feature: true, feature: true,
clickable: true, clickable: true,
can: props.canClick, can: canClick,
locked: !canClick, locked: !canClick,
small, small,
...classes ...classes
@ -26,36 +26,73 @@
</div> </div>
</template> </template>
<script setup lang="tsx"> <script lang="tsx">
import { GenericClickable } from "@/features/clickable"; import { GenericClickable } from "@/features/clickable";
import { FeatureComponent, Visibility } from "@/features/feature"; import { StyleValue, Visibility } from "@/features/feature";
import { coerceComponent, isCoercableComponent, setupHoldToClick } from "@/util/vue"; import { coerceComponent, isCoercableComponent, setupHoldToClick } from "@/util/vue";
import { computed, toRefs, unref } from "vue"; import { computed, defineComponent, PropType, toRefs, unref, UnwrapRef } from "vue";
import LinkNode from "../system/LinkNode.vue"; import LinkNode from "../system/LinkNode.vue";
import MarkNode from "./MarkNode.vue"; import MarkNode from "./MarkNode.vue";
const props = toRefs(defineProps<FeatureComponent<GenericClickable>>()); export default defineComponent({
props: {
display: {
type: Object as PropType<UnwrapRef<GenericClickable["display"]>>,
required: true
},
visibility: {
type: Object as PropType<Visibility>,
required: true
},
style: Object as PropType<StyleValue>,
classes: Object as PropType<Record<string, boolean>>,
onClick: Function as PropType<VoidFunction>,
onHold: Function as PropType<VoidFunction>,
canClick: {
type: Boolean,
required: true
},
small: Boolean,
mark: [Boolean, String],
id: {
type: String,
required: true
}
},
setup(props) {
const { display, onClick, onHold } = toRefs(props);
const component = computed(() => { const component = computed(() => {
const display = unref(props.display); const currDisplay = unref(display);
if (display == null) { if (currDisplay == null) {
return null; return null;
} }
if (isCoercableComponent(display)) { if (isCoercableComponent(currDisplay)) {
return coerceComponent(display); return coerceComponent(currDisplay);
} }
return ( return (
<span> <span>
<div v-if={display.title}> <div v-if={currDisplay.title}>
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
<component v-is={coerceComponent(display.title!, "h2")} /> <component v-is={coerceComponent(currDisplay.title!, "h2")} />
</div> </div>
<component v-is={coerceComponent(display.description, "div")} /> <component v-is={coerceComponent(currDisplay.description, "div")} />
</span> </span>
); );
}); });
const { start, stop } = setupHoldToClick(props.onClick, props.onHold); const { start, stop } = setupHoldToClick(onClick, onHold);
return {
start,
stop,
component,
LinkNode,
MarkNode,
Visibility
};
}
});
</script> </script>
<style scoped> <style scoped>

View file

@ -13,13 +13,32 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { FeatureComponent, Visibility } from "@/features/feature"; import { Visibility } from "@/features/feature";
import { GenericGrid } from "@/features/grid"; import { GridCell } from "@/features/grid";
import { defineComponent } from "vue"; import { defineComponent, PropType } from "vue";
import GridCell from "./GridCell.vue"; import GridCellVue from "./GridCell.vue";
// https://github.com/thepaperpilot/The-Modding-Tree-X/issues/1 export default defineComponent({
export default defineComponent(function Grid(props: FeatureComponent<GenericGrid>) { props: {
return { ...props, GridCell, Visibility }; visibility: {
type: Object as PropType<Visibility>,
required: true
},
rows: {
type: Number,
required: true
},
cols: {
type: Number,
required: true
},
cells: {
type: Object as PropType<Record<string, GridCell>>,
required: true
}
},
setup() {
return { GridCell: GridCellVue, Visibility };
}
}); });
</script> </script>

View file

@ -19,22 +19,56 @@
</button> </button>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import { Visibility } from "@/features/feature"; import { CoercableComponent, StyleValue, Visibility } from "@/features/feature";
import { GridCell } from "@/features/grid";
import { coerceComponent, setupHoldToClick } from "@/util/vue"; import { coerceComponent, setupHoldToClick } from "@/util/vue";
import { computed, toRefs, unref } from "vue"; import { computed, defineComponent, PropType, toRefs, unref } from "vue";
import LinkNode from "../system/LinkNode.vue"; import LinkNode from "../system/LinkNode.vue";
const props = toRefs(defineProps<GridCell>()); export default defineComponent({
props: {
visibility: {
type: Object as PropType<Visibility>,
required: true
},
onClick: Function as PropType<VoidFunction>,
onHold: Function as PropType<VoidFunction>,
display: {
type: [Object, String] as PropType<CoercableComponent>,
required: true
},
title: [Object, String] as PropType<CoercableComponent>,
style: Object as PropType<StyleValue>,
canClick: {
type: Boolean,
required: true
},
id: {
type: String,
required: true
}
},
setup(props) {
const { onClick, onHold, title, display } = toRefs(props);
const { start, stop } = setupHoldToClick(props.onClick, props.onHold); const { start, stop } = setupHoldToClick(onClick, onHold);
const titleComponent = computed(() => { const titleComponent = computed(() => {
const title = unref(props.title); const currTitle = unref(title);
return title && coerceComponent(title); return currTitle && coerceComponent(currTitle);
});
const component = computed(() => coerceComponent(unref(display)));
return {
start,
stop,
titleComponent,
component,
Visibility,
LinkNode
};
}
}); });
const component = computed(() => coerceComponent(unref(props.display)));
</script> </script>
<style scoped> <style scoped>

View file

@ -23,21 +23,57 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import themes from "@/data/themes"; import themes from "@/data/themes";
import { FeatureComponent, Visibility } from "@/features/feature"; import { CoercableComponent, Visibility } from "@/features/feature";
import { GenericInfobox } from "@/features/infobox";
import settings from "@/game/settings"; import settings from "@/game/settings";
import { coerceComponent } from "@/util/vue"; import { coerceComponent } from "@/util/vue";
import { computed, toRefs, unref } from "vue";
import LinkNode from "../system/LinkNode.vue";
import CollapseTransition from "@ivanv/vue-collapse-transition/src/CollapseTransition.vue"; import CollapseTransition from "@ivanv/vue-collapse-transition/src/CollapseTransition.vue";
import { computed, defineComponent, PropType, StyleValue, toRefs } from "vue";
import LinkNode from "../system/LinkNode.vue";
const props = toRefs(defineProps<FeatureComponent<GenericInfobox>>()); export default defineComponent({
props: {
visibility: {
type: Object as PropType<Visibility>,
required: true
},
display: {
type: [Object, String] as PropType<CoercableComponent>,
required: true
},
title: [Object, String] as PropType<CoercableComponent>,
color: String,
collapsed: {
type: Boolean,
required: true
},
style: Object as PropType<StyleValue>,
titleStyle: Object as PropType<StyleValue>,
bodyStyle: Object as PropType<StyleValue>,
classes: Object as PropType<Record<string, boolean>>,
id: {
type: String,
required: true
}
},
setup(props) {
const { title, display } = toRefs(props);
const titleComponent = computed(() => coerceComponent(unref(props.title))); const titleComponent = computed(() => title.value && coerceComponent(title.value));
const bodyComponent = computed(() => coerceComponent(unref(props.display))); const bodyComponent = computed(() => coerceComponent(display.value));
const stacked = computed(() => themes[settings.theme].stackedInfoboxes); const stacked = computed(() => themes[settings.theme].stackedInfoboxes);
return {
titleComponent,
bodyComponent,
stacked,
LinkNode,
CollapseTransition,
Visibility
};
}
});
</script> </script>
<style scoped> <style scoped>

View file

@ -17,15 +17,14 @@ import { coerceComponent } from "@/util/vue";
import { computed, StyleValue, toRefs } from "vue"; import { computed, StyleValue, toRefs } from "vue";
import ResourceVue from "../system/Resource.vue"; import ResourceVue from "../system/Resource.vue";
const props = toRefs( const _props = defineProps<{
defineProps<{
resource: Resource; resource: Resource;
color?: string; color?: string;
classes?: Record<string, boolean>; classes?: Record<string, boolean>;
style?: StyleValue; style?: StyleValue;
effectDisplay?: CoercableComponent; effectDisplay?: CoercableComponent;
}>() }>();
); const props = toRefs(_props);
const effectComponent = computed(() => { const effectComponent = computed(() => {
const effectDisplay = props.effectDisplay?.value; const effectDisplay = props.effectDisplay?.value;

View file

@ -10,36 +10,66 @@
</div> </div>
</template> </template>
<script setup lang="tsx"> <script lang="tsx">
import { FeatureComponent, Visibility } from "@/features/feature"; import { StyleValue, Visibility } from "@/features/feature";
import { GenericMilestone } from "@/features/milestone"; import { GenericMilestone } from "@/features/milestone";
import { coerceComponent, isCoercableComponent } from "@/util/vue"; import { coerceComponent, isCoercableComponent } from "@/util/vue";
import { computed, toRefs } from "vue"; import { computed, defineComponent, PropType, toRefs, UnwrapRef } from "vue";
import LinkNode from "../system/LinkNode.vue"; import LinkNode from "../system/LinkNode.vue";
const props = toRefs(defineProps<FeatureComponent<GenericMilestone>>()); export default defineComponent({
props: {
visibility: {
type: Object as PropType<Visibility>,
required: true
},
display: {
type: Object as PropType<UnwrapRef<GenericMilestone["display"]>>,
required: true
},
style: Object as PropType<StyleValue>,
classes: Object as PropType<Record<string, boolean>>,
earned: {
type: Boolean,
required: true
},
id: {
type: String,
required: true
}
},
setup(props) {
const { display } = toRefs(props);
const component = computed(() => { const component = computed(() => {
const display = props.display.value; const currDisplay = display.value;
if (display == null) { if (currDisplay == null) {
return null; return null;
} }
if (isCoercableComponent(display)) { if (isCoercableComponent(currDisplay)) {
return coerceComponent(display); return coerceComponent(currDisplay);
} }
return ( return (
<span> <span>
<component v-is={coerceComponent(display.requirement, "h3")} /> <component v-is={coerceComponent(currDisplay.requirement, "h3")} />
<div v-if={display.effectDisplay}> <div v-if={currDisplay.effectDisplay}>
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
<component v-is={coerceComponent(display.effectDisplay!, "b")} /> <component v-is={coerceComponent(currDisplay.effectDisplay!, "b")} />
</div> </div>
<div v-if={display.optionsDisplay}> <div v-if={currDisplay.optionsDisplay}>
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
<component v-is={coerceComponent(display.optionsDisplay!, "span")} /> <component v-is={coerceComponent(currDisplay.optionsDisplay!, "span")} />
</div> </div>
</span> </span>
); );
});
return {
component,
LinkNode,
Visibility
};
}
}); });
</script> </script>

View file

@ -7,6 +7,7 @@ import { CoercableComponent } from "@/features/feature";
import { coerceComponent } from "@/util/vue"; import { coerceComponent } from "@/util/vue";
import { computed, toRefs } from "vue"; import { computed, toRefs } from "vue";
const { display } = toRefs(defineProps<{ display: CoercableComponent }>()); const _props = defineProps<{ display: CoercableComponent }>();
const { display } = toRefs(_props);
const component = computed(() => coerceComponent(display)); const component = computed(() => coerceComponent(display));
</script> </script>

View file

@ -1,30 +1,50 @@
<template> <template>
<button <button
@click="emits('selectTab')" @click="selectTab"
class="tabButton" class="tabButton"
:style="style" :style="unref(style)"
:class="{ :class="{
active, active,
...classes ...unref(classes)
}" }"
> >
<component :is="component" /> <component :is="component" />
</button> </button>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import { FeatureComponent } from "@/features/feature"; import { CoercableComponent, StyleValue } from "@/features/feature";
import { GenericTabButton } from "@/features/tabFamily"; import { ProcessedComputable } from "@/util/computed";
import { coerceComponent } from "@/util/vue"; import { computeComponent } from "@/util/vue";
import { computed, toRefs } from "vue"; import { defineComponent, PropType, toRefs, unref } from "vue";
const props = toRefs(defineProps<FeatureComponent<GenericTabButton> & { active: boolean }>()); export default defineComponent({
props: {
display: {
type: [Object, String] as PropType<ProcessedComputable<CoercableComponent>>,
required: true
},
style: Object as PropType<ProcessedComputable<StyleValue>>,
classes: Object as PropType<ProcessedComputable<Record<string, boolean>>>,
active: [Object, Boolean] as PropType<ProcessedComputable<boolean>>
},
emits: ["selectTab"],
setup(props, { emit }) {
const { display } = toRefs(props);
const emits = defineEmits<{ const component = computeComponent(display);
(e: "selectTab"): void;
}>();
const component = computed(() => coerceComponent(props.display.value)); function selectTab() {
emit("selectTab");
}
return {
selectTab,
component,
unref
};
}
});
</script> </script>
<style scoped> <style scoped>

View file

@ -4,7 +4,7 @@
<div class="tab-buttons" :class="{ floating }"> <div class="tab-buttons" :class="{ floating }">
<TabButton <TabButton
v-for="(button, id) in tabs" v-for="(button, id) in tabs"
@selectTab="selectTab(id)" @selectTab="selected = id"
:key="id" :key="id"
:active="button.tab === activeTab" :active="button.tab === activeTab"
v-bind="button" v-bind="button"
@ -12,48 +12,81 @@
</div> </div>
</Sticky> </Sticky>
<template v-if="activeTab"> <template v-if="activeTab">
<component :is="display" /> <component :is="display!" />
</template> </template>
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import themes from "@/data/themes"; import themes from "@/data/themes";
import { FeatureComponent, PersistentState } from "@/features/feature"; import { CoercableComponent } from "@/features/feature";
import { GenericTabFamily } from "@/features/tabFamily"; import { GenericTab } from "@/features/tab";
import { GenericTabButton } from "@/features/tabFamily";
import settings from "@/game/settings"; import settings from "@/game/settings";
import { coerceComponent, isCoercableComponent } from "@/util/vue"; import { coerceComponent, isCoercableComponent } from "@/util/vue";
import { computed, toRefs, unref } from "vue"; import { computed, defineComponent, PropType, toRefs, unref } from "vue";
import Sticky from "../system/Sticky.vue"; import Sticky from "../system/Sticky.vue";
import TabButton from "./TabButton.vue";
const props = toRefs(defineProps<FeatureComponent<GenericTabFamily>>()); export default defineComponent({
props: {
activeTab: {
type: Object as PropType<GenericTab | CoercableComponent | null>,
required: true
},
selected: {
type: String,
required: true
},
tabs: {
type: Object as PropType<Record<string, GenericTabButton>>,
required: true
}
},
setup(props) {
const { activeTab } = toRefs(props);
const floating = computed(() => { const floating = computed(() => {
return themes[settings.theme].floatingTabs; return themes[settings.theme].floatingTabs;
}); });
const display = computed(() => { const display = computed(() => {
const activeTab = props.activeTab.value; const currActiveTab = activeTab.value;
return activeTab return currActiveTab
? coerceComponent(isCoercableComponent(activeTab) ? activeTab : activeTab.display) ? coerceComponent(
isCoercableComponent(currActiveTab)
? currActiveTab
: unref(currActiveTab.display)
)
: null; : null;
}); });
const classes = computed(() => { const classes = computed(() => {
const activeTab = props.activeTab.value; const currActiveTab = activeTab.value;
const tabClasses = const tabClasses =
isCoercableComponent(activeTab) || !activeTab ? undefined : unref(activeTab.classes); isCoercableComponent(currActiveTab) || !currActiveTab
? undefined
: unref(currActiveTab.classes);
return tabClasses; return tabClasses;
}); });
const style = computed(() => { const style = computed(() => {
const activeTab = props.activeTab.value; const currActiveTab = activeTab.value;
return isCoercableComponent(activeTab) || !activeTab ? undefined : unref(activeTab.style); return isCoercableComponent(currActiveTab) || !currActiveTab
}); ? undefined
: unref(currActiveTab.style);
});
function selectTab(tab: string) { return {
props[PersistentState].value = tab; floating,
} display,
classes,
style,
Sticky,
TabButton
};
}
});
</script> </script>
<style scoped> <style scoped>

View file

@ -20,46 +20,96 @@
</button> </button>
</template> </template>
<script setup lang="tsx"> <script lang="tsx">
import { FeatureComponent, Visibility } from "@/features/feature"; import { StyleValue, Visibility } from "@/features/feature";
import { displayResource } from "@/features/resource"; import { displayResource, Resource } from "@/features/resource";
import { GenericUpgrade } from "@/features/upgrade"; import { GenericUpgrade } from "@/features/upgrade";
import { DecimalSource } from "@/lib/break_eternity";
import { coerceComponent, isCoercableComponent } from "@/util/vue"; import { coerceComponent, isCoercableComponent } from "@/util/vue";
import { computed, toRefs, unref } from "vue"; import { computed, defineComponent, PropType, Ref, toRef, toRefs, unref, UnwrapRef } from "vue";
import LinkNode from "../system/LinkNode.vue"; import LinkNode from "../system/LinkNode.vue";
import MarkNode from "./MarkNode.vue"; import MarkNode from "./MarkNode.vue";
const props = toRefs(defineProps<FeatureComponent<GenericUpgrade>>()); export default defineComponent({
props: {
display: {
type: Object as PropType<UnwrapRef<GenericUpgrade["display"]>>,
required: true
},
visibility: {
type: Object as PropType<Visibility>,
required: true
},
style: Object as PropType<StyleValue>,
classes: Object as PropType<Record<string, boolean>>,
resource: {
type: Object as PropType<Resource>,
required: true
},
cost: {
type: Object as PropType<DecimalSource>,
required: true
},
canPurchase: {
type: Boolean,
required: true
},
bought: {
type: Boolean,
required: true
},
mark: [Boolean, String],
id: {
type: String,
required: true
},
purchase: {
type: Function as PropType<VoidFunction>,
required: true
}
},
setup(props) {
const { display, cost } = toRefs(props);
const resource = toRef(props, "resource") as unknown as Ref<Resource>;
const component = computed(() => { const component = computed(() => {
const display = unref(props.display); const currDisplay = display.value;
if (display == null) { if (currDisplay == null) {
return null; return null;
} }
if (isCoercableComponent(display)) { if (isCoercableComponent(currDisplay)) {
return coerceComponent(display); return coerceComponent(currDisplay);
} }
return ( return (
<span> <span>
<div v-if={display.title}> <div v-if={currDisplay.title}>
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
<component v-is={coerceComponent(display.title!, "h2")} /> <component v-is={coerceComponent(currDisplay.title!, "h2")} />
</div> </div>
<component v-is={coerceComponent(display.description, "div")} /> <component v-is={coerceComponent(currDisplay.description, "div")} />
<div v-if={display.effectDisplay}> <div v-if={currDisplay.effectDisplay}>
<br /> <br />
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
Currently: <component v-is={coerceComponent(display.effectDisplay!)} /> Currently: <component v-is={coerceComponent(currDisplay.effectDisplay!)} />
</div> </div>
<template v-if={unref(props.resource) != null && unref(props.cost) != null}> <template v-if={resource.value != null && cost.value != null}>
<br /> <br />
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
Cost: {displayResource(unref(props.resource)!, unref(props.cost))}{" "} Cost: {displayResource(resource.value, cost.value)}{" "}
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
{unref(props.resource)!.displayName} {resource.value.displayName}
</template> </template>
</span> </span>
); );
});
return {
component,
LinkNode,
MarkNode,
Visibility
};
}
}); });
</script> </script>

View file

@ -58,7 +58,8 @@ import panZoom from "vue-panzoom";
import BoardLinkVue from "./BoardLink.vue"; import BoardLinkVue from "./BoardLink.vue";
import BoardNodeVue from "./BoardNode.vue"; import BoardNodeVue from "./BoardNode.vue";
const props = toRefs(defineProps<FeatureComponent<GenericBoard>>()); const _props = defineProps<FeatureComponent<GenericBoard>>();
const props = toRefs(_props);
const lastMousePosition = ref({ x: 0, y: 0 }); const lastMousePosition = ref({ x: 0, y: 0 });
const dragged = ref({ x: 0, y: 0 }); const dragged = ref({ x: 0, y: 0 });

View file

@ -14,11 +14,10 @@
import { BoardNodeLink } from "@/features/board"; import { BoardNodeLink } from "@/features/board";
import { computed, toRefs, unref } from "vue"; import { computed, toRefs, unref } from "vue";
const props = toRefs( const _props = defineProps<{
defineProps<{
link: BoardNodeLink; link: BoardNodeLink;
}>() }>();
); const props = toRefs(_props);
const startPosition = computed(() => { const startPosition = computed(() => {
const position = props.link.value.startNode.position; const position = props.link.value.startNode.position;

View file

@ -182,8 +182,7 @@ import { computed, ref, toRefs, unref, watch } from "vue";
const sqrtTwo = Math.sqrt(2); const sqrtTwo = Math.sqrt(2);
const props = toRefs( const _props = defineProps<{
defineProps<{
node: BoardNode; node: BoardNode;
nodeType: GenericNodeType; nodeType: GenericNodeType;
dragging?: BoardNode; dragging?: BoardNode;
@ -195,8 +194,8 @@ const props = toRefs(
receivingNode?: boolean; receivingNode?: boolean;
selectedNode?: BoardNode | null; selectedNode?: BoardNode | null;
selectedAction?: GenericBoardNodeAction | null; selectedAction?: GenericBoardNodeAction | null;
}>() }>();
); const props = toRefs(_props);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "mouseDown", event: MouseEvent | TouchEvent, node: number, isDraggable: boolean): void; (e: "mouseDown", event: MouseEvent | TouchEvent, node: number, isDraggable: boolean): void;
(e: "endDragging", node: number): void; (e: "endDragging", node: number): void;

View file

@ -1,12 +1,18 @@
<template> <template>
<span class="row" v-for="(row, index) in nodes" :key="index"> <span class="row" v-for="(row, index) in nodes" :key="index">
<TreeNode v-for="(node, nodeIndex) in row" :key="nodeIndex" v-bind="wrapFeature(node)" /> <TreeNode
v-for="(node, nodeIndex) in row"
:key="nodeIndex"
v-bind="node"
:force-tooltip="node.forceTooltip"
/>
</span> </span>
<span class="left-side-nodes" v-if="leftSideNodes"> <span class="left-side-nodes" v-if="leftSideNodes">
<TreeNode <TreeNode
v-for="(node, nodeIndex) in leftSideNodes" v-for="(node, nodeIndex) in leftSideNodes"
:key="nodeIndex" :key="nodeIndex"
v-bind="wrapFeature(node)" v-bind="node"
:force-tooltip="node.forceTooltip"
small small
/> />
</span> </span>
@ -14,21 +20,30 @@
<TreeNode <TreeNode
v-for="(node, nodeIndex) in rightSideNodes" v-for="(node, nodeIndex) in rightSideNodes"
:key="nodeIndex" :key="nodeIndex"
v-bind="wrapFeature(node)" v-bind="node"
:force-tooltip="node.forceTooltip"
small small
/> />
</span> </span>
</template> </template>
<script lang="ts"> <script lang="ts">
import { FeatureComponent, wrapFeature } from "@/features/feature"; import { GenericTreeNode } from "@/features/tree";
import { GenericTree } from "@/features/tree"; import { defineComponent, PropType } from "vue";
import { defineComponent } from "vue";
import TreeNode from "./TreeNode.vue"; import TreeNode from "./TreeNode.vue";
// https://github.com/thepaperpilot/The-Modding-Tree-X/issues/1 export default defineComponent({
export default defineComponent(function Grid(props: FeatureComponent<GenericTree>) { props: {
return { ...props, TreeNode, wrapFeature }; nodes: {
type: Array as PropType<GenericTreeNode[][]>,
required: true
},
leftSideNodes: Array as PropType<GenericTreeNode[]>,
rightSideNodes: Array as PropType<GenericTreeNode[]>
},
setup() {
return { TreeNode };
}
}); });
</script> </script>

View file

@ -1,25 +1,15 @@
<template> <template>
<Tooltip <Tooltip
v-if="visibility !== Visibility.None" v-if="unref(visibility) !== Visibility.None"
v-show="visibility === Visibility.Visible" v-show="unref(visibility) === Visibility.Visible"
v-bind=" v-bind="tooltipToBind"
typeof tooltip === 'object' && !isCoercableComponent(tooltip) :display="tooltipDisplay"
? wrapFeature(tooltip)
: null
"
:display="
typeof tooltip === 'object'
? isCoercableComponent(tooltip)
? unref(tooltip)
: tooltip.display
: tooltip || ''
"
:force="forceTooltip" :force="forceTooltip"
:class="{ :class="{
treeNode: true, treeNode: true,
can: canClick, can: unref(canClick),
small, small: unref(small),
...classes ...unref(classes)
}" }"
> >
<button <button
@ -32,51 +22,113 @@
@touchcancel="stop" @touchcancel="stop"
:style="[ :style="[
{ {
backgroundColor: color, backgroundColor: unref(color),
boxShadow: `-4px -4px 4px rgba(0, 0, 0, 0.25) inset, 0 0 20px ${glowColor}` boxShadow: `-4px -4px 4px rgba(0, 0, 0, 0.25) inset, 0 0 20px ${unref(
glowColor
)}`
}, },
style ?? [] unref(style) ?? []
]" ]"
:disabled="!canClick" :disabled="!unref(canClick)"
> >
<component :is="component" /> <component :is="component" />
</button> </button>
<MarkNode :mark="mark" /> <MarkNode :mark="unref(mark)" />
<LinkNode :id="id" /> <LinkNode :id="unref(id)" />
</Tooltip> </Tooltip>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import { GenericTreeNode } from "@/features/tree"; import TooltipVue from "@/components/system/Tooltip.vue";
import { coerceComponent, isCoercableComponent, setupHoldToClick } from "@/util/vue"; import { CoercableComponent, StyleValue, Visibility } from "@/features/feature";
import { computed, toRefs, unref } from "vue"; import { Tooltip } from "@/features/tooltip";
import Tooltip from "@/components/system/Tooltip.vue"; import { ProcessedComputable } from "@/util/computed";
import MarkNode from "../MarkNode.vue"; import {
import { FeatureComponent, Visibility, wrapFeature } from "@/features/feature"; computeOptionalComponent,
isCoercableComponent,
setupHoldToClick,
unwrapRef
} from "@/util/vue";
import { computed, defineComponent, PropType, Ref, toRefs, unref } from "vue";
import LinkNode from "../../system/LinkNode.vue"; import LinkNode from "../../system/LinkNode.vue";
import MarkNode from "../MarkNode.vue";
const props = toRefs( export default defineComponent({
defineProps< props: {
FeatureComponent<GenericTreeNode> & { display: [Object, String] as PropType<ProcessedComputable<CoercableComponent>>,
small?: boolean; visibility: {
} type: Object as PropType<ProcessedComputable<Visibility>>,
>() required: true
); },
style: Object as PropType<ProcessedComputable<StyleValue>>,
classes: Object as PropType<ProcessedComputable<Record<string, boolean>>>,
tooltip: Object as PropType<ProcessedComputable<CoercableComponent | Tooltip>>,
onClick: Function as PropType<VoidFunction>,
onHold: Function as PropType<VoidFunction>,
color: [Object, String] as PropType<ProcessedComputable<string>>,
glowColor: [Object, String] as PropType<ProcessedComputable<string>>,
forceTooltip: {
type: Object as PropType<Ref<boolean>>,
required: true
},
canClick: {
type: [Object, Boolean] as PropType<ProcessedComputable<boolean>>,
required: true
},
mark: [Object, Boolean, String] as PropType<ProcessedComputable<boolean | string>>,
id: {
type: [Object, String] as PropType<ProcessedComputable<string>>,
required: true
},
small: [Object, Boolean] as PropType<ProcessedComputable<boolean>>
},
setup(props) {
const { tooltip, forceTooltip, onClick, onHold, display } = toRefs(props);
function click(e: MouseEvent) { function click(e: MouseEvent) {
if (e.shiftKey && props.tooltip) { if (e.shiftKey && tooltip) {
props.forceTooltip.value = !props.forceTooltip.value; forceTooltip.value = !forceTooltip.value;
} else { } else {
unref(props.onClick)?.(); unref(onClick)?.();
}
} }
}
const component = computed(() => { const component = computeOptionalComponent(display);
const display = unref(props.display); const tooltipDisplay = computed(() => {
return display && coerceComponent(display); const currTooltip = unwrapRef(tooltip);
if (typeof currTooltip === "object" && !isCoercableComponent(currTooltip)) {
return currTooltip.display;
}
return currTooltip || "";
});
const tooltipToBind = computed(() => {
const currTooltip = unwrapRef(tooltip);
if (typeof currTooltip === "object" && !isCoercableComponent(currTooltip)) {
return currTooltip;
}
return null;
});
const { start, stop } = setupHoldToClick(onClick, onHold);
return {
click,
start,
stop,
component,
tooltipDisplay,
tooltipToBind,
Tooltip: TooltipVue,
MarkNode,
LinkNode,
unref,
Visibility,
isCoercableComponent
};
}
}); });
const { start, stop } = setupHoldToClick(props.onClick, props.onHold);
</script> </script>
<style scoped> <style scoped>

View file

@ -12,12 +12,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, toRefs, unref, watch } from "vue"; import { ref, toRefs, unref, watch } from "vue";
const props = toRefs( const _props = defineProps<{
defineProps<{
disabled?: boolean; disabled?: boolean;
skipConfirm?: boolean; skipConfirm?: boolean;
}>() }>();
); const props = toRefs(_props);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "click"): void; (e: "click"): void;
(e: "confirmingChanged", value: boolean): void; (e: "confirmingChanged", value: boolean): void;
@ -63,7 +62,8 @@ function cancel() {
</style> </style>
<style> <style>
.danger { .danger,
.button.danger {
position: relative; position: relative;
border: solid 2px var(--danger); border: solid 2px var(--danger);
border-right-width: 16px; border-right-width: 16px;

View file

@ -5,13 +5,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, ref, toRefs } from "vue"; import { nextTick, ref } from "vue";
toRefs( defineProps<{
defineProps<{
left?: boolean; left?: boolean;
}>() }>();
);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "click"): void; (e: "click"): void;
}>(); }>();

View file

@ -21,15 +21,14 @@ import "vue-next-select/dist/index.css";
export type SelectOption = { label: string; value: unknown }; export type SelectOption = { label: string; value: unknown };
const props = toRefs( const _props = defineProps<{
defineProps<{
title?: CoercableComponent; title?: CoercableComponent;
modelValue?: unknown; modelValue?: unknown;
options: SelectOption[]; options: SelectOption[];
placeholder?: string; placeholder?: string;
closeOnSelect?: boolean; closeOnSelect?: boolean;
}>() }>();
); const props = toRefs(_props);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:modelValue", value: unknown): void; (e: "update:modelValue", value: unknown): void;
}>(); }>();

View file

@ -11,14 +11,13 @@
import { computed, toRefs, unref } from "vue"; import { computed, toRefs, unref } from "vue";
import Tooltip from "../system/Tooltip.vue"; import Tooltip from "../system/Tooltip.vue";
const props = toRefs( const _props = defineProps<{
defineProps<{
title?: string; title?: string;
modelValue?: number; modelValue?: number;
min?: number; min?: number;
max?: number; max?: number;
}>() }>();
); const props = toRefs(_props);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:modelValue", value: number): void; (e: "update:modelValue", value: number): void;
}>(); }>();

View file

@ -31,15 +31,14 @@ import { coerceComponent } from "@/util/vue";
import { computed, onMounted, ref, toRefs, unref } from "vue"; import { computed, onMounted, ref, toRefs, unref } from "vue";
import VueTextareaAutosize from "vue-textarea-autosize"; import VueTextareaAutosize from "vue-textarea-autosize";
const props = toRefs( const _props = defineProps<{
defineProps<{
title?: CoercableComponent; title?: CoercableComponent;
modelValue?: string; modelValue?: string;
textArea?: boolean; textArea?: boolean;
placeholder?: string; placeholder?: string;
maxHeight?: number; maxHeight?: number;
}>() }>();
); const props = toRefs(_props);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:modelValue", value: string): void; (e: "update:modelValue", value: string): void;
(e: "submit"): void; (e: "submit"): void;

View file

@ -10,12 +10,11 @@ import { CoercableComponent } from "@/features/feature";
import { coerceComponent } from "@/util/vue"; import { coerceComponent } from "@/util/vue";
import { computed, toRefs, unref } from "vue"; import { computed, toRefs, unref } from "vue";
const props = toRefs( const _props = defineProps<{
defineProps<{
title?: CoercableComponent; title?: CoercableComponent;
modelValue?: boolean; modelValue?: boolean;
}>() }>();
); const props = toRefs(_props);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:modelValue", value: boolean): void; (e: "update:modelValue", value: boolean): void;
}>(); }>();

View file

@ -5,9 +5,9 @@
<div class="inner-tab"> <div class="inner-tab">
<Layer <Layer
v-if="layerKeys.includes(tab)" v-if="layerKeys.includes(tab)"
v-bind="wrapFeature(layers[tab])" v-bind="layers[tab]!"
:index="index" :index="index"
:tab="() => ($refs[`tab-${index}`] as HTMLElement | undefined)" :tab="() => (($refs[`tab-${index}`] as HTMLElement[] | undefined)?.[0])"
/> />
<component :is="tab" :index="index" v-else /> <component :is="tab" :index="index" v-else />
</div> </div>
@ -18,7 +18,6 @@
<script setup lang="ts"> <script setup lang="ts">
import modInfo from "@/data/modInfo.json"; import modInfo from "@/data/modInfo.json";
import { wrapFeature } from "@/features/feature";
import { layers } from "@/game/layers"; import { layers } from "@/game/layers";
import player from "@/game/player"; import player from "@/game/player";
import { computed, toRef } from "vue"; import { computed, toRef } from "vue";

View file

@ -20,7 +20,7 @@
<br /> <br />
<div> <div>
<a :href="discordLink"> <a :href="discordLink">
<img src="images/discord.png" class="game-over-modal-discord" /> <span class="material-icons game-over-modal-discord">discord</span>
{{ discordName }} {{ discordName }}
</a> </a>
</div> </div>

View file

@ -64,7 +64,8 @@ import { computed, ref, toRefs, unref } from "vue";
const { title, logo, author, discordName, discordLink, versionNumber, versionTitle } = modInfo; const { title, logo, author, discordName, discordLink, versionNumber, versionTitle } = modInfo;
const props = toRefs(defineProps<{ changelog: typeof Changelog | null }>()); const _props = defineProps<{ changelog: typeof Changelog | null }>();
const props = toRefs(_props);
const isOpen = ref(false); const isOpen = ref(false);

View file

@ -1,51 +1,82 @@
<template> <template>
<div class="layer-container"> <div class="layer-container">
<button v-if="showGoBack" class="goBack" @click="goBack"></button> <button v-if="showGoBack" class="goBack" @click="goBack"></button>
<button class="layer-tab minimized" v-if="minimized" @click="minimized = false"> <button class="layer-tab minimized" v-if="minimized.value" @click="minimized.value = false">
<div>{{ name }}</div> <div>{{ unref(name) }}</div>
</button> </button>
<div class="layer-tab" :style="style" :class="classes" v-else> <div class="layer-tab" :style="unref(style)" :class="unref(classes)" v-else>
<Links v-if="links" :links="links"> <Links v-if="links" :links="unref(links)">
<component :is="component" /> <component :is="component" />
</Links> </Links>
<component v-else :is="component" /> <component v-else :is="component" />
</div> </div>
<button v-if="minimizable" class="minimize" @click="minimized = true"></button> <button v-if="unref(minimizable)" class="minimize" @click="minimized.value = true">
</button>
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import Links from "@/components/system/Links.vue"; import Links from "@/components/system/Links.vue";
import { FeatureComponent } from "@/features/feature";
import { GenericLayer } from "@/game/layers";
import { coerceComponent } from "@/util/vue";
import { computed, nextTick, toRefs, unref, watch } from "vue";
import modInfo from "@/data/modInfo.json"; import modInfo from "@/data/modInfo.json";
import { CoercableComponent, PersistentRef, StyleValue } from "@/features/feature";
import { Link } from "@/features/links";
import player from "@/game/player"; import player from "@/game/player";
import { ProcessedComputable } from "@/util/computed";
import { computeComponent, wrapRef } from "@/util/vue";
import { computed, defineComponent, nextTick, PropType, toRefs, unref, watch } from "vue";
const props = toRefs( export default defineComponent({
defineProps< components: { Links },
FeatureComponent<GenericLayer> & { props: {
index: number; index: {
tab: () => HTMLElement | undefined; type: Number,
} required: true
>() },
); tab: {
type: Function as PropType<() => HTMLElement | undefined>,
required: true
},
display: {
type: [Object, String] as PropType<ProcessedComputable<CoercableComponent>>,
required: true
},
minimized: {
type: Object as PropType<PersistentRef<boolean>>,
required: true
},
minWidth: {
type: [Object, Number] as PropType<ProcessedComputable<number>>,
required: true
},
name: {
type: [Object, String] as PropType<ProcessedComputable<string>>,
required: true
},
style: Object as PropType<ProcessedComputable<StyleValue>>,
classes: Object as PropType<ProcessedComputable<Record<string, boolean>>>,
links: [Object, Array] as PropType<ProcessedComputable<Link[]>>,
minimizable: [Object, Boolean] as PropType<ProcessedComputable<boolean>>
},
setup(props) {
const { display, index, minimized, minWidth, tab } = toRefs(props);
const component = computed(() => coerceComponent(unref(props.display))); const component = computeComponent(display);
const showGoBack = computed( const showGoBack = computed(
() => modInfo.allowGoBack && unref(props.index) > 0 && !props.minimized.value () => modInfo.allowGoBack && unref(index) > 0 && !minimized.value
); );
function goBack() { function goBack() {
player.tabs = player.tabs.slice(0, unref(props.index)); player.tabs = player.tabs.slice(0, unref(props.index));
} }
nextTick(() => updateTab(props.minimized.value, props.minWidth.value)); nextTick(() => updateTab(minimized.value, unref(minWidth.value)));
watch([props.minimized, props.minWidth], ([minimized, minWidth]) => updateTab(minimized, minWidth)); watch([minimized, wrapRef(minWidth)], ([minimized, minWidth]) =>
updateTab(minimized, minWidth)
);
function updateTab(minimized: boolean, minWidth: number) { function updateTab(minimized: boolean, minWidth: number) {
const tabValue = props.tab.value(); const tabValue = tab.value();
if (tabValue != undefined) { if (tabValue != undefined) {
if (minimized) { if (minimized) {
tabValue.style.flexGrow = "0"; tabValue.style.flexGrow = "0";
@ -61,7 +92,16 @@ function updateTab(minimized: boolean, minWidth: number) {
tabValue.style.margin = ""; tabValue.style.margin = "";
} }
} }
} }
return {
component,
showGoBack,
unref,
goBack
};
}
});
</script> </script>
<style scoped> <style scoped>

View file

@ -12,13 +12,12 @@
import { Link, LinkNode } from "@/features/links"; import { Link, LinkNode } from "@/features/links";
import { computed, toRefs, unref } from "vue"; import { computed, toRefs, unref } from "vue";
const props = toRefs( const _props = defineProps<{
defineProps<{
link: Link; link: Link;
startNode: LinkNode; startNode: LinkNode;
endNode: LinkNode; endNode: LinkNode;
}>() }>();
); const props = toRefs(_props);
const startPosition = computed(() => { const startPosition = computed(() => {
const position = { x: props.startNode.value.x || 0, y: props.startNode.value.y || 0 }; const position = { x: props.startNode.value.x || 0, y: props.startNode.value.y || 0 };

View file

@ -6,7 +6,8 @@
import { RegisterLinkNodeInjectionKey, UnregisterLinkNodeInjectionKey } from "@/features/links"; import { RegisterLinkNodeInjectionKey, UnregisterLinkNodeInjectionKey } from "@/features/links";
import { computed, inject, ref, toRefs, unref, watch } from "vue"; import { computed, inject, ref, toRefs, unref, watch } from "vue";
const props = toRefs(defineProps<{ id: string }>()); const _props = defineProps<{ id: string }>();
const props = toRefs(_props);
const register = inject(RegisterLinkNodeInjectionKey); const register = inject(RegisterLinkNodeInjectionKey);
const unregister = inject(UnregisterLinkNodeInjectionKey); const unregister = inject(UnregisterLinkNodeInjectionKey);

View file

@ -1,7 +1,7 @@
<template> <template>
<slot /> <slot />
<div ref="resizeListener" class="resize-listener" /> <div ref="resizeListener" class="resize-listener" />
<svg v-bind="$attrs" v-if="validLinks"> <svg v-if="validLinks" v-bind="$attrs">
<LinkVue <LinkVue
v-for="(link, index) in validLinks" v-for="(link, index) in validLinks"
:key="index" :key="index"
@ -19,10 +19,11 @@ import {
RegisterLinkNodeInjectionKey, RegisterLinkNodeInjectionKey,
UnregisterLinkNodeInjectionKey UnregisterLinkNodeInjectionKey
} from "@/features/links"; } from "@/features/links";
import { computed, nextTick, onMounted, provide, ref, toRefs, unref } from "vue"; import { computed, nextTick, onMounted, provide, ref, toRefs } from "vue";
import LinkVue from "./Link.vue"; import LinkVue from "./Link.vue";
const props = toRefs(defineProps<{ links: Link[] }>()); const _props = defineProps<{ links: Link[] }>();
const { links } = toRefs(_props);
const observer = new MutationObserver(updateNodes); const observer = new MutationObserver(updateNodes);
const resizeObserver = new ResizeObserver(updateBounds); const resizeObserver = new ResizeObserver(updateBounds);
@ -42,7 +43,7 @@ onMounted(() => {
}); });
const validLinks = computed(() => const validLinks = computed(() =>
unref(props.links.value).filter(link => { links.value.filter(link => {
const n = nodes.value; const n = nodes.value;
return ( return (
n[link.startNode.id]?.x != undefined && n[link.startNode.id]?.x != undefined &&

View file

@ -2,8 +2,8 @@
<teleport to="#modal-root"> <teleport to="#modal-root">
<transition <transition
name="modal" name="modal"
@before-enter="setAnimating(true)" @before-enter="isAnimating = true"
@after-leave="setAnimating(false)" @after-leave="isAnimating = false"
> >
<div <div
class="modal-mask" class="modal-mask"
@ -41,13 +41,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { Link } from "@/features/links"; import { Link } from "@/features/links";
import { computed, ref } from "vue"; import { computed, ref, toRefs } from "vue";
import Links from "./Links.vue"; import Links from "./Links.vue";
const props = defineProps<{ const _props = defineProps<{
modelValue: boolean; modelValue: boolean;
links?: Link[]; links?: Link[];
}>(); }>();
const props = toRefs(_props);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:modelValue", value: boolean): void; (e: "update:modelValue", value: boolean): void;
}>(); }>();
@ -58,9 +59,8 @@ function close() {
} }
const isAnimating = ref(false); const isAnimating = ref(false);
function setAnimating(value: boolean) {
isAnimating.value = value; defineExpose({ isOpen });
}
</script> </script>
<style scoped> <style scoped>

View file

@ -15,7 +15,7 @@
<br /> <br />
<div> <div>
<a :href="discordLink" class="nan-modal-discord-link"> <a :href="discordLink" class="nan-modal-discord-link">
<img src="images/discord.png" class="nan-modal-discord" /> <span class="material-icons nan-modal-discord">discord</span>
{{ discordName }} {{ discordName }}
</a> </a>
</div> </div>
@ -48,14 +48,14 @@ import modInfo from "@/data/modInfo.json";
import player from "@/game/player"; import player from "@/game/player";
import state from "@/game/state"; import state from "@/game/state";
import Decimal, { DecimalSource, format } from "@/util/bignum"; import Decimal, { DecimalSource, format } from "@/util/bignum";
import { computed, ref, toRef } from "vue"; import { ComponentPublicInstance, computed, ref, toRef } from "vue";
import Toggle from "../fields/Toggle.vue"; import Toggle from "../fields/Toggle.vue";
import SavesManager from "./SavesManager.vue"; import SavesManager from "./SavesManager.vue";
const { discordName, discordLink } = modInfo; const { discordName, discordLink } = modInfo;
const autosave = toRef(player, "autosave"); const autosave = toRef(player, "autosave");
const hasNaN = toRef(state, "hasNaN"); const hasNaN = toRef(state, "hasNaN");
const savesManager = ref<typeof SavesManager | null>(null); const savesManager = ref<ComponentPublicInstance<typeof SavesManager> | null>(null);
const path = computed(() => state.NaNPath?.join(".")); const path = computed(() => state.NaNPath?.join("."));
const property = computed(() => state.NaNPath?.slice(-1)[0]); const property = computed(() => state.NaNPath?.slice(-1)[0]);

View file

@ -113,9 +113,9 @@ import Options from "./Options.vue";
import SavesManager from "./SavesManager.vue"; import SavesManager from "./SavesManager.vue";
import Tooltip from "./Tooltip.vue"; import Tooltip from "./Tooltip.vue";
const info = ref<typeof Info | null>(null); const info = ref<ComponentPublicInstance<typeof Info> | null>(null);
const savesManager = ref<typeof SavesManager | null>(null); const savesManager = ref<ComponentPublicInstance<typeof SavesManager> | null>(null);
const options = ref<typeof Options | null>(null); const options = ref<ComponentPublicInstance<typeof Options> | null>(null);
// For some reason Info won't accept the changelog unless I do this: // For some reason Info won't accept the changelog unless I do this:
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const changelog = ref<ComponentPublicInstance<any> | null>(null); const changelog = ref<ComponentPublicInstance<any> | null>(null);
@ -262,8 +262,8 @@ function openDiscord() {
text-shadow: 5px 0 10px var(--points), -3px 0 12px var(--points); text-shadow: 5px 0 10px var(--points), -3px 0 12px var(--points);
} }
.nav a, .nav > div > a,
.overlay-nav a { .overlay-nav > div > a {
color: var(--foreground); color: var(--foreground);
text-shadow: none; text-shadow: none;
} }

View file

@ -11,32 +11,24 @@
<Toggle title="Show TPS" v-model="showTPS" /> <Toggle title="Show TPS" v-model="showTPS" />
<Toggle title="Hide Maxed Challenges" v-model="hideChallenges" /> <Toggle title="Hide Maxed Challenges" v-model="hideChallenges" />
<Toggle title="Unthrottled" v-model="unthrottled" /> <Toggle title="Unthrottled" v-model="unthrottled" />
<Toggle <Toggle :title="offlineProdTitle" v-model="offlineProd" />
title="Offline Production<tooltip display='Save-specific'>*</tooltip>" <Toggle :title="autosaveTitle" v-model="autosave" />
v-model="offlineProd" <Toggle :title="isPausedTitle" v-model="isPaused" />
/>
<Toggle
title="Autosave<tooltip display='Save-specific'>*</tooltip>"
v-model="autosave"
/>
<Toggle
title="Pause game<tooltip display='Save-specific'>*</tooltip>"
v-model="isPaused"
/>
</template> </template>
</Modal> </Modal>
</template> </template>
<script setup lang="ts"> <script setup lang="tsx">
import Modal from "@/components/system/Modal.vue"; import Modal from "@/components/system/Modal.vue";
import rawThemes from "@/data/themes"; import rawThemes from "@/data/themes";
import { MilestoneDisplay } from "@/features/milestone"; import { MilestoneDisplay } from "@/features/milestone";
import player from "@/game/player"; import player from "@/game/player";
import settings from "@/game/settings"; import settings from "@/game/settings";
import { camelToTitle } from "@/util/common"; import { camelToTitle } from "@/util/common";
import { computed, ref, toRefs } from "vue"; import { computed, ref, toRef, toRefs } from "vue";
import Toggle from "../fields/Toggle.vue"; import Toggle from "../fields/Toggle.vue";
import Select from "../fields/Select.vue"; import Select from "../fields/Select.vue";
import Tooltip from "./Tooltip.vue";
const isOpen = ref(false); const isOpen = ref(false);
@ -58,7 +50,8 @@ const msDisplayOptions = Object.values(MilestoneDisplay).map(option => ({
})); }));
const { showTPS, hideChallenges, theme, msDisplay, unthrottled } = toRefs(settings); const { showTPS, hideChallenges, theme, msDisplay, unthrottled } = toRefs(settings);
const { autosave, offlineProd, devSpeed } = toRefs(player); const { autosave, offlineProd } = toRefs(player);
const devSpeed = toRef(player, "devSpeed");
const isPaused = computed({ const isPaused = computed({
get() { get() {
return devSpeed.value === 0; return devSpeed.value === 0;
@ -67,6 +60,22 @@ const isPaused = computed({
devSpeed.value = value ? null : 0; devSpeed.value = value ? null : 0;
} }
}); });
const offlineProdTitle = (
<template>
Offline Production<Tooltip display="Save-specific">*</Tooltip>
</template>
);
const autosaveTitle = (
<template>
Autosave<Tooltip display="Save-specific">*</Tooltip>
</template>
);
const isPausedTitle = (
<template>
Pause game<Tooltip display="Save-specific">*</Tooltip>
</template>
);
</script> </script>
<style scoped> <style scoped>

View file

@ -8,12 +8,11 @@
import { displayResource, Resource } from "@/features/resource"; import { displayResource, Resource } from "@/features/resource";
import { computed, toRefs } from "vue"; import { computed, toRefs } from "vue";
const props = toRefs( const _props = defineProps<{
defineProps<{
resource: Resource; resource: Resource;
color: string; color: string;
}>() }>();
); const props = toRefs(_props);
const amount = computed(() => displayResource(props.resource)); const amount = computed(() => displayResource(props.resource));
</script> </script>

View file

@ -57,17 +57,16 @@
<script setup lang="ts"> <script setup lang="ts">
import player from "@/game/player"; import player from "@/game/player";
import { computed, ref, toRefs, unref, watch } from "vue"; import { computed, ref, toRefs, watch } from "vue";
import DangerButton from "../fields/DangerButton.vue"; import DangerButton from "../fields/DangerButton.vue";
import FeedbackButton from "../fields/FeedbackButton.vue"; import FeedbackButton from "../fields/FeedbackButton.vue";
import Text from "../fields/Text.vue"; import Text from "../fields/Text.vue";
import { LoadablePlayerData } from "./SavesManager.vue"; import { LoadablePlayerData } from "./SavesManager.vue";
const props = toRefs( const _props = defineProps<{
defineProps<{
save: LoadablePlayerData; save: LoadablePlayerData;
}>() }>();
); const { save } = toRefs(_props);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "export"): void; (e: "export"): void;
(e: "open"): void; (e: "open"): void;
@ -91,8 +90,10 @@ const newName = ref("");
watch(isEditing, () => (newName.value = "")); watch(isEditing, () => (newName.value = ""));
const isActive = computed(() => unref(props.save).id === player.id); const isActive = computed(() => save.value && save.value.id === player.id);
const currentTime = computed(() => (isActive.value ? player.time : unref(props.save).time)); const currentTime = computed(() =>
isActive.value ? player.time : (save.value && save.value.time) || 0
);
function changeName() { function changeName() {
emit("editName", newName.value); emit("editName", newName.value);

View file

@ -1,21 +1,26 @@
<template> <template>
<Modal v-model="isOpen"> <Modal v-model="isOpen" ref="modal">
<template v-slot:header> <template v-slot:header>
<h2>Saves Manager</h2> <h2>Saves Manager</h2>
</template> </template>
<template v-slot:body> <template v-slot:body>
<div v-sortable="{ update, handle: '.handle' }"> <Draggable
:list="settings.saves"
handle=".handle"
v-if="unref(modal?.isOpen)"
:itemKey="(save: string) => save"
>
<template #item="{ element }">
<Save <Save
v-for="(save, index) in saves" :save="saves[element]"
:key="index" @open="openSave(element)"
:save="save!" @export="exportSave(element)"
@open="openSave(save!.id)" @editName="name => editSave(element, name)"
@export="exportSave(save!.id)" @duplicate="duplicateSave(element)"
@editName="name => editSave(save!.id, name)" @delete="deleteSave(element)"
@duplicate="duplicateSave(save!.id)"
@delete="deleteSave(save!.id)"
/> />
</div> </template>
</Draggable>
</template> </template>
<template v-slot:footer> <template v-slot:footer>
<div class="modal-footer"> <div class="modal-footer">
@ -55,16 +60,17 @@
import Modal from "@/components/system/Modal.vue"; import Modal from "@/components/system/Modal.vue";
import player, { PlayerData } from "@/game/player"; import player, { PlayerData } from "@/game/player";
import settings from "@/game/settings"; import settings from "@/game/settings";
import { getUniqueID, loadSave, save, newSave as createNewSave } from "@/util/save"; import { getUniqueID, loadSave, save, newSave } from "@/util/save";
import { nextTick, ref, watch } from "vue"; import { ComponentPublicInstance, computed, nextTick, reactive, ref, unref, watch } from "vue";
import Select from "../fields/Select.vue"; import Select from "../fields/Select.vue";
import Text from "../fields/Text.vue"; import Text from "../fields/Text.vue";
import Save from "./Save.vue"; import Save from "./Save.vue";
import vSortable from "vue-sortable"; import Draggable from "vuedraggable";
export type LoadablePlayerData = Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown }; export type LoadablePlayerData = Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown };
const isOpen = ref(false); const isOpen = ref(false);
const modal = ref<ComponentPublicInstance<typeof Modal> | null>(null);
defineExpose({ defineExpose({
open() { open() {
@ -75,12 +81,6 @@ defineExpose({
const importingFailed = ref(false); const importingFailed = ref(false);
const saveToImport = ref(""); const saveToImport = ref("");
watch(isOpen, isOpen => {
if (isOpen) {
loadSaveData();
}
});
watch(saveToImport, save => { watch(saveToImport, save => {
if (save) { if (save) {
nextTick(() => { nextTick(() => {
@ -96,7 +96,6 @@ watch(saveToImport, save => {
id, id,
btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))) btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
); );
saves.value[id] = playerData;
saveToImport.value = ""; saveToImport.value = "";
importingFailed.value = false; importingFailed.value = false;
@ -122,25 +121,36 @@ let bank = ref(
}, []) }, [])
); );
const saves = ref<Record<string, LoadablePlayerData | undefined>>({}); const cachedSaves = reactive<Record<string, LoadablePlayerData>>({});
function getCachedSave(id: string) {
function loadSaveData() { if (!(id in cachedSaves)) {
saves.value = settings.saves.reduce((acc: Record<string, LoadablePlayerData>, curr: string) => { const save = localStorage.getItem(id);
try {
const save = localStorage.getItem(curr);
if (save == null) { if (save == null) {
acc[curr] = { error: `Save with id "${curr}" doesn't exist`, id: curr }; cachedSaves[id] = { error: `Save with id "${id}" doesn't exist`, id };
} else { } else {
acc[curr] = JSON.parse(decodeURIComponent(escape(atob(save)))); try {
acc[curr].id = curr; cachedSaves[id] = JSON.parse(decodeURIComponent(escape(atob(save))));
} cachedSaves[id].id = id;
} catch (error) { } catch (error) {
console.warn(`Can't load save with id "${curr}"`, error); cachedSaves[id] = { error, id };
acc[curr] = { error, id: curr };
} }
return acc; }
}, {}); }
return cachedSaves[id];
} }
// Wipe cache whenever the modal is opened
watch(isOpen, isOpen => {
if (isOpen) {
Object.keys(cachedSaves).forEach(key => delete cachedSaves[key]);
}
});
const saves = computed(() =>
settings.saves.reduce((acc: Record<string, LoadablePlayerData>, curr: string) => {
acc[curr] = getCachedSave(curr);
return acc;
}, {})
);
function exportSave(id: string) { function exportSave(id: string) {
let saveToExport; let saveToExport;
@ -172,13 +182,11 @@ function duplicateSave(id: string) {
); );
settings.saves.push(playerData.id); settings.saves.push(playerData.id);
saves.value[playerData.id] = playerData;
} }
function deleteSave(id: string) { function deleteSave(id: string) {
settings.saves = settings.saves.filter((save: string) => save !== id); settings.saves = settings.saves.filter((save: string) => save !== id);
localStorage.removeItem(id); localStorage.removeItem(id);
saves.value[id] = undefined;
} }
function openSave(id: string) { function openSave(id: string) {
@ -189,11 +197,6 @@ function openSave(id: string) {
loadSave(saves.value[id]!); loadSave(saves.value[id]!);
} }
function newSave() {
const playerData = createNewSave();
saves.value[playerData.id] = playerData;
}
function newFromPreset(preset: string) { function newFromPreset(preset: string) {
const playerData = JSON.parse(decodeURIComponent(escape(atob(preset)))); const playerData = JSON.parse(decodeURIComponent(escape(atob(preset))));
playerData.id = getUniqueID(); playerData.id = getUniqueID();
@ -203,24 +206,19 @@ function newFromPreset(preset: string) {
); );
settings.saves.push(playerData.id); settings.saves.push(playerData.id);
saves.value[playerData.id] = playerData;
} }
function editSave(id: string, newName: string) { function editSave(id: string, newName: string) {
saves.value[id].name = newName; const currSave = saves.value[id];
if (currSave) {
currSave.name = newName;
if (player.id === id) { if (player.id === id) {
player.name = newName; player.name = newName;
save(); save();
} else { } else {
localStorage.setItem( localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(currSave)))));
id, }
btoa(unescape(encodeURIComponent(JSON.stringify(saves.value[id]))))
);
} }
}
function update(e: { newIndex: number; oldIndex: number }) {
settings.saves.splice(e.newIndex, 0, settings.saves.splice(e.oldIndex, 1)[0]);
} }
</script> </script>

View file

@ -5,8 +5,8 @@
<script setup lang="ts"> <script setup lang="ts">
withDefaults( withDefaults(
defineProps<{ defineProps<{
width: string; width?: string;
height: string; height?: string;
}>(), }>(),
{ {
width: "8px", width: "8px",

View file

@ -2,42 +2,66 @@
<div <div
class="tooltip-container" class="tooltip-container"
:class="{ shown: isShown }" :class="{ shown: isShown }"
@mouseenter="setHover(true)" @mouseenter="isHovered = true"
@mouseleave="setHover(false)" @mouseleave="isHovered = false"
> >
<slot /> <slot />
<transition name="fade"> <transition name="fade">
<div <div
v-if="isShown" v-if="isShown"
class="tooltip" class="tooltip"
:class="{ top, left, right, bottom }" :class="{
top: unref(top),
left: unref(left),
right: unref(right),
bottom: unref(bottom)
}"
:style="{ :style="{
'--xoffset': xoffset || '0px', '--xoffset': unref(xoffset) || '0px',
'--yoffset': yoffset || '0px' '--yoffset': unref(yoffset) || '0px'
}" }"
> >
<component :is="component" /> <component v-if="component" :is="component" />
</div> </div>
</transition> </transition>
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import { FeatureComponent } from "@/features/feature"; import { CoercableComponent } from "@/features/feature";
import { Tooltip } from "@/features/tooltip"; import { ProcessedComputable } from "@/util/computed";
import { coerceComponent } from "@/util/vue"; import { computeOptionalComponent, unwrapRef } from "@/util/vue";
import { computed, ref, toRefs, unref } from "vue"; import { computed, defineComponent, PropType, ref, toRefs, unref } from "vue";
const props = toRefs(defineProps<FeatureComponent<Tooltip>>()); export default defineComponent({
props: {
display: {
type: [Object, String] as PropType<ProcessedComputable<CoercableComponent>>,
required: true
},
top: Boolean as PropType<ProcessedComputable<boolean>>,
left: Boolean as PropType<ProcessedComputable<boolean>>,
right: Boolean as PropType<ProcessedComputable<boolean>>,
bottom: Boolean as PropType<ProcessedComputable<boolean>>,
xoffset: String as PropType<ProcessedComputable<string>>,
yoffset: String as PropType<ProcessedComputable<string>>,
force: Boolean as PropType<ProcessedComputable<boolean>>
},
setup(props) {
const { display, force } = toRefs(props);
const isHovered = ref(false); const isHovered = ref(false);
const isShown = computed(() => unwrapRef(force) || isHovered.value);
const component = computeOptionalComponent(display);
function setHover(hover: boolean) { return {
isHovered.value = hover; isHovered,
} isShown,
component,
const isShown = computed(() => unref(props.force) || isHovered.value); unref
const component = computed(() => props.display.value && coerceComponent(unref(props.display))); };
}
});
</script> </script>
<style scoped> <style scoped>

View file

@ -360,16 +360,6 @@ export const g = createTreeNode({
}); });
export const h = createTreeNode({ export const h = createTreeNode({
id: "h", id: "h",
branches: [
"g",
() => ({
target: "flatBoi",
featureType: "bar",
endOffset: {
x: -50 + 100 * flatBoi.progress.value.toNumber()
}
})
],
tooltip() { tooltip() {
return `Restore your points to ${format(otherThingy.value)}`; return `Restore your points to ${format(otherThingy.value)}`;
}, },
@ -388,7 +378,7 @@ const tree = createTree({
[g, spook, h] [g, spook, h]
]; ];
}, },
branches: [ branches: () => [
{ {
startNode: fNode, startNode: fNode,
endNode: treeNode, endNode: treeNode,
@ -415,7 +405,7 @@ const illuminatiTabs = createTabFamily({
display: "first" display: "first"
}), }),
second: createTabButton({ second: createTabButton({
tab: fTab, tab: () => fTab,
display: "second" display: "second"
}) })
}, },
@ -569,7 +559,15 @@ const layer = createLayer({
id, id,
color, color,
name, name,
links: tree.links, links() {
const links = tree.links.value.slice();
links.push({
startNode: h,
endNode: flatBoi,
offsetEnd: { x: -50 + 100 * flatBoi.progress.value.toNumber(), y: 0 }
});
return links;
},
points, points,
beep, beep,
thingy, thingy,
@ -601,7 +599,7 @@ const layer = createLayer({
treeNode, treeNode,
resetButton, resetButton,
minWidth: 800, minWidth: 800,
display: tabs display: render(tabs)
}); });
export default layer; export default layer;

View file

@ -57,28 +57,28 @@ export const main = createLayer({
id: "main", id: "main",
name: "Tree", name: "Tree",
links: tree.links, links: tree.links,
display() { display: (
return (
<template> <template>
<div v-if={player.devSpeed === 0}>Game Paused</div> <div v-show={player.devSpeed === 0}>Game Paused</div>
<div v-else-if={player.devSpeed && player.devSpeed !== 1}> <div v-show={player.devSpeed && player.devSpeed !== 1}>
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} Dev Speed: {format(player.devSpeed || 0)}x
Dev Speed: {format(player.devSpeed!)}x
</div> </div>
<div v-if={player.offlineTime != undefined}> <div v-show={player.offlineTime != undefined}>
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} Offline Time: {formatTime(player.offlineTime || 0)}
Offline Time: {formatTime(player.offlineTime!)}
</div> </div>
<div> <div>
<span v-if={Decimal.lt(points.value, "1e1000")}>You have </span> <span v-show={Decimal.lt(points.value, "1e1000")}>You have </span>
<h2>{format(points.value)}</h2> <h2>{format(points.value)}</h2>
<span v-if={Decimal.lt(points.value, "1e1e6")}> points</span> <span v-show={Decimal.lt(points.value, "1e1e6")}> points</span>
</div> </div>
<div v-if={Decimal.gt(pointGain.value, 0)}> <div v-show={Decimal.gt(pointGain.value, 0)}>
({oomps.value === "" ? formatSmall(pointGain.value) : oomps.value}/sec) ({oomps.value === "" ? formatSmall(pointGain.value) : oomps.value}/sec)
</div> </div>
<Spacer /> <Spacer />
<Modal v-model={showModal}> <Modal
modelValue={showModal.value}
onUpdate:modelValue={value => (showModal.value = value)}
>
<svg style="height: 80vmin; width: 80vmin;"> <svg style="height: 80vmin; width: 80vmin;">
<path d="M 32 222 Q 128 222, 128 0 Q 128 222, 224 222 L 224 224 L 32 224" /> <path d="M 32 222 Q 128 222, 128 0 Q 128 222, 224 222 L 224 224 L 32 224" />
@ -89,8 +89,7 @@ export const main = createLayer({
</Modal> </Modal>
{render(tree)} {render(tree)}
</template> </template>
); ),
},
points, points,
best, best,
total, total,

View file

@ -117,12 +117,16 @@ globalBus.on("addLayer", layer => {
achievement[PersistentState].value = true; achievement[PersistentState].value = true;
achievement.onComplete?.(); achievement.onComplete?.();
if (achievement.display) { if (achievement.display) {
const display = unref(achievement.display); const Display = coerceComponent(unref(achievement.display));
toast.info( toast.info(
<template> <div>
<h2>Milestone earned!</h2> <h3>Achievement earned!</h3>
<div>{coerceComponent(display)}</div> <div>
</template> {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
{/* @ts-ignore */}
<Display />
</div>
</div>
); );
} }
} }

View file

@ -3,10 +3,9 @@ import { GenericLayer } from "@/game/layers";
import Decimal, { DecimalSource } from "@/util/bignum"; import Decimal, { DecimalSource } from "@/util/bignum";
import { ProcessedComputable } from "@/util/computed"; import { ProcessedComputable } from "@/util/computed";
import { isArray } from "@vue/shared"; import { isArray } from "@vue/shared";
import { ComponentOptions, CSSProperties, DefineComponent, isRef, ref, Ref, UnwrapRef } from "vue"; import { ComponentOptions, CSSProperties, DefineComponent, isRef, ref, Ref } from "vue";
export const PersistentState = Symbol("PersistentState"); export const PersistentState = Symbol("PersistentState");
export const SetupPersistence = Symbol("SetupPersistence");
export const DefaultValue = Symbol("DefaultValue"); export const DefaultValue = Symbol("DefaultValue");
export const Component = Symbol("Component"); export const Component = Symbol("Component");
@ -27,30 +26,19 @@ export type StyleValue = string | CSSProperties | Array<string | CSSProperties>;
export type Persistent<T extends State = State> = { export type Persistent<T extends State = State> = {
[PersistentState]: Ref<T>; [PersistentState]: Ref<T>;
[DefaultValue]: T; [DefaultValue]: T;
[SetupPersistence]: () => Ref<T>;
};
export type PersistentRef<T extends State = State> = Ref<T> & {
[DefaultValue]: T;
[SetupPersistence]: () => Ref<T>;
}; };
export type PersistentRef<T extends State = State> = Ref<T> & Persistent<T>;
// TODO if importing .vue components in .tsx can become type safe, // TODO if importing .vue components in .tsx can become type safe,
// this type can probably be safely removed // this type can probably be safely removed
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type GenericComponent = DefineComponent<any, any, any>; export type GenericComponent = DefineComponent<any, any, any>;
// Example usage: `<Upgrade {...wrapComputable<GenericUpgrade>(upgrade)} />`
export function wrapFeature<T>(component: T): UnwrapRef<T> {
// TODO is this okay, or do we actually need to unref each property?
return component as unknown as UnwrapRef<T>;
}
export type FeatureComponent<T> = Omit< export type FeatureComponent<T> = Omit<
{ {
[K in keyof T]: T[K] extends ProcessedComputable<infer S> ? S : T[K]; [K in keyof T]: T[K] extends ProcessedComputable<infer S> ? S : T[K];
}, },
typeof Component | typeof DefaultValue | typeof SetupPersistence typeof Component | typeof DefaultValue
>; >;
export type Replace<T, S> = S & Omit<T, keyof S>; export type Replace<T, S> = S & Omit<T, keyof S>;
@ -75,14 +63,13 @@ export function showIf(condition: boolean, otherwise = Visibility.None): Visibil
} }
export function persistent<T extends State>(defaultValue: T | Ref<T>): PersistentRef<T> { export function persistent<T extends State>(defaultValue: T | Ref<T>): PersistentRef<T> {
const persistent = isRef(defaultValue) ? defaultValue : (ref(defaultValue) as Ref<T>); const persistent = (
(persistent as unknown as PersistentRef<T>)[DefaultValue] = isRef(defaultValue) isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
? defaultValue.value ) as PersistentRef<T>;
: defaultValue;
(persistent as unknown as PersistentRef<T>)[SetupPersistence] = function () { persistent[PersistentState] = persistent;
return persistent; persistent[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue;
}; return persistent as PersistentRef<T>;
return persistent as unknown as PersistentRef<T>;
} }
export function makePersistent<T extends State>( export function makePersistent<T extends State>(
@ -92,18 +79,8 @@ export function makePersistent<T extends State>(
const persistent = obj as Partial<Persistent<T>>; const persistent = obj as Partial<Persistent<T>>;
const state = ref(defaultValue) as Ref<T>; const state = ref(defaultValue) as Ref<T>;
Object.defineProperty(persistent, PersistentState, { persistent[PersistentState] = state;
get: () => {
return state.value;
},
set: (val: T) => {
state.value = val;
}
});
persistent[DefaultValue] = isRef(defaultValue) ? (defaultValue.value as T) : defaultValue; persistent[DefaultValue] = isRef(defaultValue) ? (defaultValue.value as T) : defaultValue;
persistent[SetupPersistence] = function () {
return state;
};
} }
export function setDefault<T, K extends keyof T>( export function setDefault<T, K extends keyof T>(
@ -135,32 +112,39 @@ export function findFeatures(obj: Record<string, unknown>, type: symbol): unknow
} }
globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>) => { globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>) => {
const handleObject = ( const handleObject = (obj: Record<string, unknown>, path: string[] = []): boolean => {
obj: Record<string, unknown>,
persistentState: Record<string, unknown>
): boolean => {
let foundPersistent = false; let foundPersistent = false;
Object.keys(obj).forEach(key => { Object.keys(obj).forEach(key => {
const value = obj[key]; const value = obj[key];
if (value && typeof value === "object") { if (value && typeof value === "object") {
if (SetupPersistence in value) { if (PersistentState in value) {
foundPersistent = true; foundPersistent = true;
// Construct save path if it doesn't exist
const persistentState = path.reduce<Record<string, unknown>>((acc, curr) => {
if (!(curr in acc)) {
acc[curr] = {};
}
return acc[curr] as Record<string, unknown>;
}, saveData);
// Cache currently saved value
const savedValue = persistentState[key]; const savedValue = persistentState[key];
// eslint-disable-next-line @typescript-eslint/no-explicit-any // Add ref to save data
persistentState[key] = (value as PersistentRef | Persistent)[ persistentState[key] = (value as Persistent)[PersistentState];
SetupPersistence // Load previously saved value
]();
if (savedValue != null) { if (savedValue != null) {
(persistentState[key] as Ref<unknown>).value = savedValue; (persistentState[key] as Ref<unknown>).value = savedValue;
} }
} else if (!(value instanceof Decimal)) { } else if (!(value instanceof Decimal)) {
if (typeof persistentState[key] !== "object") { // Continue traversing
persistentState[key] = {}; const foundPersistentInChild = handleObject(value as Record<string, unknown>, [
} ...path,
const foundPersistentInChild = handleObject( key
value as Record<string, unknown>, ]);
persistentState[key] as Record<string, unknown>
); // Show warning for persistent values inside arrays
// TODO handle arrays better
if (foundPersistentInChild) { if (foundPersistentInChild) {
if (isArray(value)) { if (isArray(value)) {
console.warn( console.warn(
@ -177,5 +161,5 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
}); });
return foundPersistent; return foundPersistent;
}; };
handleObject(layer, saveData); handleObject(layer);
}); });

View file

@ -21,30 +21,44 @@ import {
ProcessedComputable ProcessedComputable
} from "@/util/computed"; } from "@/util/computed";
import { createProxy, Proxied } from "@/util/proxies"; import { createProxy, Proxied } from "@/util/proxies";
import { unref } from "vue"; import { computed, unref } from "vue";
export const GridType = Symbol("Grid"); export const GridType = Symbol("Grid");
export type CellComputable<T> = Computable<T> | ((id: string | number, state: State) => T); export type CellComputable<T> = Computable<T> | ((id: string | number, state: State) => T);
function createGridProxy(object: Record<string, unknown>): Record<string | number, GridCell> { function createGridProxy(grid: GenericGrid): Record<string | number, GridCell> {
if (object.isProxy) { return new Proxy({}, getGridHandler(grid)) as Proxied<Record<string | number, GridCell>>;
console.warn(
"Creating a proxy out of a proxy! This may cause unintentional function calls and stack overflows."
);
}
return new Proxy(object, gridHandler) as Proxied<Record<string | number, GridCell>>;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const gridHandler: ProxyHandler<Record<PropertyKey, any>> = { function getGridHandler(
grid: GenericGrid
): ProxyHandler<Record<string | number, Proxied<GridCell>>> {
const keys = computed(() => {
const keys = [];
for (let row = 1; row <= grid.rows; row++) {
for (let col = 1; col <= grid.cols; col++) {
keys.push((row * 100 + col).toString());
}
}
return keys;
});
return {
get(target, key) { get(target, key) {
if (key === "isProxy") { if (key === "isProxy") {
return true; return true;
} }
if (typeof key === "symbol") {
return (grid as never)[key];
}
if (target[key] == null) { if (target[key] == null) {
target[key] = new Proxy(target, getCellHandler(key.toString())); target[key] = new Proxy(
grid,
getCellHandler(key.toString())
) as unknown as Proxied<GridCell>;
} }
return target[key]; return target[key];
@ -52,42 +66,54 @@ const gridHandler: ProxyHandler<Record<PropertyKey, any>> = {
set(target, key, value) { set(target, key, value) {
console.warn("Cannot set grid cells", target, key, value); console.warn("Cannot set grid cells", target, key, value);
return false; return false;
},
ownKeys() {
return keys.value;
},
has(target, key) {
return keys.value.includes(key.toString());
} }
}; };
}
function getCellHandler(id: string) { function getCellHandler(id: string): ProxyHandler<GenericGrid> {
return { return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
get(target: Record<string, any>, key: string, receiver: typeof Proxy): any { get(target, key, receiver): any {
if (key === "isProxy") { if (key === "isProxy") {
return true; return true;
} }
let prop = target[key]; // eslint-disable-next-line @typescript-eslint/no-explicit-any
let prop = (target as any)[key];
if (isFunction(prop)) { if (isFunction(prop)) {
return () => prop.call(receiver, id, target.getState(id)); return () => prop.call(receiver, id, target.getState(id));
} }
if (prop != undefined || key.slice == undefined) { if (prop != undefined || typeof key === "symbol") {
return prop; return prop;
} }
key = key.slice(0, 1).toUpperCase() + key.slice(1); key = key.slice(0, 1).toUpperCase() + key.slice(1);
prop = target[`get${key}`];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
prop = (target as any)[`get${key}`];
if (isFunction(prop)) { if (isFunction(prop)) {
return prop.call(receiver, id, target.getState(id)); return prop.call(receiver, id, target.getState(id));
} else if (prop != undefined) { } else if (prop != undefined) {
return unref(prop); return unref(prop);
} }
prop = target[`on${key}`]; // eslint-disable-next-line @typescript-eslint/no-explicit-any
prop = (target as any)[`on${key}`];
if (isFunction(prop)) { if (isFunction(prop)) {
return () => prop.call(receiver, id, target.getState(id)); return () => prop.call(receiver, id, target.getState(id));
} else if (prop != undefined) { } else if (prop != undefined) {
return prop; return prop;
} }
return target[key]; // eslint-disable-next-line @typescript-eslint/no-explicit-any
return (target as any)[key];
}, },
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
set(target: Record<string, any>, key: string, value: any, receiver: typeof Proxy): boolean { set(target: Record<string, any>, key: string, value: any, receiver: typeof Proxy): boolean {
@ -138,7 +164,7 @@ export interface GridOptions {
export interface BaseGrid extends Persistent<Record<string | number, State>> { export interface BaseGrid extends Persistent<Record<string | number, State>> {
id: string; id: string;
getID: (id: string | number, state: State) => string; getID: (id: string | number, state: State) => string;
getState: (id: string | number, state: State) => State; getState: (id: string | number) => State;
setState: (id: string | number, state: State) => void; setState: (id: string | number, state: State) => void;
cells: Record<string | number, GridCell>; cells: Record<string | number, GridCell>;
type: typeof GridType; type: typeof GridType;
@ -176,7 +202,6 @@ export function createGrid<T extends GridOptions>(options: T & ThisType<Grid<T>>
grid.id = getUniqueID("grid-"); grid.id = getUniqueID("grid-");
grid[Component] = GridComponent; grid[Component] = GridComponent;
grid.cells = createGridProxy(grid as unknown as Record<string, unknown>);
grid.getID = function (this: GenericGrid, cell: string | number) { grid.getID = function (this: GenericGrid, cell: string | number) {
return grid.id + "-" + cell; return grid.id + "-" + cell;
}; };
@ -205,5 +230,6 @@ export function createGrid<T extends GridOptions>(options: T & ThisType<Grid<T>>
processComputable(grid as T, "getDisplay"); processComputable(grid as T, "getDisplay");
const proxy = createProxy(grid as unknown as Grid<T>); const proxy = createProxy(grid as unknown as Grid<T>);
(proxy as GenericGrid).cells = createGridProxy(proxy as GenericGrid);
return proxy; return proxy;
} }

View file

@ -146,13 +146,16 @@ globalBus.on("addLayer", layer => {
milestone.onComplete?.(); milestone.onComplete?.();
if (milestone.display) { if (milestone.display) {
const display = unref(milestone.display); const display = unref(milestone.display);
toast.info( const Display = coerceComponent(
<template>
<h2>Milestone earned!</h2>
<div>
{coerceComponent(
isCoercableComponent(display) ? display : display.requirement isCoercableComponent(display) ? display : display.requirement
)} );
toast(
<template>
<h3>Milestone earned!</h3>
<div>
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
{/* @ts-ignore */}
<Display />
</div> </div>
</template> </template>
); );

View file

@ -112,3 +112,14 @@ export function displayResource(resource: Resource, overrideAmount?: DecimalSour
} }
return format(amount, resource.precision, resource.small); return format(amount, resource.precision, resource.small);
} }
// unref may unwrap a resource too far, so this function properly unwraps it
export function unwrapResource<T extends State>(
resource: Resource<T> | Ref<Resource<T>>
): Resource<T> {
console.log(resource);
if ("displayName" in resource) {
return resource;
}
return resource.value;
}

View file

@ -15,6 +15,7 @@ import {
getUniqueID, getUniqueID,
makePersistent, makePersistent,
Persistent, Persistent,
PersistentRef,
PersistentState, PersistentState,
Replace, Replace,
setDefault, setDefault,
@ -85,6 +86,7 @@ export interface TabFamilyOptions {
interface BaseTabFamily extends Persistent<string> { interface BaseTabFamily extends Persistent<string> {
id: string; id: string;
activeTab: Ref<GenericTab | CoercableComponent | null>; activeTab: Ref<GenericTab | CoercableComponent | null>;
selected: Ref<string>;
type: typeof TabFamilyType; type: typeof TabFamilyType;
[Component]: typeof TabFamilyComponent; [Component]: typeof TabFamilyComponent;
} }
@ -112,6 +114,7 @@ export function createTabFamily<T extends TabFamilyOptions>(
tabFamily[Component] = TabFamilyComponent; tabFamily[Component] = TabFamilyComponent;
makePersistent<string>(tabFamily, Object.keys(options.tabs)[0]); makePersistent<string>(tabFamily, Object.keys(options.tabs)[0]);
tabFamily.selected = tabFamily[PersistentState];
tabFamily.activeTab = computed(() => { tabFamily.activeTab = computed(() => {
const tabs = unref(proxy.tabs); const tabs = unref(proxy.tabs);
if ( if (

View file

@ -162,7 +162,7 @@ export function createTree<T extends TreeOptions>(options: T & ThisType<Tree<T>>
proxy.isResetting.value = false; proxy.isResetting.value = false;
proxy.resettingNode.value = null; proxy.resettingNode.value = null;
}; };
tree.links = computed(() => proxy.branches as Link[]); tree.links = computed(() => (proxy.branches == null ? [] : unref(proxy.branches)));
processComputable(tree as T, "visibility"); processComputable(tree as T, "visibility");
setDefault(tree, "visibility", Visibility.Visible); setDefault(tree, "visibility", Visibility.Visible);

View file

@ -47,17 +47,17 @@ function update() {
// Add offline time if any // Add offline time if any
if (player.offlineTime != undefined) { if (player.offlineTime != undefined) {
if (player.offlineTime.gt(modInfo.offlineLimit * 3600)) { if (Decimal.gt(player.offlineTime, modInfo.offlineLimit * 3600)) {
player.offlineTime = new Decimal(modInfo.offlineLimit * 3600); player.offlineTime = new Decimal(modInfo.offlineLimit * 3600);
} }
if (player.offlineTime.gt(0) && player.devSpeed !== 0) { if (Decimal.gt(player.offlineTime, 0) && player.devSpeed !== 0) {
const offlineDiff = Decimal.max(player.offlineTime.div(10), diff); const offlineDiff = Decimal.div(player.offlineTime, 10).max(diff);
player.offlineTime = player.offlineTime.sub(offlineDiff); player.offlineTime = Decimal.sub(player.offlineTime, offlineDiff);
diff = diff.add(offlineDiff); diff = diff.add(offlineDiff);
} else if (player.devSpeed === 0) { } else if (player.devSpeed === 0) {
player.offlineTime = player.offlineTime.add(diff); player.offlineTime = Decimal.add(player.offlineTime, diff);
} }
if (!player.offlineProd || player.offlineTime.lt(0)) { if (!player.offlineProd || Decimal.lt(player.offlineTime, 0)) {
player.offlineTime = null; player.offlineTime = null;
} }
} }
@ -74,7 +74,7 @@ function update() {
if (diff.eq(0)) { if (diff.eq(0)) {
return; return;
} }
player.timePlayed = player.timePlayed.add(diff); player.timePlayed = Decimal.add(player.timePlayed, diff);
globalBus.emit("update", diff, trueDiff); globalBus.emit("update", diff, trueDiff);
if (settings.unthrottled) { if (settings.unthrottled) {

View file

@ -1,7 +1,7 @@
import Decimal, { DecimalSource } from "@/util/bignum"; import Decimal, { DecimalSource } from "@/util/bignum";
import { isPlainObject } from "@/util/common"; import { isPlainObject } from "@/util/common";
import { ProxiedWithState, ProxyPath, ProxyState } from "@/util/proxies"; import { ProxiedWithState, ProxyPath, ProxyState } from "@/util/proxies";
import { reactive, unref } from "vue"; import { shallowReactive, unref } from "vue";
import transientState from "./state"; import transientState from "./state";
export interface PlayerData { export interface PlayerData {
@ -12,8 +12,8 @@ export interface PlayerData {
time: number; time: number;
autosave: boolean; autosave: boolean;
offlineProd: boolean; offlineProd: boolean;
offlineTime: Decimal | null; offlineTime: DecimalSource | null;
timePlayed: Decimal; timePlayed: DecimalSource;
keepGoing: boolean; keepGoing: boolean;
minimized: Record<string, boolean>; minimized: Record<string, boolean>;
modID: string; modID: string;
@ -23,7 +23,7 @@ export interface PlayerData {
export type Player = ProxiedWithState<PlayerData>; export type Player = ProxiedWithState<PlayerData>;
const state = reactive<PlayerData>({ const state = shallowReactive<PlayerData>({
id: "", id: "",
devSpeed: null, devSpeed: null,
name: "", name: "",
@ -51,24 +51,17 @@ const playerHandler: ProxyHandler<Record<PropertyKey, any>> = {
if (key === ProxyState || key === ProxyPath) { if (key === ProxyState || key === ProxyPath) {
return target[key]; return target[key];
} }
if (target[ProxyState][key] == undefined) {
return; const value = target[ProxyState][key];
} if (isPlainObject(value) && !(value instanceof Decimal)) {
if ( if (value !== target[key]?.[ProxyState]) {
isPlainObject(target[ProxyState][key]) &&
!(target[ProxyState][key] instanceof Decimal)
) {
if (target[ProxyState][key] !== target[key]?.[ProxyState]) {
const path = [...target[ProxyPath], key]; const path = [...target[ProxyPath], key];
target[key] = new Proxy( target[key] = new Proxy({ [ProxyState]: value, [ProxyPath]: path }, playerHandler);
{ [ProxyState]: target[ProxyState][key], [ProxyPath]: path },
playerHandler
);
} }
return target[key]; return target[key];
} }
return target[ProxyState][key]; return value;
}, },
set( set(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View file

@ -2,7 +2,7 @@ import modInfo from "@/data/modInfo.json";
import { Themes } from "@/data/themes"; import { Themes } from "@/data/themes";
import { globalBus } from "@/game/events"; import { globalBus } from "@/game/events";
import { hardReset } from "@/util/save"; import { hardReset } from "@/util/save";
import { reactive, watch } from "vue"; import { shallowReactive, watch } from "vue";
export interface Settings { export interface Settings {
active: string; active: string;
@ -12,7 +12,7 @@ export interface Settings {
unthrottled: boolean; unthrottled: boolean;
} }
const state = reactive<Partial<Settings>>({ const state = shallowReactive<Partial<Settings>>({
active: "", active: "",
saves: [], saves: [],
showTPS: true, showTPS: true,

View file

@ -1,4 +1,4 @@
import { reactive } from "vue"; import { shallowReactive } from "vue";
export interface Transient { export interface Transient {
lastTenTicks: number[]; lastTenTicks: number[];
@ -7,7 +7,7 @@ export interface Transient {
NaNReceiver?: Record<string, unknown>; NaNReceiver?: Record<string, unknown>;
} }
export default window.state = reactive<Transient>({ export default window.state = shallowReactive<Transient>({
lastTenTicks: [], lastTenTicks: [],
hasNaN: false, hasNaN: false,
NaNPath: [] NaNPath: []

View file

@ -20,6 +20,8 @@ type ComputableKeysOf<T> = Pick<
}[keyof T] }[keyof T]
>; >;
// TODO fix the typing of this function, such that casting isn't necessary and can be used to
// detect if a createX function is validly written
export function processComputable<T, S extends keyof ComputableKeysOf<T>>( export function processComputable<T, S extends keyof ComputableKeysOf<T>>(
obj: T, obj: T,
key: S key: S

View file

@ -28,7 +28,8 @@ export function createProxy<T extends Record<string, unknown>>(object: T): T {
"Creating a proxy out of a proxy! This may cause unintentional function calls and stack overflows." "Creating a proxy out of a proxy! This may cause unintentional function calls and stack overflows."
); );
} }
return new Proxy(object, layerHandler) as T; //return new Proxy(object, layerHandler) as T;
return object;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -39,6 +40,7 @@ const layerHandler: ProxyHandler<Record<PropertyKey, any>> = {
} }
if ( if (
target[key] == null ||
isRef(target[key]) || isRef(target[key]) ||
target[key].isProxy || target[key].isProxy ||
target[key] instanceof Decimal || target[key] instanceof Decimal ||

View file

@ -3,8 +3,8 @@ import player, { Player, PlayerData, stringifySave } from "@/game/player";
import settings, { loadSettings } from "@/game/settings"; import settings, { loadSettings } from "@/game/settings";
import Decimal from "./bignum"; import Decimal from "./bignum";
export function setupInitialStore(player: Partial<PlayerData> = {}): asserts player is Player { export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
Object.assign( return Object.assign(
{ {
id: `${modInfo.id}-0`, id: `${modInfo.id}-0`,
name: "Default Save", name: "Default Save",
@ -20,7 +20,7 @@ export function setupInitialStore(player: Partial<PlayerData> = {}): asserts pla
layers: {} layers: {}
}, },
player player
); ) as Player;
} }
export function save(): string { export function save(): string {
@ -54,8 +54,8 @@ export async function load(): Promise<void> {
export function newSave(): PlayerData { export function newSave(): PlayerData {
const id = getUniqueID(); const id = getUniqueID();
const player = { id }; const player = setupInitialStore({ id });
setupInitialStore(player); console.log(player);
localStorage.setItem(id, btoa(unescape(encodeURIComponent(stringifySave(player))))); localStorage.setItem(id, btoa(unescape(encodeURIComponent(stringifySave(player)))));
settings.saves.push(id); settings.saves.push(id);
@ -81,13 +81,15 @@ export async function loadSave(playerObj: Partial<PlayerData>): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
removeLayer(layers[layer]!); removeLayer(layers[layer]!);
} }
console.log(getInitialLayers(playerObj))
getInitialLayers(playerObj).forEach(layer => addLayer(layer, playerObj)); getInitialLayers(playerObj).forEach(layer => addLayer(layer, playerObj));
setupInitialStore(playerObj); playerObj = setupInitialStore(playerObj);
if (playerObj.offlineProd && playerObj.time) { if (playerObj.offlineProd && playerObj.time) {
if (playerObj.offlineTime == undefined) playerObj.offlineTime = new Decimal(0); if (playerObj.offlineTime == undefined) playerObj.offlineTime = new Decimal(0);
playerObj.offlineTime = playerObj.offlineTime.add((Date.now() - playerObj.time) / 1000); playerObj.offlineTime = Decimal.add(
playerObj.offlineTime,
(Date.now() - playerObj.time) / 1000
);
} }
playerObj.time = Date.now(); playerObj.time = Date.now();
if (playerObj.modVersion !== modInfo.versionNumber) { if (playerObj.modVersion !== modInfo.versionNumber) {
@ -95,7 +97,7 @@ export async function loadSave(playerObj: Partial<PlayerData>): Promise<void> {
} }
Object.assign(player, playerObj); Object.assign(player, playerObj);
settings.active = playerObj.id; settings.active = player.id;
} }
setInterval(() => { setInterval(() => {

View file

@ -5,7 +5,21 @@ import {
Component as ComponentKey, Component as ComponentKey,
GenericComponent GenericComponent
} from "@/features/feature"; } from "@/features/feature";
import { Component, DefineComponent, defineComponent, h, reactive, Ref } from "vue"; import { isArray } from "@vue/shared";
import {
Component,
computed,
ComputedRef,
DefineComponent,
defineComponent,
h,
PropType,
ref,
Ref,
unref,
WritableComputedRef
} from "vue";
import { ProcessedComputable } from "./computed";
export function coerceComponent(component: CoercableComponent, defaultWrapper = "span"): Component { export function coerceComponent(component: CoercableComponent, defaultWrapper = "span"): Component {
if (typeof component === "string") { if (typeof component === "string") {
@ -78,24 +92,17 @@ export function setupHoldToClick(
stop: VoidFunction; stop: VoidFunction;
handleHolding: VoidFunction; handleHolding: VoidFunction;
} { } {
const state = reactive<{ const interval = ref<null | number>(null);
interval: null | number;
time: number;
}>({
interval: null,
time: 0
});
function start() { function start() {
if (!state.interval) { if (!interval.value) {
state.interval = setInterval(handleHolding, 250); interval.value = setInterval(handleHolding, 250);
} }
} }
function stop() { function stop() {
if (state.interval) { if (interval.value) {
clearInterval(state.interval); clearInterval(interval.value);
state.interval = null; interval.value = null;
state.time = 0;
} }
} }
function handleHolding() { function handleHolding() {
@ -108,3 +115,47 @@ export function setupHoldToClick(
return { start, stop, handleHolding }; return { start, stop, handleHolding };
} }
export function computeComponent(
component: Ref<ProcessedComputable<CoercableComponent>>
): ComputedRef<Component> {
return computed(() => {
return coerceComponent(unref(unref<ProcessedComputable<CoercableComponent>>(component)));
});
}
export function computeOptionalComponent(
component: Ref<ProcessedComputable<CoercableComponent | undefined> | undefined>
): ComputedRef<Component | undefined> {
return computed(() => {
let currComponent = unref<ProcessedComputable<CoercableComponent | undefined> | undefined>(
component
);
if (currComponent == null) return;
currComponent = unref(currComponent);
return currComponent == null ? undefined : coerceComponent(currComponent);
});
}
export function wrapRef<T>(ref: Ref<ProcessedComputable<T>>): ComputedRef<T> {
return computed(() => unwrapRef(ref));
}
export function unwrapRef<T>(ref: Ref<ProcessedComputable<T>>): T {
return unref(unref<ProcessedComputable<T>>(ref));
}
type PropTypes =
| typeof Boolean
| typeof String
| typeof Number
| typeof Function
| typeof Object
| typeof Array;
// TODO Unfortunately, the typescript engine gives up on typing completely when you use this method,
// Even though it has the same typing as when doing it manually
export function processedPropType<T>(...types: PropTypes[]): PropType<ProcessedComputable<T>> {
if (!types.includes(Object)) {
types.push(Object);
}
return types as PropType<ProcessedComputable<T>>;
}