Fixing more issues
This commit is contained in:
parent
15a460bf42
commit
90e49e196f
60 changed files with 1380 additions and 781 deletions
|
@ -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
38
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -30,55 +30,102 @@
|
||||||
</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%)`;
|
||||||
break;
|
break;
|
||||||
case Direction.Left:
|
case Direction.Left:
|
||||||
barStyle.clipPath = `inset(0% 0% 0% ${normalizedProgress.value} + '%)`;
|
barStyle.clipPath = `inset(0% 0% 0% ${normalizedProgress.value} + '%)`;
|
||||||
break;
|
break;
|
||||||
case Direction.Default:
|
case Direction.Default:
|
||||||
barStyle.clipPath = "inset(0% 50% 0% 0%)";
|
barStyle.clipPath = "inset(0% 50% 0% 0%)";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
return barStyle;
|
||||||
|
});
|
||||||
|
|
||||||
|
const component = computed(() => {
|
||||||
|
const currDisplay = unref(display);
|
||||||
|
return currDisplay && coerceComponent(unref(currDisplay));
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
normalizedProgress,
|
||||||
|
barStyle,
|
||||||
|
component,
|
||||||
|
MarkNode,
|
||||||
|
LinkNode,
|
||||||
|
Visibility
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return barStyle;
|
|
||||||
});
|
|
||||||
|
|
||||||
const component = computed(() => {
|
|
||||||
const display = props.display.value;
|
|
||||||
return display && coerceComponent(display);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -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: {
|
||||||
|
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(() => {
|
const buttonText = computed(() => {
|
||||||
if (props.active.value) {
|
if (active.value) {
|
||||||
return props.canComplete.value ? "Finish" : "Exit Early";
|
return canComplete.value ? "Finish" : "Exit Early";
|
||||||
}
|
}
|
||||||
if (props.maxed.value) {
|
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(currDisplay)) {
|
||||||
|
return coerceComponent(currDisplay);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<template v-if={currDisplay.title}>
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||||
|
<component v-is={coerceComponent(currDisplay.title!, "h3")} />
|
||||||
|
</template>
|
||||||
|
<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(currDisplay.goal!)} />
|
||||||
|
</div>
|
||||||
|
<div v-if={currDisplay.reward}>
|
||||||
|
<br />
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||||
|
Reward: <component v-is={coerceComponent(currDisplay.reward!)} />
|
||||||
|
</div>
|
||||||
|
<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
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (isCoercableComponent(display)) {
|
|
||||||
return coerceComponent(display);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<template v-if={display.title}>
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
<component v-is={coerceComponent(display.title!, "h3")} />
|
|
||||||
</template>
|
|
||||||
<component v-is={coerceComponent(display.description, "div")} />
|
|
||||||
<div v-if={display.goal}>
|
|
||||||
<br />
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
Goal: <component v-is={coerceComponent(display.goal!)} />
|
|
||||||
</div>
|
|
||||||
<div v-if={display.reward}>
|
|
||||||
<br />
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
Reward: <component v-is={coerceComponent(display.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>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -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(currDisplay)) {
|
||||||
|
return coerceComponent(currDisplay);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<div v-if={currDisplay.title}>
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||||
|
<component v-is={coerceComponent(currDisplay.title!, "h2")} />
|
||||||
|
</div>
|
||||||
|
<component v-is={coerceComponent(currDisplay.description, "div")} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const { start, stop } = setupHoldToClick(onClick, onHold);
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
component,
|
||||||
|
LinkNode,
|
||||||
|
MarkNode,
|
||||||
|
Visibility
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (isCoercableComponent(display)) {
|
|
||||||
return coerceComponent(display);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<div v-if={display.title}>
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
<component v-is={coerceComponent(display.title!, "h2")} />
|
|
||||||
</div>
|
|
||||||
<component v-is={coerceComponent(display.description, "div")} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { start, stop } = setupHoldToClick(props.onClick, props.onHold);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(currDisplay)) {
|
||||||
|
return coerceComponent(currDisplay);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<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(currDisplay.effectDisplay!, "b")} />
|
||||||
|
</div>
|
||||||
|
<div v-if={currDisplay.optionsDisplay}>
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||||
|
<component v-is={coerceComponent(currDisplay.optionsDisplay!, "span")} />
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
LinkNode,
|
||||||
|
Visibility
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (isCoercableComponent(display)) {
|
|
||||||
return coerceComponent(display);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<component v-is={coerceComponent(display.requirement, "h3")} />
|
|
||||||
<div v-if={display.effectDisplay}>
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
<component v-is={coerceComponent(display.effectDisplay!, "b")} />
|
|
||||||
</div>
|
|
||||||
<div v-if={display.optionsDisplay}>
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
<component v-is={coerceComponent(display.optionsDisplay!, "span")} />
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 currActiveTab = activeTab.value;
|
||||||
|
return currActiveTab
|
||||||
|
? coerceComponent(
|
||||||
|
isCoercableComponent(currActiveTab)
|
||||||
|
? currActiveTab
|
||||||
|
: unref(currActiveTab.display)
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const classes = computed(() => {
|
||||||
|
const currActiveTab = activeTab.value;
|
||||||
|
const tabClasses =
|
||||||
|
isCoercableComponent(currActiveTab) || !currActiveTab
|
||||||
|
? undefined
|
||||||
|
: unref(currActiveTab.classes);
|
||||||
|
return tabClasses;
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = computed(() => {
|
||||||
|
const currActiveTab = activeTab.value;
|
||||||
|
return isCoercableComponent(currActiveTab) || !currActiveTab
|
||||||
|
? undefined
|
||||||
|
: unref(currActiveTab.style);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
floating,
|
||||||
|
display,
|
||||||
|
classes,
|
||||||
|
style,
|
||||||
|
Sticky,
|
||||||
|
TabButton
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const display = computed(() => {
|
|
||||||
const activeTab = props.activeTab.value;
|
|
||||||
return activeTab
|
|
||||||
? coerceComponent(isCoercableComponent(activeTab) ? activeTab : activeTab.display)
|
|
||||||
: null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const classes = computed(() => {
|
|
||||||
const activeTab = props.activeTab.value;
|
|
||||||
const tabClasses =
|
|
||||||
isCoercableComponent(activeTab) || !activeTab ? undefined : unref(activeTab.classes);
|
|
||||||
return tabClasses;
|
|
||||||
});
|
|
||||||
|
|
||||||
const style = computed(() => {
|
|
||||||
const activeTab = props.activeTab.value;
|
|
||||||
return isCoercableComponent(activeTab) || !activeTab ? undefined : unref(activeTab.style);
|
|
||||||
});
|
|
||||||
|
|
||||||
function selectTab(tab: string) {
|
|
||||||
props[PersistentState].value = tab;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -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(currDisplay)) {
|
||||||
|
return coerceComponent(currDisplay);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<div v-if={currDisplay.title}>
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||||
|
<component v-is={coerceComponent(currDisplay.title!, "h2")} />
|
||||||
|
</div>
|
||||||
|
<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(currDisplay.effectDisplay!)} />
|
||||||
|
</div>
|
||||||
|
<template v-if={resource.value != null && cost.value != null}>
|
||||||
|
<br />
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||||
|
Cost: {displayResource(resource.value, cost.value)}{" "}
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||||
|
{resource.value.displayName}
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
LinkNode,
|
||||||
|
MarkNode,
|
||||||
|
Visibility
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (isCoercableComponent(display)) {
|
|
||||||
return coerceComponent(display);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<div v-if={display.title}>
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
<component v-is={coerceComponent(display.title!, "h2")} />
|
|
||||||
</div>
|
|
||||||
<component v-is={coerceComponent(display.description, "div")} />
|
|
||||||
<div v-if={display.effectDisplay}>
|
|
||||||
<br />
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
Currently: <component v-is={coerceComponent(display.effectDisplay!)} />
|
|
||||||
</div>
|
|
||||||
<template v-if={unref(props.resource) != null && unref(props.cost) != null}>
|
|
||||||
<br />
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
Cost: {displayResource(unref(props.resource)!, unref(props.cost))}{" "}
|
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
|
||||||
{unref(props.resource)!.displayName}
|
|
||||||
</template>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -182,21 +182,20 @@ 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;
|
dragged?: {
|
||||||
dragged?: {
|
x: number;
|
||||||
x: number;
|
y: number;
|
||||||
y: number;
|
};
|
||||||
};
|
hasDragged?: boolean;
|
||||||
hasDragged?: boolean;
|
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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
if (e.shiftKey && tooltip) {
|
||||||
|
forceTooltip.value = !forceTooltip.value;
|
||||||
|
} else {
|
||||||
|
unref(onClick)?.();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
>()
|
|
||||||
);
|
|
||||||
|
|
||||||
function click(e: MouseEvent) {
|
const component = computeOptionalComponent(display);
|
||||||
if (e.shiftKey && props.tooltip) {
|
const tooltipDisplay = computed(() => {
|
||||||
props.forceTooltip.value = !props.forceTooltip.value;
|
const currTooltip = unwrapRef(tooltip);
|
||||||
} else {
|
|
||||||
unref(props.onClick)?.();
|
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 component = computed(() => {
|
|
||||||
const display = unref(props.display);
|
|
||||||
return display && coerceComponent(display);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { start, stop } = setupHoldToClick(props.onClick, props.onHold);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
@ -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;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
@ -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;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -1,67 +1,107 @@
|
||||||
<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 = computeComponent(display);
|
||||||
|
const showGoBack = computed(
|
||||||
|
() => modInfo.allowGoBack && unref(index) > 0 && !minimized.value
|
||||||
|
);
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
player.tabs = player.tabs.slice(0, unref(props.index));
|
||||||
}
|
}
|
||||||
>()
|
|
||||||
);
|
|
||||||
|
|
||||||
const component = computed(() => coerceComponent(unref(props.display)));
|
nextTick(() => updateTab(minimized.value, unref(minWidth.value)));
|
||||||
const showGoBack = computed(
|
watch([minimized, wrapRef(minWidth)], ([minimized, minWidth]) =>
|
||||||
() => modInfo.allowGoBack && unref(props.index) > 0 && !props.minimized.value
|
updateTab(minimized, minWidth)
|
||||||
);
|
);
|
||||||
|
|
||||||
function goBack() {
|
function updateTab(minimized: boolean, minWidth: number) {
|
||||||
player.tabs = player.tabs.slice(0, unref(props.index));
|
const tabValue = tab.value();
|
||||||
}
|
if (tabValue != undefined) {
|
||||||
|
if (minimized) {
|
||||||
nextTick(() => updateTab(props.minimized.value, props.minWidth.value));
|
tabValue.style.flexGrow = "0";
|
||||||
watch([props.minimized, props.minWidth], ([minimized, minWidth]) => updateTab(minimized, minWidth));
|
tabValue.style.flexShrink = "0";
|
||||||
|
tabValue.style.width = "60px";
|
||||||
function updateTab(minimized: boolean, minWidth: number) {
|
tabValue.style.minWidth = tabValue.style.flexBasis = "";
|
||||||
const tabValue = props.tab.value();
|
tabValue.style.margin = "0";
|
||||||
if (tabValue != undefined) {
|
} else {
|
||||||
if (minimized) {
|
tabValue.style.flexGrow = "";
|
||||||
tabValue.style.flexGrow = "0";
|
tabValue.style.flexShrink = "";
|
||||||
tabValue.style.flexShrink = "0";
|
tabValue.style.width = "";
|
||||||
tabValue.style.width = "60px";
|
tabValue.style.minWidth = tabValue.style.flexBasis = `${minWidth}px`;
|
||||||
tabValue.style.minWidth = tabValue.style.flexBasis = "";
|
tabValue.style.margin = "";
|
||||||
tabValue.style.margin = "0";
|
}
|
||||||
} else {
|
}
|
||||||
tabValue.style.flexGrow = "";
|
|
||||||
tabValue.style.flexShrink = "";
|
|
||||||
tabValue.style.width = "";
|
|
||||||
tabValue.style.minWidth = tabValue.style.flexBasis = `${minWidth}px`;
|
|
||||||
tabValue.style.margin = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
showGoBack,
|
||||||
|
unref,
|
||||||
|
goBack
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
<Save
|
:list="settings.saves"
|
||||||
v-for="(save, index) in saves"
|
handle=".handle"
|
||||||
:key="index"
|
v-if="unref(modal?.isOpen)"
|
||||||
:save="save!"
|
:itemKey="(save: string) => save"
|
||||||
@open="openSave(save!.id)"
|
>
|
||||||
@export="exportSave(save!.id)"
|
<template #item="{ element }">
|
||||||
@editName="name => editSave(save!.id, name)"
|
<Save
|
||||||
@duplicate="duplicateSave(save!.id)"
|
:save="saves[element]"
|
||||||
@delete="deleteSave(save!.id)"
|
@open="openSave(element)"
|
||||||
/>
|
@export="exportSave(element)"
|
||||||
</div>
|
@editName="name => editSave(element, name)"
|
||||||
|
@duplicate="duplicateSave(element)"
|
||||||
|
@delete="deleteSave(element)"
|
||||||
|
/>
|
||||||
|
</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 {
|
if (save == null) {
|
||||||
const save = localStorage.getItem(curr);
|
cachedSaves[id] = { error: `Save with id "${id}" doesn't exist`, id };
|
||||||
if (save == null) {
|
} else {
|
||||||
acc[curr] = { error: `Save with id "${curr}" doesn't exist`, id: curr };
|
try {
|
||||||
} else {
|
cachedSaves[id] = JSON.parse(decodeURIComponent(escape(atob(save))));
|
||||||
acc[curr] = JSON.parse(decodeURIComponent(escape(atob(save))));
|
cachedSaves[id].id = id;
|
||||||
acc[curr].id = curr;
|
} catch (error) {
|
||||||
|
cachedSaves[id] = { error, id };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Can't load save with id "${curr}"`, error);
|
|
||||||
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,25 +206,20 @@ 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 (player.id === id) {
|
if (currSave) {
|
||||||
player.name = newName;
|
currSave.name = newName;
|
||||||
save();
|
if (player.id === id) {
|
||||||
} else {
|
player.name = newName;
|
||||||
localStorage.setItem(
|
save();
|
||||||
id,
|
} else {
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -57,40 +57,39 @@ 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-show={player.devSpeed === 0}>Game Paused</div>
|
||||||
<div v-if={player.devSpeed === 0}>Game Paused</div>
|
<div v-show={player.devSpeed && player.devSpeed !== 1}>
|
||||||
<div v-else-if={player.devSpeed && player.devSpeed !== 1}>
|
Dev Speed: {format(player.devSpeed || 0)}x
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
</div>
|
||||||
Dev Speed: {format(player.devSpeed!)}x
|
<div v-show={player.offlineTime != undefined}>
|
||||||
</div>
|
Offline Time: {formatTime(player.offlineTime || 0)}
|
||||||
<div v-if={player.offlineTime != undefined}>
|
</div>
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
<div>
|
||||||
Offline Time: {formatTime(player.offlineTime!)}
|
<span v-show={Decimal.lt(points.value, "1e1000")}>You have </span>
|
||||||
</div>
|
<h2>{format(points.value)}</h2>
|
||||||
<div>
|
<span v-show={Decimal.lt(points.value, "1e1e6")}> points</span>
|
||||||
<span v-if={Decimal.lt(points.value, "1e1000")}>You have </span>
|
</div>
|
||||||
<h2>{format(points.value)}</h2>
|
<div v-show={Decimal.gt(pointGain.value, 0)}>
|
||||||
<span v-if={Decimal.lt(points.value, "1e1e6")}> points</span>
|
({oomps.value === "" ? formatSmall(pointGain.value) : oomps.value}/sec)
|
||||||
</div>
|
</div>
|
||||||
<div v-if={Decimal.gt(pointGain.value, 0)}>
|
<Spacer />
|
||||||
({oomps.value === "" ? formatSmall(pointGain.value) : oomps.value}/sec)
|
<Modal
|
||||||
</div>
|
modelValue={showModal.value}
|
||||||
<Spacer />
|
onUpdate:modelValue={value => (showModal.value = value)}
|
||||||
<Modal v-model={showModal}>
|
>
|
||||||
<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" />
|
||||||
|
|
||||||
<circle cx="64" cy="128" r="64" fill="#8da8b0" />
|
<circle cx="64" cy="128" r="64" fill="#8da8b0" />
|
||||||
<circle cx="128" cy="64" r="64" fill="#71368a" />
|
<circle cx="128" cy="64" r="64" fill="#71368a" />
|
||||||
<circle cx="192" cy="128" r="64" fill="#fa8508" />
|
<circle cx="192" cy="128" r="64" fill="#fa8508" />
|
||||||
</svg>
|
</svg>
|
||||||
</Modal>
|
</Modal>
|
||||||
{render(tree)}
|
{render(tree)}
|
||||||
</template>
|
</template>
|
||||||
);
|
),
|
||||||
},
|
|
||||||
points,
|
points,
|
||||||
best,
|
best,
|
||||||
total,
|
total,
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,73 +21,99 @@ 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(
|
||||||
get(target, key) {
|
grid: GenericGrid
|
||||||
if (key === "isProxy") {
|
): ProxyHandler<Record<string | number, Proxied<GridCell>>> {
|
||||||
return true;
|
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;
|
||||||
if (target[key] == null) {
|
});
|
||||||
target[key] = new Proxy(target, getCellHandler(key.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return target[key];
|
|
||||||
},
|
|
||||||
set(target, key, value) {
|
|
||||||
console.warn("Cannot set grid cells", target, key, value);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getCellHandler(id: string) {
|
|
||||||
return {
|
return {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
get(target, key) {
|
||||||
get(target: Record<string, any>, key: string, receiver: typeof Proxy): any {
|
|
||||||
if (key === "isProxy") {
|
if (key === "isProxy") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let prop = target[key];
|
if (typeof key === "symbol") {
|
||||||
|
return (grid as never)[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target[key] == null) {
|
||||||
|
target[key] = new Proxy(
|
||||||
|
grid,
|
||||||
|
getCellHandler(key.toString())
|
||||||
|
) as unknown as Proxied<GridCell>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return target[key];
|
||||||
|
},
|
||||||
|
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): ProxyHandler<GenericGrid> {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
get(target, key, receiver): any {
|
||||||
|
if (key === "isProxy") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
isCoercableComponent(display) ? display : display.requirement
|
||||||
|
);
|
||||||
|
toast(
|
||||||
<template>
|
<template>
|
||||||
<h2>Milestone earned!</h2>
|
<h3>Milestone earned!</h3>
|
||||||
<div>
|
<div>
|
||||||
{coerceComponent(
|
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||||
isCoercableComponent(display) ? display : display.requirement
|
{/* @ts-ignore */}
|
||||||
)}
|
<Display />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: []
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ||
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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>>;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue