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-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"vue/script-setup-uses-vars": "warn",
"vue/no-mutating-props": "off"
"vue/no-mutating-props": "off",
"vue/multi-word-component-names": "off"
},
globals: {
defineProps: "readonly",

38
package-lock.json generated
View file

@ -14,10 +14,10 @@
"vue": "^3.2.26",
"vue-next-select": "^2.10.2",
"vue-panzoom": "^1.1.6",
"vue-sortable": "github:Netbel/vue-sortable#master-fix",
"vue-textarea-autosize": "^1.1.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": {
"@ivanv/vue-collapse-transition": "^1.0.2",
@ -11483,14 +11483,6 @@
"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": {
"version": "4.1.3",
"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",
"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": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz",
@ -20849,13 +20852,6 @@
"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": {
"version": "4.1.3",
"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": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz",

View file

@ -14,10 +14,10 @@
"vue": "^3.2.26",
"vue-next-select": "^2.10.2",
"vue-panzoom": "^1.1.6",
"vue-sortable": "github:Netbel/vue-sortable#master-fix",
"vue-textarea-autosize": "^1.1.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": {
"@ivanv/vue-collapse-transition": "^1.0.2",

View file

@ -21,20 +21,46 @@
</Tooltip>
</template>
<script setup lang="ts">
import { GenericAchievement } from "@/features/achievement";
import { FeatureComponent } from "@/features/feature";
<script lang="ts">
import { CoercableComponent, Visibility } from "@/features/feature";
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 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(() => {
const display = props.display.value;
return display && coerceComponent(display);
return {
component: computed(() => {
return display.value && coerceComponent(display.value);
}),
LinkNode,
MarkNode,
Visibility
};
}
});
</script>

View file

@ -30,38 +30,74 @@
</div>
</template>
<script setup lang="ts">
import { Direction, GenericBar } from "@/features/bar";
import { FeatureComponent, Visibility } from "@/features/feature";
import Decimal from "@/util/bignum";
<script lang="ts">
import { Direction } from "@/features/bar";
import { CoercableComponent, Visibility } from "@/features/feature";
import Decimal, { DecimalSource } from "@/util/bignum";
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 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(() => {
let progress =
props.progress.value instanceof Decimal
? props.progress.value.toNumber()
: Number(props.progress.value);
return (1 - Math.min(Math.max(progress, 0), 1)) * 100;
let progressNumber =
progress.value instanceof Decimal
? progress.value.toNumber()
: Number(progress.value);
return (1 - Math.min(Math.max(progressNumber, 0), 1)) * 100;
});
const barStyle = computed(() => {
const barStyle: Partial<CSSProperties> = {
width: unref(props.width) + 0.5 + "px",
height: unref(props.height) + 0.5 + "px"
width: unref(width) + 0.5 + "px",
height: unref(height) + 0.5 + "px"
};
switch (unref(props.direction)) {
switch (unref(direction)) {
case Direction.Up:
barStyle.clipPath = `inset(${normalizedProgress.value}% 0% 0% 0%)`;
barStyle.width = unref(props.width) + 1 + "px";
barStyle.width = unref(width) + 1 + "px";
break;
case Direction.Down:
barStyle.clipPath = `inset(0% 0% ${normalizedProgress.value}% 0%)`;
barStyle.width = unref(props.width) + 1 + "px";
barStyle.width = unref(width) + 1 + "px";
break;
case Direction.Right:
barStyle.clipPath = `inset(0% ${normalizedProgress.value}% 0% 0%)`;
@ -77,8 +113,19 @@ const barStyle = computed(() => {
});
const component = computed(() => {
const display = props.display.value;
return display && coerceComponent(display);
const currDisplay = unref(display);
return currDisplay && coerceComponent(unref(currDisplay));
});
return {
normalizedProgress,
barStyle,
component,
MarkNode,
LinkNode,
Visibility
};
}
});
</script>

View file

@ -18,62 +18,114 @@
{{ buttonText }}
</button>
<component v-if="component" :is="component" />
<default-challenge-display v-else :id="id" />
<MarkNode :mark="mark" />
<LinkNode :id="id" />
</div>
</template>
<script setup lang="tsx">
<script lang="tsx">
import { GenericChallenge } from "@/features/challenge";
import { FeatureComponent, Visibility } from "@/features/feature";
import { StyleValue, Visibility } from "@/features/feature";
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: {
active: {
type: Boolean,
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
}
},
setup(props) {
const { active, maxed, canComplete, display } = toRefs(props);
const buttonText = computed(() => {
if (props.active.value) {
return props.canComplete.value ? "Finish" : "Exit Early";
if (active.value) {
return canComplete.value ? "Finish" : "Exit Early";
}
if (props.maxed.value) {
if (maxed.value) {
return "Completed";
}
return "Start";
});
const component = computed(() => {
const display = props.display.value;
if (display == null) {
const currDisplay = display.value;
if (currDisplay == null) {
return null;
}
if (isCoercableComponent(display)) {
return coerceComponent(display);
if (isCoercableComponent(currDisplay)) {
return coerceComponent(currDisplay);
}
return (
<span>
<template v-if={display.title}>
<template v-if={currDisplay.title}>
{/* 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>
<component v-is={coerceComponent(display.description, "div")} />
<div v-if={display.goal}>
<component v-is={coerceComponent(currDisplay.description, "div")} />
<div v-if={currDisplay.goal}>
<br />
{/* 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 v-if={display.reward}>
<div v-if={currDisplay.reward}>
<br />
{/* 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 v-if={display.effectDisplay}>
Currently: {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
<component v-is={coerceComponent(display.effectDisplay!)} />
<div v-if={currDisplay.effectDisplay}>
Currently:{" "}
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
<component v-is={coerceComponent(currDisplay.effectDisplay!)} />
</div>
</span>
);
});
return {
buttonText,
component,
MarkNode,
LinkNode,
Visibility
};
}
});
</script>
<style scoped>

View file

@ -13,7 +13,7 @@
:class="{
feature: true,
clickable: true,
can: props.canClick,
can: canClick,
locked: !canClick,
small,
...classes
@ -26,36 +26,73 @@
</div>
</template>
<script setup lang="tsx">
<script lang="tsx">
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 { computed, toRefs, unref } from "vue";
import { computed, defineComponent, PropType, toRefs, unref, UnwrapRef } from "vue";
import LinkNode from "../system/LinkNode.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 display = unref(props.display);
if (display == null) {
const currDisplay = unref(display);
if (currDisplay == null) {
return null;
}
if (isCoercableComponent(display)) {
return coerceComponent(display);
if (isCoercableComponent(currDisplay)) {
return coerceComponent(currDisplay);
}
return (
<span>
<div v-if={display.title}>
<div v-if={currDisplay.title}>
{/* 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>
<component v-is={coerceComponent(display.description, "div")} />
<component v-is={coerceComponent(currDisplay.description, "div")} />
</span>
);
});
const { start, stop } = setupHoldToClick(props.onClick, props.onHold);
const { start, stop } = setupHoldToClick(onClick, onHold);
return {
start,
stop,
component,
LinkNode,
MarkNode,
Visibility
};
}
});
</script>
<style scoped>

View file

@ -13,13 +13,32 @@
</template>
<script lang="ts">
import { FeatureComponent, Visibility } from "@/features/feature";
import { GenericGrid } from "@/features/grid";
import { defineComponent } from "vue";
import GridCell from "./GridCell.vue";
import { Visibility } from "@/features/feature";
import { GridCell } from "@/features/grid";
import { defineComponent, PropType } from "vue";
import GridCellVue from "./GridCell.vue";
// https://github.com/thepaperpilot/The-Modding-Tree-X/issues/1
export default defineComponent(function Grid(props: FeatureComponent<GenericGrid>) {
return { ...props, GridCell, Visibility };
export default defineComponent({
props: {
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>

View file

@ -19,22 +19,56 @@
</button>
</template>
<script setup lang="ts">
import { Visibility } from "@/features/feature";
import { GridCell } from "@/features/grid";
<script lang="ts">
import { CoercableComponent, StyleValue, Visibility } from "@/features/feature";
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";
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 title = unref(props.title);
return title && coerceComponent(title);
const currTitle = unref(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>
<style scoped>

View file

@ -23,21 +23,57 @@
</div>
</template>
<script setup lang="ts">
<script lang="ts">
import themes from "@/data/themes";
import { FeatureComponent, Visibility } from "@/features/feature";
import { GenericInfobox } from "@/features/infobox";
import { CoercableComponent, Visibility } from "@/features/feature";
import settings from "@/game/settings";
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 { 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 bodyComponent = computed(() => coerceComponent(unref(props.display)));
const titleComponent = computed(() => title.value && coerceComponent(title.value));
const bodyComponent = computed(() => coerceComponent(display.value));
const stacked = computed(() => themes[settings.theme].stackedInfoboxes);
return {
titleComponent,
bodyComponent,
stacked,
LinkNode,
CollapseTransition,
Visibility
};
}
});
</script>
<style scoped>

View file

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

View file

@ -10,37 +10,67 @@
</div>
</template>
<script setup lang="tsx">
import { FeatureComponent, Visibility } from "@/features/feature";
<script lang="tsx">
import { StyleValue, Visibility } from "@/features/feature";
import { GenericMilestone } from "@/features/milestone";
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";
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 display = props.display.value;
if (display == null) {
const currDisplay = display.value;
if (currDisplay == null) {
return null;
}
if (isCoercableComponent(display)) {
return coerceComponent(display);
if (isCoercableComponent(currDisplay)) {
return coerceComponent(currDisplay);
}
return (
<span>
<component v-is={coerceComponent(display.requirement, "h3")} />
<div v-if={display.effectDisplay}>
<component v-is={coerceComponent(currDisplay.requirement, "h3")} />
<div v-if={currDisplay.effectDisplay}>
{/* 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 v-if={display.optionsDisplay}>
<div v-if={currDisplay.optionsDisplay}>
{/* 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>
</span>
);
});
return {
component,
LinkNode,
Visibility
};
}
});
</script>
<style scoped>

View file

@ -7,6 +7,7 @@ import { CoercableComponent } from "@/features/feature";
import { coerceComponent } from "@/util/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));
</script>

View file

@ -1,30 +1,50 @@
<template>
<button
@click="emits('selectTab')"
@click="selectTab"
class="tabButton"
:style="style"
:style="unref(style)"
:class="{
active,
...classes
...unref(classes)
}"
>
<component :is="component" />
</button>
</template>
<script setup lang="ts">
import { FeatureComponent } from "@/features/feature";
import { GenericTabButton } from "@/features/tabFamily";
import { coerceComponent } from "@/util/vue";
import { computed, toRefs } from "vue";
<script lang="ts">
import { CoercableComponent, StyleValue } from "@/features/feature";
import { ProcessedComputable } from "@/util/computed";
import { computeComponent } from "@/util/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<{
(e: "selectTab"): void;
}>();
const component = computeComponent(display);
const component = computed(() => coerceComponent(props.display.value));
function selectTab() {
emit("selectTab");
}
return {
selectTab,
component,
unref
};
}
});
</script>
<style scoped>

View file

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

View file

@ -20,47 +20,97 @@
</button>
</template>
<script setup lang="tsx">
import { FeatureComponent, Visibility } from "@/features/feature";
import { displayResource } from "@/features/resource";
<script lang="tsx">
import { StyleValue, Visibility } from "@/features/feature";
import { displayResource, Resource } from "@/features/resource";
import { GenericUpgrade } from "@/features/upgrade";
import { DecimalSource } from "@/lib/break_eternity";
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 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 display = unref(props.display);
if (display == null) {
const currDisplay = display.value;
if (currDisplay == null) {
return null;
}
if (isCoercableComponent(display)) {
return coerceComponent(display);
if (isCoercableComponent(currDisplay)) {
return coerceComponent(currDisplay);
}
return (
<span>
<div v-if={display.title}>
<div v-if={currDisplay.title}>
{/* 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>
<component v-is={coerceComponent(display.description, "div")} />
<div v-if={display.effectDisplay}>
<component v-is={coerceComponent(currDisplay.description, "div")} />
<div v-if={currDisplay.effectDisplay}>
<br />
{/* 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>
<template v-if={unref(props.resource) != null && unref(props.cost) != null}>
<template v-if={resource.value != null && cost.value != null}>
<br />
{/* 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 */}
{unref(props.resource)!.displayName}
{resource.value.displayName}
</template>
</span>
);
});
return {
component,
LinkNode,
MarkNode,
Visibility
};
}
});
</script>
<style scoped>

View file

@ -58,7 +58,8 @@ import panZoom from "vue-panzoom";
import BoardLinkVue from "./BoardLink.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 dragged = ref({ x: 0, y: 0 });

View file

@ -14,11 +14,10 @@
import { BoardNodeLink } from "@/features/board";
import { computed, toRefs, unref } from "vue";
const props = toRefs(
defineProps<{
const _props = defineProps<{
link: BoardNodeLink;
}>()
);
}>();
const props = toRefs(_props);
const startPosition = computed(() => {
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 props = toRefs(
defineProps<{
const _props = defineProps<{
node: BoardNode;
nodeType: GenericNodeType;
dragging?: BoardNode;
@ -195,8 +194,8 @@ const props = toRefs(
receivingNode?: boolean;
selectedNode?: BoardNode | null;
selectedAction?: GenericBoardNodeAction | null;
}>()
);
}>();
const props = toRefs(_props);
const emit = defineEmits<{
(e: "mouseDown", event: MouseEvent | TouchEvent, node: number, isDraggable: boolean): void;
(e: "endDragging", node: number): void;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,14 +11,13 @@
import { computed, toRefs, unref } from "vue";
import Tooltip from "../system/Tooltip.vue";
const props = toRefs(
defineProps<{
const _props = defineProps<{
title?: string;
modelValue?: number;
min?: number;
max?: number;
}>()
);
}>();
const props = toRefs(_props);
const emit = defineEmits<{
(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 VueTextareaAutosize from "vue-textarea-autosize";
const props = toRefs(
defineProps<{
const _props = defineProps<{
title?: CoercableComponent;
modelValue?: string;
textArea?: boolean;
placeholder?: string;
maxHeight?: number;
}>()
);
}>();
const props = toRefs(_props);
const emit = defineEmits<{
(e: "update:modelValue", value: string): void;
(e: "submit"): void;

View file

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

View file

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

View file

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

View file

@ -64,7 +64,8 @@ import { computed, ref, toRefs, unref } from "vue";
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);

View file

@ -1,51 +1,82 @@
<template>
<div class="layer-container">
<button v-if="showGoBack" class="goBack" @click="goBack"></button>
<button class="layer-tab minimized" v-if="minimized" @click="minimized = false">
<div>{{ name }}</div>
<button class="layer-tab minimized" v-if="minimized.value" @click="minimized.value = false">
<div>{{ unref(name) }}</div>
</button>
<div class="layer-tab" :style="style" :class="classes" v-else>
<Links v-if="links" :links="links">
<div class="layer-tab" :style="unref(style)" :class="unref(classes)" v-else>
<Links v-if="links" :links="unref(links)">
<component :is="component" />
</Links>
<component v-else :is="component" />
</div>
<button v-if="minimizable" class="minimize" @click="minimized = true"></button>
<button v-if="unref(minimizable)" class="minimize" @click="minimized.value = true">
</button>
</div>
</template>
<script setup lang="ts">
<script lang="ts">
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 { CoercableComponent, PersistentRef, StyleValue } from "@/features/feature";
import { Link } from "@/features/links";
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(
defineProps<
FeatureComponent<GenericLayer> & {
index: number;
tab: () => HTMLElement | undefined;
}
>()
);
export default defineComponent({
components: { Links },
props: {
index: {
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(
() => modInfo.allowGoBack && unref(props.index) > 0 && !props.minimized.value
() => modInfo.allowGoBack && unref(index) > 0 && !minimized.value
);
function goBack() {
player.tabs = player.tabs.slice(0, unref(props.index));
}
nextTick(() => updateTab(props.minimized.value, props.minWidth.value));
watch([props.minimized, props.minWidth], ([minimized, minWidth]) => updateTab(minimized, minWidth));
nextTick(() => updateTab(minimized.value, unref(minWidth.value)));
watch([minimized, wrapRef(minWidth)], ([minimized, minWidth]) =>
updateTab(minimized, minWidth)
);
function updateTab(minimized: boolean, minWidth: number) {
const tabValue = props.tab.value();
const tabValue = tab.value();
if (tabValue != undefined) {
if (minimized) {
tabValue.style.flexGrow = "0";
@ -62,6 +93,15 @@ function updateTab(minimized: boolean, minWidth: number) {
}
}
}
return {
component,
showGoBack,
unref,
goBack
};
}
});
</script>
<style scoped>

View file

@ -12,13 +12,12 @@
import { Link, LinkNode } from "@/features/links";
import { computed, toRefs, unref } from "vue";
const props = toRefs(
defineProps<{
const _props = defineProps<{
link: Link;
startNode: LinkNode;
endNode: LinkNode;
}>()
);
}>();
const props = toRefs(_props);
const startPosition = computed(() => {
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 { 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 unregister = inject(UnregisterLinkNodeInjectionKey);

View file

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

View file

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

View file

@ -15,7 +15,7 @@
<br />
<div>
<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 }}
</a>
</div>
@ -48,14 +48,14 @@ import modInfo from "@/data/modInfo.json";
import player from "@/game/player";
import state from "@/game/state";
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 SavesManager from "./SavesManager.vue";
const { discordName, discordLink } = modInfo;
const autosave = toRef(player, "autosave");
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 property = computed(() => state.NaNPath?.slice(-1)[0]);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -57,28 +57,28 @@ export const main = createLayer({
id: "main",
name: "Tree",
links: tree.links,
display() {
return (
display: (
<template>
<div v-if={player.devSpeed === 0}>Game Paused</div>
<div v-else-if={player.devSpeed && player.devSpeed !== 1}>
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
Dev Speed: {format(player.devSpeed!)}x
<div v-show={player.devSpeed === 0}>Game Paused</div>
<div v-show={player.devSpeed && player.devSpeed !== 1}>
Dev Speed: {format(player.devSpeed || 0)}x
</div>
<div v-if={player.offlineTime != undefined}>
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
Offline Time: {formatTime(player.offlineTime!)}
<div v-show={player.offlineTime != undefined}>
Offline Time: {formatTime(player.offlineTime || 0)}
</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>
<span v-if={Decimal.lt(points.value, "1e1e6")}> points</span>
<span v-show={Decimal.lt(points.value, "1e1e6")}> points</span>
</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)
</div>
<Spacer />
<Modal v-model={showModal}>
<Modal
modelValue={showModal.value}
onUpdate:modelValue={value => (showModal.value = value)}
>
<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" />
@ -89,8 +89,7 @@ export const main = createLayer({
</Modal>
{render(tree)}
</template>
);
},
),
points,
best,
total,

View file

@ -117,12 +117,16 @@ globalBus.on("addLayer", layer => {
achievement[PersistentState].value = true;
achievement.onComplete?.();
if (achievement.display) {
const display = unref(achievement.display);
const Display = coerceComponent(unref(achievement.display));
toast.info(
<template>
<h2>Milestone earned!</h2>
<div>{coerceComponent(display)}</div>
</template>
<div>
<h3>Achievement earned!</h3>
<div>
{/* 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 { ProcessedComputable } from "@/util/computed";
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 SetupPersistence = Symbol("SetupPersistence");
export const DefaultValue = Symbol("DefaultValue");
export const Component = Symbol("Component");
@ -27,30 +26,19 @@ export type StyleValue = string | CSSProperties | Array<string | CSSProperties>;
export type Persistent<T extends State = State> = {
[PersistentState]: Ref<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,
// this type can probably be safely removed
// eslint-disable-next-line @typescript-eslint/no-explicit-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<
{
[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>;
@ -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> {
const persistent = isRef(defaultValue) ? defaultValue : (ref(defaultValue) as Ref<T>);
(persistent as unknown as PersistentRef<T>)[DefaultValue] = isRef(defaultValue)
? defaultValue.value
: defaultValue;
(persistent as unknown as PersistentRef<T>)[SetupPersistence] = function () {
return persistent;
};
return persistent as unknown as PersistentRef<T>;
const persistent = (
isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
) as PersistentRef<T>;
persistent[PersistentState] = persistent;
persistent[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue;
return persistent as PersistentRef<T>;
}
export function makePersistent<T extends State>(
@ -92,18 +79,8 @@ export function makePersistent<T extends State>(
const persistent = obj as Partial<Persistent<T>>;
const state = ref(defaultValue) as Ref<T>;
Object.defineProperty(persistent, PersistentState, {
get: () => {
return state.value;
},
set: (val: T) => {
state.value = val;
}
});
persistent[PersistentState] = state;
persistent[DefaultValue] = isRef(defaultValue) ? (defaultValue.value as T) : defaultValue;
persistent[SetupPersistence] = function () {
return state;
};
}
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>) => {
const handleObject = (
obj: Record<string, unknown>,
persistentState: Record<string, unknown>
): boolean => {
const handleObject = (obj: Record<string, unknown>, path: string[] = []): boolean => {
let foundPersistent = false;
Object.keys(obj).forEach(key => {
const value = obj[key];
if (value && typeof value === "object") {
if (SetupPersistence in value) {
if (PersistentState in value) {
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];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
persistentState[key] = (value as PersistentRef | Persistent)[
SetupPersistence
]();
// Add ref to save data
persistentState[key] = (value as Persistent)[PersistentState];
// Load previously saved value
if (savedValue != null) {
(persistentState[key] as Ref<unknown>).value = savedValue;
}
} else if (!(value instanceof Decimal)) {
if (typeof persistentState[key] !== "object") {
persistentState[key] = {};
}
const foundPersistentInChild = handleObject(
value as Record<string, unknown>,
persistentState[key] as Record<string, unknown>
);
// Continue traversing
const foundPersistentInChild = handleObject(value as Record<string, unknown>, [
...path,
key
]);
// Show warning for persistent values inside arrays
// TODO handle arrays better
if (foundPersistentInChild) {
if (isArray(value)) {
console.warn(
@ -177,5 +161,5 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
});
return foundPersistent;
};
handleObject(layer, saveData);
handleObject(layer);
});

View file

@ -21,30 +21,44 @@ import {
ProcessedComputable
} from "@/util/computed";
import { createProxy, Proxied } from "@/util/proxies";
import { unref } from "vue";
import { computed, unref } from "vue";
export const GridType = Symbol("Grid");
export type CellComputable<T> = Computable<T> | ((id: string | number, state: State) => T);
function createGridProxy(object: Record<string, unknown>): Record<string | number, GridCell> {
if (object.isProxy) {
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>>;
function createGridProxy(grid: GenericGrid): Record<string | number, GridCell> {
return new Proxy({}, getGridHandler(grid)) as Proxied<Record<string | number, GridCell>>;
}
// 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) {
if (key === "isProxy") {
return true;
}
if (typeof key === "symbol") {
return (grid as never)[key];
}
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];
@ -52,42 +66,54 @@ const gridHandler: ProxyHandler<Record<PropertyKey, any>> = {
set(target, key, value) {
console.warn("Cannot set grid cells", target, key, value);
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 {
// 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") {
return true;
}
let prop = target[key];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let prop = (target as any)[key];
if (isFunction(prop)) {
return () => prop.call(receiver, id, target.getState(id));
}
if (prop != undefined || key.slice == undefined) {
if (prop != undefined || typeof key === "symbol") {
return prop;
}
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)) {
return prop.call(receiver, id, target.getState(id));
} else if (prop != undefined) {
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)) {
return () => prop.call(receiver, id, target.getState(id));
} else if (prop != undefined) {
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
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>> {
id: 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;
cells: Record<string | number, GridCell>;
type: typeof GridType;
@ -176,7 +202,6 @@ export function createGrid<T extends GridOptions>(options: T & ThisType<Grid<T>>
grid.id = getUniqueID("grid-");
grid[Component] = GridComponent;
grid.cells = createGridProxy(grid as unknown as Record<string, unknown>);
grid.getID = function (this: GenericGrid, cell: string | number) {
return grid.id + "-" + cell;
};
@ -205,5 +230,6 @@ export function createGrid<T extends GridOptions>(options: T & ThisType<Grid<T>>
processComputable(grid as T, "getDisplay");
const proxy = createProxy(grid as unknown as Grid<T>);
(proxy as GenericGrid).cells = createGridProxy(proxy as GenericGrid);
return proxy;
}

View file

@ -146,13 +146,16 @@ globalBus.on("addLayer", layer => {
milestone.onComplete?.();
if (milestone.display) {
const display = unref(milestone.display);
toast.info(
<template>
<h2>Milestone earned!</h2>
<div>
{coerceComponent(
const Display = coerceComponent(
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>
</template>
);

View file

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

View file

@ -162,7 +162,7 @@ export function createTree<T extends TreeOptions>(options: T & ThisType<Tree<T>>
proxy.isResetting.value = false;
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");
setDefault(tree, "visibility", Visibility.Visible);

View file

@ -47,17 +47,17 @@ function update() {
// Add offline time if any
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);
}
if (player.offlineTime.gt(0) && player.devSpeed !== 0) {
const offlineDiff = Decimal.max(player.offlineTime.div(10), diff);
player.offlineTime = player.offlineTime.sub(offlineDiff);
if (Decimal.gt(player.offlineTime, 0) && player.devSpeed !== 0) {
const offlineDiff = Decimal.div(player.offlineTime, 10).max(diff);
player.offlineTime = Decimal.sub(player.offlineTime, offlineDiff);
diff = diff.add(offlineDiff);
} 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;
}
}
@ -74,7 +74,7 @@ function update() {
if (diff.eq(0)) {
return;
}
player.timePlayed = player.timePlayed.add(diff);
player.timePlayed = Decimal.add(player.timePlayed, diff);
globalBus.emit("update", diff, trueDiff);
if (settings.unthrottled) {

View file

@ -1,7 +1,7 @@
import Decimal, { DecimalSource } from "@/util/bignum";
import { isPlainObject } from "@/util/common";
import { ProxiedWithState, ProxyPath, ProxyState } from "@/util/proxies";
import { reactive, unref } from "vue";
import { shallowReactive, unref } from "vue";
import transientState from "./state";
export interface PlayerData {
@ -12,8 +12,8 @@ export interface PlayerData {
time: number;
autosave: boolean;
offlineProd: boolean;
offlineTime: Decimal | null;
timePlayed: Decimal;
offlineTime: DecimalSource | null;
timePlayed: DecimalSource;
keepGoing: boolean;
minimized: Record<string, boolean>;
modID: string;
@ -23,7 +23,7 @@ export interface PlayerData {
export type Player = ProxiedWithState<PlayerData>;
const state = reactive<PlayerData>({
const state = shallowReactive<PlayerData>({
id: "",
devSpeed: null,
name: "",
@ -51,24 +51,17 @@ const playerHandler: ProxyHandler<Record<PropertyKey, any>> = {
if (key === ProxyState || key === ProxyPath) {
return target[key];
}
if (target[ProxyState][key] == undefined) {
return;
}
if (
isPlainObject(target[ProxyState][key]) &&
!(target[ProxyState][key] instanceof Decimal)
) {
if (target[ProxyState][key] !== target[key]?.[ProxyState]) {
const value = target[ProxyState][key];
if (isPlainObject(value) && !(value instanceof Decimal)) {
if (value !== target[key]?.[ProxyState]) {
const path = [...target[ProxyPath], key];
target[key] = new Proxy(
{ [ProxyState]: target[ProxyState][key], [ProxyPath]: path },
playerHandler
);
target[key] = new Proxy({ [ProxyState]: value, [ProxyPath]: path }, playerHandler);
}
return target[key];
}
return target[ProxyState][key];
return value;
},
set(
// 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 { globalBus } from "@/game/events";
import { hardReset } from "@/util/save";
import { reactive, watch } from "vue";
import { shallowReactive, watch } from "vue";
export interface Settings {
active: string;
@ -12,7 +12,7 @@ export interface Settings {
unthrottled: boolean;
}
const state = reactive<Partial<Settings>>({
const state = shallowReactive<Partial<Settings>>({
active: "",
saves: [],
showTPS: true,

View file

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

View file

@ -20,6 +20,8 @@ type ComputableKeysOf<T> = Pick<
}[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>>(
obj: T,
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."
);
}
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
@ -39,6 +40,7 @@ const layerHandler: ProxyHandler<Record<PropertyKey, any>> = {
}
if (
target[key] == null ||
isRef(target[key]) ||
target[key].isProxy ||
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 Decimal from "./bignum";
export function setupInitialStore(player: Partial<PlayerData> = {}): asserts player is Player {
Object.assign(
export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
return Object.assign(
{
id: `${modInfo.id}-0`,
name: "Default Save",
@ -20,7 +20,7 @@ export function setupInitialStore(player: Partial<PlayerData> = {}): asserts pla
layers: {}
},
player
);
) as Player;
}
export function save(): string {
@ -54,8 +54,8 @@ export async function load(): Promise<void> {
export function newSave(): PlayerData {
const id = getUniqueID();
const player = { id };
setupInitialStore(player);
const player = setupInitialStore({ id });
console.log(player);
localStorage.setItem(id, btoa(unescape(encodeURIComponent(stringifySave(player)))));
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
removeLayer(layers[layer]!);
}
console.log(getInitialLayers(playerObj))
getInitialLayers(playerObj).forEach(layer => addLayer(layer, playerObj));
setupInitialStore(playerObj);
playerObj = setupInitialStore(playerObj);
if (playerObj.offlineProd && playerObj.time) {
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();
if (playerObj.modVersion !== modInfo.versionNumber) {
@ -95,7 +97,7 @@ export async function loadSave(playerObj: Partial<PlayerData>): Promise<void> {
}
Object.assign(player, playerObj);
settings.active = playerObj.id;
settings.active = player.id;
}
setInterval(() => {

View file

@ -5,7 +5,21 @@ import {
Component as ComponentKey,
GenericComponent
} 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 {
if (typeof component === "string") {
@ -78,24 +92,17 @@ export function setupHoldToClick(
stop: VoidFunction;
handleHolding: VoidFunction;
} {
const state = reactive<{
interval: null | number;
time: number;
}>({
interval: null,
time: 0
});
const interval = ref<null | number>(null);
function start() {
if (!state.interval) {
state.interval = setInterval(handleHolding, 250);
if (!interval.value) {
interval.value = setInterval(handleHolding, 250);
}
}
function stop() {
if (state.interval) {
clearInterval(state.interval);
state.interval = null;
state.time = 0;
if (interval.value) {
clearInterval(interval.value);
interval.value = null;
}
}
function handleHolding() {
@ -108,3 +115,47 @@ export function setupHoldToClick(
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>>;
}