Remove processedPropType and convert all components to composition API

This commit is contained in:
thepaperpilot 2024-10-20 05:47:59 -05:00
parent ccd685cb9c
commit 1e5411d279
19 changed files with 1058 additions and 1046 deletions

View file

@ -29,14 +29,23 @@ import player from "game/player";
import { computed, toRef, unref } from "vue"; import { computed, toRef, unref } from "vue";
import Layer from "./Layer.vue"; import Layer from "./Layer.vue";
import Nav from "./Nav.vue"; import Nav from "./Nav.vue";
import { deepUnref } from "util/vue";
const tabs = toRef(player, "tabs"); const tabs = toRef(player, "tabs");
const layerKeys = computed(() => Object.keys(layers)); const layerKeys = computed(() => Object.keys(layers));
const useHeader = projInfo.useHeader; const useHeader = projInfo.useHeader;
function gatherLayerProps(layer: GenericLayer) { function gatherLayerProps(layer: GenericLayer) {
const { display, minimized, name, color, minimizable, nodes, minimizedDisplay } = layer; const { display, name, color, minimizable, minimizedDisplay } = deepUnref(layer);
return { display, minimized, name, color, minimizable, nodes, minimizedDisplay }; return {
display,
name,
color,
minimizable,
minimizedDisplay,
minimized: layer.minimized,
nodes: layer.nodes
};
} }
</script> </script>

View file

@ -23,80 +23,48 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import projInfo from "data/projInfo.json"; import projInfo from "data/projInfo.json";
import type { CoercableComponent } from "features/feature"; import type { CoercableComponent } from "features/feature";
import type { FeatureNode } from "game/layers"; import type { FeatureNode } from "game/layers";
import player from "game/player"; import player from "game/player";
import { computeComponent, computeOptionalComponent, processedPropType, unwrapRef } from "util/vue"; import { computeComponent, computeOptionalComponent } from "util/vue";
import { PropType, Ref, computed, defineComponent, onErrorCaptured, ref, toRefs, unref } from "vue"; import { Ref, computed, onErrorCaptured, ref, toRef, unref } from "vue";
import Context from "./Context.vue"; import Context from "./Context.vue";
import ErrorVue from "./Error.vue"; import ErrorVue from "./Error.vue";
export default defineComponent({ const props = defineProps<{
components: { Context, ErrorVue }, index: number;
props: { display: CoercableComponent;
index: { minimizedDisplay?: CoercableComponent;
type: Number, minimized: Ref<boolean>;
required: true name: string;
}, color?: string;
display: { minimizable?: boolean;
type: processedPropType<CoercableComponent>(Object, String, Function), nodes: Ref<Record<string, FeatureNode | undefined>>;
required: true }>();
},
minimizedDisplay: processedPropType<CoercableComponent>(Object, String, Function),
minimized: {
type: Object as PropType<Ref<boolean>>,
required: true
},
name: {
type: processedPropType<string>(String),
required: true
},
color: processedPropType<string>(String),
minimizable: processedPropType<boolean>(Boolean),
nodes: {
type: Object as PropType<Ref<Record<string, FeatureNode | undefined>>>,
required: true
}
},
emits: ["setMinimized"],
setup(props) {
const { display, index, minimized, minimizedDisplay } = toRefs(props);
const component = computeComponent(display); const component = computeComponent(toRef(props, "display"));
const minimizedComponent = computeOptionalComponent(minimizedDisplay); const minimizedComponent = computeOptionalComponent(toRef(props, "minimizedDisplay"));
const showGoBack = computed( const showGoBack = computed(
() => projInfo.allowGoBack && index.value > 0 && !unwrapRef(minimized) () => projInfo.allowGoBack && props.index > 0 && !unref(props.minimized)
); );
function goBack() { function goBack() {
player.tabs.splice(unref(props.index), Infinity); player.tabs.splice(unref(props.index), Infinity);
} }
function updateNodes(nodes: Record<string, FeatureNode | undefined>) { function updateNodes(nodes: Record<string, FeatureNode | undefined>) {
props.nodes.value = nodes; props.nodes.value = nodes;
} }
const errors = ref<Error[]>([]); const errors = ref<Error[]>([]);
onErrorCaptured((err, instance, info) => { onErrorCaptured((err, instance, info) => {
console.warn(`Error caught in "${props.name}" layer`, err, instance, info); console.warn(`Error caught in "${props.name}" layer`, err, instance, info);
errors.value.push( errors.value.push(
err instanceof Error ? (err as Error) : new Error(JSON.stringify(err)) err instanceof Error ? (err as Error) : new Error(JSON.stringify(err))
); );
return false; return false;
});
return {
component,
minimizedComponent,
showGoBack,
updateNodes,
unref,
goBack,
errors
};
}
}); });
</script> </script>

View file

@ -16,8 +16,8 @@
<script setup lang="ts"> <script setup lang="ts">
import "components/common/fields.css"; import "components/common/fields.css";
import type { CoercableComponent } from "features/feature"; import type { CoercableComponent } from "features/feature";
import { computeOptionalComponent, unwrapRef } from "util/vue"; import { computeOptionalComponent } from "util/vue";
import { ref, toRef, watch } from "vue"; import { ref, toRef, unref, watch } from "vue";
import VueNextSelect from "vue-next-select"; import VueNextSelect from "vue-next-select";
import "vue-next-select/dist/index.css"; import "vue-next-select/dist/index.css";
@ -40,7 +40,7 @@ const value = ref<SelectOption | null>(
props.options.find(option => option.value === props.modelValue) ?? null props.options.find(option => option.value === props.modelValue) ?? null
); );
watch(toRef(props, "modelValue"), modelValue => { watch(toRef(props, "modelValue"), modelValue => {
if (unwrapRef(value) !== modelValue) { if (unref(value) !== modelValue) {
value.value = props.options.find(option => option.value === modelValue) ?? null; value.value = props.options.find(option => option.value === modelValue) ?? null;
} }
}); });

View file

@ -23,89 +23,62 @@
</div> </div>
</template> </template>
<script lang="tsx"> <script setup lang="tsx">
import "components/common/features.css"; import "components/common/features.css";
import MarkNode from "components/MarkNode.vue";
import Node from "components/Node.vue";
import { isHidden, isVisible, jsx, Visibility } from "features/feature"; import { isHidden, isVisible, jsx, Visibility } from "features/feature";
import { displayRequirements, Requirements } from "game/requirements"; import { displayRequirements, Requirements } from "game/requirements";
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "util/vue"; import { coerceComponent, isCoercableComponent } from "util/vue";
import { Component, defineComponent, shallowRef, StyleValue, toRefs, unref, UnwrapRef, watchEffect } from "vue"; import { Component, shallowRef, StyleValue, unref, UnwrapRef, watchEffect } from "vue";
import { GenericAchievement } from "./achievement"; import { GenericAchievement } from "./achievement";
export default defineComponent({ const props = defineProps<{
props: { visibility: Visibility | boolean;
visibility: { display?: UnwrapRef<GenericAchievement["display"]>;
type: processedPropType<Visibility | boolean>(Number, Boolean), earned: boolean;
required: true requirements?: Requirements;
}, image?: string;
display: processedPropType<UnwrapRef<GenericAchievement["display"]>>(Object, String, Function), style?: StyleValue;
earned: { classes?: Record<string, boolean>;
type: processedPropType<boolean>(Boolean), mark?: boolean | string;
required: true small?: boolean;
}, id: string;
requirements: processedPropType<Requirements>(Object, Array), }>();
image: processedPropType<string>(String),
style: processedPropType<StyleValue>(String, Object, Array),
classes: processedPropType<Record<string, boolean>>(Object),
mark: processedPropType<boolean | string>(Boolean, String),
small: processedPropType<boolean>(Boolean),
id: {
type: String,
required: true
}
},
components: {
Node,
MarkNode
},
setup(props) {
const { display, requirements, earned } = toRefs(props);
const comp = shallowRef<Component | string>(""); const comp = shallowRef<Component | string>("");
watchEffect(() => { watchEffect(() => {
const currDisplay = unwrapRef(display); const currDisplay = props.display;
if (currDisplay == null) { if (currDisplay == null) {
comp.value = ""; comp.value = "";
return; return;
}
if (isCoercableComponent(currDisplay)) {
comp.value = coerceComponent(currDisplay);
return;
}
const Requirement = coerceComponent(currDisplay.requirement ? currDisplay.requirement : jsx(() => displayRequirements(unwrapRef(requirements) ?? [])), "h3");
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "", "b");
const OptionsDisplay = unwrapRef(earned) ?
coerceComponent(currDisplay.optionsDisplay || "", "span") :
"";
comp.value = coerceComponent(
jsx(() => (
<span>
<Requirement />
{currDisplay.effectDisplay != null ? (
<div>
<EffectDisplay />
</div>
) : null}
{currDisplay.optionsDisplay != null ? (
<div class="equal-spaced">
<OptionsDisplay />
</div>
) : null}
</span>
))
);
});
return {
comp,
unref,
Visibility,
isVisible,
isHidden
};
} }
if (isCoercableComponent(currDisplay)) {
comp.value = coerceComponent(currDisplay);
return;
}
const Requirement = coerceComponent(currDisplay.requirement ? currDisplay.requirement :
jsx(() => displayRequirements(props.requirements ?? [])), "h3");
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "", "b");
const OptionsDisplay = props.earned ?
coerceComponent(currDisplay.optionsDisplay || "", "span") :
"";
comp.value = coerceComponent(
jsx(() => (
<span>
<Requirement />
{currDisplay.effectDisplay != null ? (
<div>
<EffectDisplay />
</div>
) : null}
{currDisplay.optionsDisplay != null ? (
<div class="equal-spaced">
<OptionsDisplay />
</div>
) : null}
</span>
))
);
}); });
</script> </script>

View file

@ -41,107 +41,68 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import MarkNode from "components/MarkNode.vue";
import Node from "components/Node.vue";
import { CoercableComponent, isHidden, isVisible, Visibility } from "features/feature"; import { CoercableComponent, isHidden, isVisible, Visibility } from "features/feature";
import type { DecimalSource } from "util/bignum"; import type { DecimalSource } from "util/bignum";
import Decimal from "util/bignum"; import Decimal from "util/bignum";
import { Direction } from "util/common"; import { Direction } from "util/common";
import { computeOptionalComponent, processedPropType, unwrapRef } from "util/vue"; import { computeOptionalComponent } from "util/vue";
import type { CSSProperties, StyleValue } from "vue"; import type { CSSProperties, StyleValue } from "vue";
import { computed, defineComponent, toRefs, unref } from "vue"; import { computed, toRef, unref } from "vue";
export default defineComponent({ const props = defineProps<{
props: { progress: DecimalSource;
progress: { width: number;
type: processedPropType<DecimalSource>(String, Object, Number), height: number;
required: true direction: Direction;
}, display?: CoercableComponent;
width: { visibility: Visibility | boolean;
type: processedPropType<number>(Number), style?: StyleValue;
required: true classes?: Record<string, boolean>;
}, borderStyle?: StyleValue;
height: { textStyle?: StyleValue;
type: processedPropType<number>(Number), baseStyle?: StyleValue;
required: true fillStyle?: StyleValue;
}, mark?: boolean | string;
direction: { id: string;
type: processedPropType<Direction>(String), }>();
required: true
},
display: processedPropType<CoercableComponent>(Object, String, Function),
visibility: {
type: processedPropType<Visibility | boolean>(Number, Boolean),
required: true
},
style: processedPropType<StyleValue>(Object, String, Array),
classes: processedPropType<Record<string, boolean>>(Object),
borderStyle: processedPropType<StyleValue>(Object, String, Array),
textStyle: processedPropType<StyleValue>(Object, String, Array),
baseStyle: processedPropType<StyleValue>(Object, String, Array),
fillStyle: processedPropType<StyleValue>(Object, String, Array),
mark: processedPropType<boolean | string>(Boolean, String),
id: {
type: String,
required: true
}
},
components: {
MarkNode,
Node
},
setup(props) {
const { progress, width, height, direction, display } = toRefs(props);
const normalizedProgress = computed(() => { const normalizedProgress = computed(() => {
let progressNumber = let progressNumber =
progress.value instanceof Decimal props.progress instanceof Decimal
? progress.value.toNumber() ? props.progress.toNumber()
: Number(progress.value); : Number(props.progress);
return (1 - Math.min(Math.max(progressNumber, 0), 1)) * 100; return (1 - Math.min(Math.max(progressNumber, 0), 1)) * 100;
});
const barStyle = computed(() => {
const barStyle: Partial<CSSProperties> = {
width: unwrapRef(width) + 0.5 + "px",
height: unwrapRef(height) + 0.5 + "px"
};
switch (unref(direction)) {
case Direction.Up:
barStyle.clipPath = `inset(${normalizedProgress.value}% 0% 0% 0%)`;
barStyle.width = unwrapRef(width) + 1 + "px";
break;
case Direction.Down:
barStyle.clipPath = `inset(0% 0% ${normalizedProgress.value}% 0%)`;
barStyle.width = unwrapRef(width) + 1 + "px";
break;
case Direction.Right:
barStyle.clipPath = `inset(0% ${normalizedProgress.value}% 0% 0%)`;
break;
case Direction.Left:
barStyle.clipPath = `inset(0% 0% 0% ${normalizedProgress.value}%)`;
break;
case Direction.Default:
barStyle.clipPath = "inset(0% 50% 0% 0%)";
break;
}
return barStyle;
});
const component = computeOptionalComponent(display);
return {
normalizedProgress,
barStyle,
component,
unref,
Visibility,
isVisible,
isHidden
};
}
}); });
const barStyle = computed(() => {
const barStyle: Partial<CSSProperties> = {
width: props.width + 0.5 + "px",
height: props.height + 0.5 + "px"
};
switch (props.direction) {
case Direction.Up:
barStyle.clipPath = `inset(${normalizedProgress.value}% 0% 0% 0%)`;
barStyle.width = props.width + 1 + "px";
break;
case Direction.Down:
barStyle.clipPath = `inset(0% 0% ${normalizedProgress.value}% 0%)`;
barStyle.width = props.width + 1 + "px";
break;
case Direction.Right:
barStyle.clipPath = `inset(0% ${normalizedProgress.value}% 0% 0%)`;
break;
case Direction.Left:
barStyle.clipPath = `inset(0% 0% 0% ${normalizedProgress.value}%)`;
break;
case Direction.Default:
barStyle.clipPath = "inset(0% 50% 0% 0%)";
break;
}
return barStyle;
});
const component = computeOptionalComponent(toRef(props, "display"));
</script> </script>
<style scoped> <style scoped>

View file

@ -30,7 +30,7 @@
</div> </div>
</template> </template>
<script lang="tsx"> <script setup lang="tsx">
import "components/common/features.css"; import "components/common/features.css";
import MarkNode from "components/MarkNode.vue"; import MarkNode from "components/MarkNode.vue";
import Node from "components/Node.vue"; import Node from "components/Node.vue";
@ -39,139 +39,92 @@ import type { StyleValue } from "features/feature";
import { isHidden, isVisible, jsx, Visibility } from "features/feature"; import { isHidden, isVisible, jsx, Visibility } from "features/feature";
import { getHighNotifyStyle, getNotifyStyle } from "game/notifications"; import { getHighNotifyStyle, getNotifyStyle } from "game/notifications";
import { displayRequirements, Requirements } from "game/requirements"; import { displayRequirements, Requirements } from "game/requirements";
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "util/vue"; import { coerceComponent, isCoercableComponent } from "util/vue";
import type { Component, PropType, UnwrapRef } from "vue"; import type { Component, UnwrapRef } from "vue";
import { computed, defineComponent, shallowRef, toRefs, unref, watchEffect } from "vue"; import { computed, shallowRef, unref, watchEffect } from "vue";
export default defineComponent({ const props = defineProps<{
props: { active: boolean;
active: { maxed: boolean;
type: processedPropType<boolean>(Boolean), canComplete: boolean;
required: true display?: UnwrapRef<GenericChallenge["display"]>;
}, requirements?: Requirements;
maxed: { visibility: Visibility | boolean;
type: processedPropType<boolean>(Boolean), style?: StyleValue;
required: true classes?: Record<string, boolean>;
}, completed: boolean;
canComplete: { canStart: boolean;
type: processedPropType<boolean>(Boolean), mark?: boolean | string;
required: true id: string;
}, toggle: VoidFunction;
display: processedPropType<UnwrapRef<GenericChallenge["display"]>>( }>();
String,
Object,
Function
),
requirements: processedPropType<Requirements>(Object, Array),
visibility: {
type: processedPropType<Visibility | boolean>(Number, Boolean),
required: true
},
style: processedPropType<StyleValue>(String, Object, Array),
classes: processedPropType<Record<string, boolean>>(Object),
completed: {
type: processedPropType<boolean>(Boolean),
required: true
},
canStart: {
type: processedPropType<boolean>(Boolean),
required: true
},
mark: processedPropType<boolean | string>(Boolean, String),
id: {
type: String,
required: true
},
toggle: {
type: Function as PropType<VoidFunction>,
required: true
}
},
components: {
MarkNode,
Node
},
setup(props) {
const { active, maxed, canComplete, display, requirements } = toRefs(props);
const buttonText = computed(() => { const buttonText = computed(() => {
if (active.value) { if (props.active) {
return canComplete.value ? "Finish" : "Exit Early"; return props.canComplete ? "Finish" : "Exit Early";
}
if (maxed.value) {
return "Completed";
}
return "Start";
});
const comp = shallowRef<Component | string>("");
const notifyStyle = computed(() => {
const currActive = unwrapRef(active);
const currCanComplete = unwrapRef(canComplete);
if (currActive) {
if (currCanComplete) {
return getHighNotifyStyle();
}
return getNotifyStyle();
}
return {};
});
watchEffect(() => {
const currDisplay = unwrapRef(display);
if (currDisplay == null) {
comp.value = "";
return;
}
if (isCoercableComponent(currDisplay)) {
comp.value = coerceComponent(currDisplay);
return;
}
const Title = coerceComponent(currDisplay.title || "", "h3");
const Description = coerceComponent(currDisplay.description, "div");
const Goal = coerceComponent(currDisplay.goal != null ? currDisplay.goal : jsx(() => displayRequirements(unwrapRef(requirements) ?? [])), "h3");
const Reward = coerceComponent(currDisplay.reward || "");
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
comp.value = coerceComponent(
jsx(() => (
<span>
{currDisplay.title != null ? (
<div>
<Title />
</div>
) : null}
<Description />
<div>
<br />
Goal: <Goal />
</div>
{currDisplay.reward != null ? (
<div>
<br />
Reward: <Reward />
</div>
) : null}
{currDisplay.effectDisplay != null ? (
<div>
Currently: <EffectDisplay />
</div>
) : null}
</span>
))
);
});
return {
buttonText,
notifyStyle,
comp,
Visibility,
isVisible,
isHidden,
unref
};
} }
if (props.maxed) {
return "Completed";
}
return "Start";
});
const comp = shallowRef<Component | string>("");
const notifyStyle = computed(() => {
const currActive = props.active;
const currCanComplete = props.canComplete;
if (currActive) {
if (currCanComplete) {
return getHighNotifyStyle();
}
return getNotifyStyle();
}
return {};
});
watchEffect(() => {
const currDisplay = props.display;
if (currDisplay == null) {
comp.value = "";
return;
}
if (isCoercableComponent(currDisplay)) {
comp.value = coerceComponent(currDisplay);
return;
}
const Title = coerceComponent(currDisplay.title || "", "h3");
const Description = coerceComponent(currDisplay.description, "div");
const Goal = coerceComponent(currDisplay.goal != null ? currDisplay.goal : jsx(() => displayRequirements(props.requirements ?? [])), "h3");
const Reward = coerceComponent(currDisplay.reward || "");
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
comp.value = coerceComponent(
jsx(() => (
<span>
{currDisplay.title != null ? (
<div>
<Title />
</div>
) : null}
<Description />
<div>
<br />
Goal: <Goal />
</div>
{currDisplay.reward != null ? (
<div>
<br />
Reward: <Reward />
</div>
) : null}
{currDisplay.effectDisplay != null ? (
<div>
Currently: <EffectDisplay />
</div>
) : null}
</span>
))
);
}); });
</script> </script>

View file

@ -27,7 +27,7 @@
</button> </button>
</template> </template>
<script lang="tsx"> <script setup lang="tsx">
import "components/common/features.css"; import "components/common/features.css";
import MarkNode from "components/MarkNode.vue"; import MarkNode from "components/MarkNode.vue";
import Node from "components/Node.vue"; import Node from "components/Node.vue";
@ -37,90 +37,53 @@ import { isHidden, isVisible, jsx, Visibility } from "features/feature";
import { import {
coerceComponent, coerceComponent,
isCoercableComponent, isCoercableComponent,
processedPropType, setupHoldToClick
setupHoldToClick,
unwrapRef
} from "util/vue"; } from "util/vue";
import type { Component, PropType, UnwrapRef } from "vue"; import type { Component, UnwrapRef } from "vue";
import { defineComponent, shallowRef, toRefs, unref, watchEffect } from "vue"; import { shallowRef, toRef, unref, watchEffect } from "vue";
export default defineComponent({ const props = defineProps<{
props: { display: UnwrapRef<GenericClickable["display"]>;
display: { visibility: Visibility | boolean;
type: processedPropType<UnwrapRef<GenericClickable["display"]>>( style?: StyleValue;
Object, classes?: Record<string, boolean>;
String, onClick?: (e?: MouseEvent | TouchEvent) => void;
Function onHold?: VoidFunction;
), canClick: boolean;
required: true small?: boolean;
}, mark?: boolean | string;
visibility: { id: string;
type: processedPropType<Visibility | boolean>(Number, Boolean), }>();
required: true
},
style: processedPropType<StyleValue>(Object, String, Array),
classes: processedPropType<Record<string, boolean>>(Object),
onClick: Function as PropType<(e?: MouseEvent | TouchEvent) => void>,
onHold: Function as PropType<VoidFunction>,
canClick: {
type: processedPropType<boolean>(Boolean),
required: true
},
small: Boolean,
mark: processedPropType<boolean | string>(Boolean, String),
id: {
type: String,
required: true
}
},
components: {
Node,
MarkNode
},
setup(props) {
const { display, onClick, onHold } = toRefs(props);
const comp = shallowRef<Component | string>(""); const comp = shallowRef<Component | string>("");
watchEffect(() => { watchEffect(() => {
const currDisplay = unwrapRef(display); const currDisplay = props.display;
if (currDisplay == null) { if (currDisplay == null) {
comp.value = ""; comp.value = "";
return; return;
}
if (isCoercableComponent(currDisplay)) {
comp.value = coerceComponent(currDisplay);
return;
}
const Title = coerceComponent(currDisplay.title ?? "", "h3");
const Description = coerceComponent(currDisplay.description, "div");
comp.value = coerceComponent(
jsx(() => (
<span>
{currDisplay.title != null ? (
<div>
<Title />
</div>
) : null}
<Description />
</span>
))
);
});
const { start, stop } = setupHoldToClick(onClick, onHold);
return {
start,
stop,
comp,
Visibility,
isVisible,
isHidden,
unref
};
} }
if (isCoercableComponent(currDisplay)) {
comp.value = coerceComponent(currDisplay);
return;
}
const Title = coerceComponent(currDisplay.title ?? "", "h3");
const Description = coerceComponent(currDisplay.description, "div");
comp.value = coerceComponent(
jsx(() => (
<span>
{currDisplay.title != null ? (
<div>
<Title />
</div>
) : null}
<Description />
</span>
))
);
}); });
const { start, stop } = setupHoldToClick(toRef(props, "onClick"), toRef(props, "onHold"));
</script> </script>
<style scoped> <style scoped>

View file

@ -7,7 +7,7 @@
class="table-grid" class="table-grid"
> >
<div v-for="row in unref(rows)" class="row-grid" :class="{ mergeAdjacent }" :key="row"> <div v-for="row in unref(rows)" class="row-grid" :class="{ mergeAdjacent }" :key="row">
<GridCell <GridCellVue
v-for="col in unref(cols)" v-for="col in unref(cols)"
:key="col" :key="col"
v-bind="gatherCellProps(unref(cells)[row * 100 + col])" v-bind="gatherCellProps(unref(cells)[row * 100 + col])"
@ -16,45 +16,26 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import "components/common/table.css"; import "components/common/table.css";
import themes from "data/themes"; import themes from "data/themes";
import { isHidden, isVisible, Visibility } from "features/feature"; import { isHidden, isVisible, Visibility } from "features/feature";
import type { GridCell } from "features/grids/grid"; import type { GridCell } from "features/grids/grid";
import settings from "game/settings"; import settings from "game/settings";
import { processedPropType } from "util/vue"; import { computed, unref } from "vue";
import { computed, defineComponent, unref } from "vue";
import GridCellVue from "./GridCell.vue"; import GridCellVue from "./GridCell.vue";
export default defineComponent({ defineProps<{
props: { visibility: Visibility | boolean;
visibility: { rows: number;
type: processedPropType<Visibility | boolean>(Number, Boolean), cols: number;
required: true cells: Record<string, GridCell>;
}, }>();
rows: {
type: processedPropType<number>(Number),
required: true
},
cols: {
type: processedPropType<number>(Number),
required: true
},
cells: {
type: processedPropType<Record<string, GridCell>>(Object),
required: true
}
},
components: { GridCell: GridCellVue },
setup() {
const mergeAdjacent = computed(() => themes[settings.theme].mergeAdjacent);
function gatherCellProps(cell: GridCell) { const mergeAdjacent = computed(() => themes[settings.theme].mergeAdjacent);
const { visibility, onClick, onHold, display, title, style, canClick, id } = cell;
return { visibility, onClick, onHold, display, title, style, canClick, id };
}
return { unref, gatherCellProps, Visibility, mergeAdjacent, isVisible, isHidden }; function gatherCellProps(cell: GridCell) {
} const { visibility, onClick, onHold, display, title, style, canClick, id } = cell;
}); return { visibility, onClick, onHold, display, title, style, canClick, id };
}
</script> </script>

View file

@ -22,7 +22,7 @@
</button> </button>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import "components/common/features.css"; import "components/common/features.css";
import Node from "components/Node.vue"; import Node from "components/Node.vue";
import type { CoercableComponent, StyleValue } from "features/feature"; import type { CoercableComponent, StyleValue } from "features/feature";
@ -30,58 +30,26 @@ import { isHidden, isVisible, Visibility } from "features/feature";
import { import {
computeComponent, computeComponent,
computeOptionalComponent, computeOptionalComponent,
processedPropType,
setupHoldToClick setupHoldToClick
} from "util/vue"; } from "util/vue";
import type { PropType } from "vue"; import { toRef, unref } from "vue";
import { defineComponent, toRefs, unref } from "vue";
export default defineComponent({ const props = defineProps<{
props: { visibility: Visibility | boolean;
visibility: { onClick?: (e?: MouseEvent | TouchEvent) => void;
type: processedPropType<Visibility | boolean>(Number, Boolean), onHold?: VoidFunction;
required: true display: CoercableComponent;
}, title?: CoercableComponent;
onClick: Function as PropType<(e?: MouseEvent | TouchEvent) => void>, style?: StyleValue;
onHold: Function as PropType<VoidFunction>, canClick: boolean;
display: { id: string;
type: processedPropType<CoercableComponent>(Object, String, Function), }>();
required: true
},
title: processedPropType<CoercableComponent>(Object, String, Function),
style: processedPropType<StyleValue>(String, Object, Array),
canClick: {
type: processedPropType<boolean>(Boolean),
required: true
},
id: {
type: String,
required: true
}
},
components: {
Node
},
setup(props) {
const { onClick, onHold, title, display } = toRefs(props);
const { start, stop } = setupHoldToClick(onClick, onHold);
const titleComponent = computeOptionalComponent(title); const { start, stop } = setupHoldToClick(toRef(props, "onClick"), toRef(props, "onHold"));
const component = computeComponent(display);
return { const titleComponent = computeOptionalComponent(toRef(props, "title"));
start, const component = computeComponent(toRef(props, "display"));
stop,
titleComponent,
component,
Visibility,
unref,
isVisible,
isHidden
};
}
});
</script> </script>
<style scoped> <style scoped>

View file

@ -28,67 +28,33 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import CollapseTransition from "@ivanv/vue-collapse-transition/src/CollapseTransition.vue"; import CollapseTransition from "@ivanv/vue-collapse-transition/src/CollapseTransition.vue";
import Node from "components/Node.vue"; import Node from "components/Node.vue";
import themes from "data/themes"; import themes from "data/themes";
import type { CoercableComponent } from "features/feature"; import type { CoercableComponent } from "features/feature";
import { isHidden, isVisible, Visibility } from "features/feature"; import { isHidden, isVisible, Visibility } from "features/feature";
import settings from "game/settings"; import settings from "game/settings";
import { computeComponent, processedPropType } from "util/vue"; import { computeComponent } from "util/vue";
import type { PropType, Ref, StyleValue } from "vue"; import type { Ref, StyleValue } from "vue";
import { computed, defineComponent, toRefs, unref } from "vue"; import { computed, toRef, unref } from "vue";
export default defineComponent({ const props = defineProps<{
props: { visibility: Visibility | boolean;
visibility: { display: CoercableComponent;
type: processedPropType<Visibility | boolean>(Number, Boolean), title: CoercableComponent;
required: true color?: string;
}, collapsed: Ref<boolean>;
display: { style?: StyleValue;
type: processedPropType<CoercableComponent>(Object, String, Function), titleStyle?: StyleValue;
required: true bodyStyle?: StyleValue;
}, classes?: Record<string, boolean>;
title: { id: string;
type: processedPropType<CoercableComponent>(Object, String, Function), }>();
required: true
},
color: processedPropType<string>(String),
collapsed: {
type: Object as PropType<Ref<boolean>>,
required: true
},
style: processedPropType<StyleValue>(Object, String, Array),
titleStyle: processedPropType<StyleValue>(Object, String, Array),
bodyStyle: processedPropType<StyleValue>(Object, String, Array),
classes: processedPropType<Record<string, boolean>>(Object),
id: {
type: String,
required: true
}
},
components: {
Node,
CollapseTransition
},
setup(props) {
const { title, display } = toRefs(props);
const titleComponent = computeComponent(title); const titleComponent = computeComponent(toRef(props, "title"));
const bodyComponent = computeComponent(display); const bodyComponent = computeComponent(toRef(props, "display"));
const stacked = computed(() => themes[settings.theme].mergeAdjacent); const stacked = computed(() => themes[settings.theme].mergeAdjacent);
return {
titleComponent,
bodyComponent,
stacked,
unref,
Visibility,
isVisible,
isHidden
};
}
});
</script> </script>
<style scoped> <style scoped>

View file

@ -7,78 +7,61 @@
/> />
</template> </template>
<script lang="tsx"> <script setup lang="tsx">
import { Application } from "@pixi/app"; import { Application } from "@pixi/app";
import type { StyleValue } from "features/feature"; import type { StyleValue } from "features/feature";
import { globalBus } from "game/events"; import { globalBus } from "game/events";
import "lib/pixi"; import "lib/pixi";
import { processedPropType } from "util/vue"; import { nextTick, onBeforeUnmount, onMounted, shallowRef, unref } from "vue";
import type { PropType } from "vue";
import { defineComponent, nextTick, onBeforeUnmount, onMounted, shallowRef, unref } from "vue";
// TODO get typing support on the Particles component const props = defineProps<{
export default defineComponent({ style?: StyleValue;
props: { classes?: Record<string, boolean>;
style: processedPropType<StyleValue>(String, Object, Array), onInit: (app: Application) => void;
classes: processedPropType<Record<string, boolean>>(Object), id: string;
onInit: { onContainerResized?: (rect: DOMRect) => void;
type: Function as PropType<(app: Application) => void>, onHotReload?: VoidFunction;
required: true }>();
},
id: {
type: String,
required: true
},
onContainerResized: Function as PropType<(rect: DOMRect) => void>,
onHotReload: Function as PropType<VoidFunction>
},
setup(props) {
const app = shallowRef<null | Application>(null);
const resizeObserver = new ResizeObserver(updateBounds); const app = shallowRef<null | Application>(null);
const resizeListener = shallowRef<HTMLElement | null>(null);
onMounted(() => { const resizeObserver = new ResizeObserver(updateBounds);
// ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element const resizeListener = shallowRef<HTMLElement | null>(null);
const resListener = resizeListener.value;
if (resListener != null) { onMounted(() => {
resizeObserver.observe(resListener); // ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
app.value = new Application({ const resListener = resizeListener.value;
resizeTo: resListener, if (resListener != null) {
backgroundAlpha: 0 resizeObserver.observe(resListener);
}); app.value = new Application({
resizeListener.value?.appendChild(app.value.view); resizeTo: resListener,
props.onInit?.(app.value as Application); backgroundAlpha: 0
}
updateBounds();
if (props.onHotReload) {
nextTick(props.onHotReload);
}
}); });
onBeforeUnmount(() => { resizeListener.value?.appendChild(app.value.view);
app.value?.destroy(); props.onInit?.(app.value as Application);
}); }
updateBounds();
let isDirty = true; if (props.onHotReload) {
function updateBounds() { nextTick(props.onHotReload);
if (isDirty) {
isDirty = false;
nextTick(() => {
if (resizeListener.value != null) {
props.onContainerResized?.(resizeListener.value.getBoundingClientRect());
}
isDirty = true;
});
}
}
globalBus.on("fontsLoaded", updateBounds);
return {
unref,
resizeListener
};
} }
}); });
onBeforeUnmount(() => {
app.value?.destroy();
});
let isDirty = true;
function updateBounds() {
if (isDirty) {
isDirty = false;
nextTick(() => {
if (resizeListener.value != null) {
props.onContainerResized?.(resizeListener.value.getBoundingClientRect());
}
isDirty = true;
});
}
}
globalBus.on("fontsLoaded", updateBounds);
</script> </script>
<style scoped> <style scoped>

View file

@ -19,61 +19,43 @@
</button> </button>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import type { CoercableComponent, StyleValue } from "features/feature"; import type { CoercableComponent, StyleValue } from "features/feature";
import { isHidden, isVisible, Visibility } from "features/feature"; import { isHidden, isVisible, Visibility } from "features/feature";
import { getNotifyStyle } from "game/notifications"; import { getNotifyStyle } from "game/notifications";
import { computeComponent, processedPropType, unwrapRef } from "util/vue"; import { computeComponent } from "util/vue";
import { computed, defineComponent, toRefs, unref } from "vue"; import { computed, toRef, unref } from "vue";
export default defineComponent({ const props = defineProps<{
props: { visibility: Visibility | boolean;
visibility: { display: CoercableComponent;
type: processedPropType<Visibility | boolean>(Number, Boolean), style?: StyleValue;
required: true classes?: Record<string, boolean>;
}, glowColor?: string;
display: { active?: boolean;
type: processedPropType<CoercableComponent>(Object, String, Function), floating?: boolean;
required: true }>();
},
style: processedPropType<StyleValue>(String, Object, Array),
classes: processedPropType<Record<string, boolean>>(Object),
glowColor: processedPropType<string>(String),
active: Boolean,
floating: Boolean
},
emits: ["selectTab"],
setup(props, { emit }) {
const { display, glowColor, floating } = toRefs(props);
const component = computeComponent(display); const emit = defineEmits<{
selectTab: [];
}>();
const glowColorStyle = computed(() => { const component = computeComponent(toRef(props, "display"));
const color = unwrapRef(glowColor);
if (color == null || color === "") {
return {};
}
if (unref(floating)) {
return getNotifyStyle(color);
}
return { boxShadow: `0px 9px 5px -6px ${color}` };
});
function selectTab() { const glowColorStyle = computed(() => {
emit("selectTab"); const color = props.glowColor;
} if (color == null || color === "") {
return {};
return {
selectTab,
component,
glowColorStyle,
unref,
Visibility,
isVisible,
isHidden
};
} }
if (props.floating) {
return getNotifyStyle(color);
}
return { boxShadow: `0px 9px 5px -6px ${color}` };
}); });
function selectTab() {
emit("selectTab");
}
</script> </script>
<style scoped> <style scoped>

View file

@ -33,7 +33,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import Sticky from "components/layout/Sticky.vue"; import Sticky from "components/layout/Sticky.vue";
import themes from "data/themes"; import themes from "data/themes";
import type { CoercableComponent, StyleValue } from "features/feature"; import type { CoercableComponent, StyleValue } from "features/feature";
@ -42,93 +42,60 @@ import type { GenericTab } from "features/tabs/tab";
import TabButton from "features/tabs/TabButton.vue"; import TabButton from "features/tabs/TabButton.vue";
import type { GenericTabButton } from "features/tabs/tabFamily"; import type { GenericTabButton } from "features/tabs/tabFamily";
import settings from "game/settings"; import settings from "game/settings";
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "util/vue"; import { coerceComponent, deepUnref, isCoercableComponent } from "util/vue";
import type { Component, PropType, Ref } from "vue"; import type { Component, Ref } from "vue";
import { computed, defineComponent, shallowRef, toRefs, unref, watchEffect } from "vue"; import { computed, shallowRef, unref, watchEffect } from "vue";
export default defineComponent({ const props = defineProps<{
props: { visibility: Visibility | boolean;
visibility: { activeTab: GenericTab | CoercableComponent | null;
type: processedPropType<Visibility | boolean>(Number, Boolean), selected: Ref<string>;
required: true tabs: Record<string, GenericTabButton>;
}, style?: StyleValue;
activeTab: { classes?: Record<string, boolean>;
type: processedPropType<GenericTab | CoercableComponent | null>(Object), buttonContainerStyle?: StyleValue;
required: true buttonContainerClasses?: Record<string, boolean>;
}, }>();
selected: {
type: Object as PropType<Ref<string>>,
required: true
},
tabs: {
type: processedPropType<Record<string, GenericTabButton>>(Object),
required: true
},
style: processedPropType<StyleValue>(String, Object, Array),
classes: processedPropType<Record<string, boolean>>(Object),
buttonContainerStyle: processedPropType<StyleValue>(String, Object, Array),
buttonContainerClasses: processedPropType<Record<string, boolean>>(Object)
},
components: {
Sticky,
TabButton
},
setup(props) {
const { activeTab } = toRefs(props);
const floating = computed(() => { const floating = computed(() => {
return themes[settings.theme].floatingTabs; return themes[settings.theme].floatingTabs;
});
const component = shallowRef<Component | string>("");
watchEffect(() => {
const currActiveTab = unwrapRef(activeTab);
if (currActiveTab == null) {
component.value = "";
return;
}
if (isCoercableComponent(currActiveTab)) {
component.value = coerceComponent(currActiveTab);
return;
}
component.value = coerceComponent(unref(currActiveTab.display));
});
const tabClasses = computed(() => {
const currActiveTab = unwrapRef(activeTab);
const tabClasses =
isCoercableComponent(currActiveTab) || !currActiveTab
? undefined
: unref(currActiveTab.classes);
return tabClasses;
});
const tabStyle = computed(() => {
const currActiveTab = unwrapRef(activeTab);
return isCoercableComponent(currActiveTab) || !currActiveTab
? undefined
: unref(currActiveTab.style);
});
function gatherButtonProps(button: GenericTabButton) {
const { display, style, classes, glowColor, visibility } = button;
return { display, style: unref(style), classes, glowColor, visibility };
}
return {
floating,
tabClasses,
tabStyle,
Visibility,
component,
gatherButtonProps,
unref,
isVisible,
isHidden
};
}
}); });
const component = shallowRef<Component | string>("");
watchEffect(() => {
const currActiveTab = props.activeTab;
if (currActiveTab == null) {
component.value = "";
return;
}
if (isCoercableComponent(currActiveTab)) {
component.value = coerceComponent(currActiveTab);
return;
}
component.value = coerceComponent(unref(currActiveTab.display));
});
const tabClasses = computed(() => {
const currActiveTab = props.activeTab;
const tabClasses =
isCoercableComponent(currActiveTab) || !currActiveTab
? undefined
: unref(currActiveTab.classes);
return tabClasses;
});
const tabStyle = computed(() => {
const currActiveTab = props.activeTab;
return isCoercableComponent(currActiveTab) || !currActiveTab
? undefined
: unref(currActiveTab.style);
});
function gatherButtonProps(button: GenericTabButton) {
const { display, style, classes, glowColor, visibility } = deepUnref(button);
return { display, style, classes, glowColor, visibility };
}
</script> </script>
<style scoped> <style scoped>

View file

@ -34,7 +34,7 @@
</div> </div>
</template> </template>
<script lang="tsx"> <script setup lang="tsx">
import themes from "data/themes"; import themes from "data/themes";
import type { CoercableComponent } from "features/feature"; import type { CoercableComponent } from "features/feature";
import { jsx, StyleValue } from "features/feature"; import { jsx, StyleValue } from "features/feature";
@ -45,66 +45,45 @@ import type { VueFeature } from "util/vue";
import { import {
coerceComponent, coerceComponent,
computeOptionalComponent, computeOptionalComponent,
processedPropType, renderJSX
renderJSX,
unwrapRef
} from "util/vue"; } from "util/vue";
import type { Component, PropType } from "vue"; import type { Component } from "vue";
import { computed, defineComponent, ref, shallowRef, toRefs, unref } from "vue"; import { computed, ref, shallowRef, toRef, unref } from "vue";
export default defineComponent({ const props = defineProps<{
props: { element?: VueFeature;
element: Object as PropType<VueFeature>, display: CoercableComponent;
display: { style?: StyleValue;
type: processedPropType<CoercableComponent>(Object, String, Function), classes?: Record<string, boolean>;
required: true direction?: Direction;
}, xoffset?: string;
style: processedPropType<StyleValue>(Object, String, Array), yoffset?: string;
classes: processedPropType<Record<string, boolean>>(Object), pinned?: Persistent<boolean>;
direction: processedPropType<Direction>(String), }>();
xoffset: processedPropType<string>(String),
yoffset: processedPropType<string>(String),
pinned: Object as PropType<Persistent<boolean>>
},
setup(props) {
const { element, display, pinned } = toRefs(props);
const isHovered = ref(false); const isHovered = ref(false);
const isShown = computed(() => (unwrapRef(pinned) || isHovered.value) && comp.value); const isShown = computed(() => (props.pinned?.value === true || isHovered.value) && comp.value);
const comp = computeOptionalComponent(display); const comp = computeOptionalComponent(toRef(props, "display"));
const elementComp = shallowRef<Component | "" | null>( const elementComp = shallowRef<Component | "" | null>(
coerceComponent( coerceComponent(
jsx(() => { jsx(() => {
const currComponent = unwrapRef(element); const currComponent = props.element;
return currComponent == null ? "" : renderJSX(currComponent); return currComponent == null ? "" : renderJSX(currComponent);
}) })
) )
); );
function togglePinned(e: MouseEvent) { function togglePinned(e: MouseEvent) {
const isPinned = pinned as unknown as Persistent<boolean> | undefined; // Vue typing :/ const isPinned = props.pinned;
if (e.shiftKey && isPinned) { if (e.shiftKey && isPinned != null) {
isPinned.value = !isPinned.value; isPinned.value = !isPinned.value;
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
}
}
const showPin = computed(() => unwrapRef(pinned) && themes[settings.theme].showPin);
return {
Direction,
isHovered,
isShown,
comp,
elementComp,
unref,
togglePinned,
showPin
};
} }
}); }
const showPin = computed(() => props.pinned?.value === true && themes[settings.theme].showPin);
</script> </script>
<style scoped> <style scoped>

View file

@ -5,74 +5,58 @@
<Links v-if="branches" :links="unref(branches)" /> <Links v-if="branches" :links="unref(branches)" />
</template> </template>
<script lang="tsx"> <script setup lang="tsx">
import "components/common/table.css"; import "components/common/table.css";
import { jsx } from "features/feature"; import { jsx } from "features/feature";
import Links from "features/links/Links.vue"; import Links from "features/links/Links.vue";
import type { GenericTreeNode, TreeBranch } from "features/trees/tree"; import type { GenericTreeNode, TreeBranch } from "features/trees/tree";
import { coerceComponent, processedPropType, renderJSX, unwrapRef } from "util/vue"; import { coerceComponent, renderJSX } from "util/vue";
import type { Component } from "vue"; import type { Component } from "vue";
import { defineComponent, shallowRef, toRefs, unref, watchEffect } from "vue"; import { shallowRef, unref, watchEffect } from "vue";
export default defineComponent({ const props = defineProps<{
props: { nodes: GenericTreeNode[][];
nodes: { leftSideNodes?: GenericTreeNode[];
type: processedPropType<GenericTreeNode[][]>(Array), rightSideNodes?: GenericTreeNode[];
required: true branches?: TreeBranch[];
}, }>();
leftSideNodes: processedPropType<GenericTreeNode[]>(Array),
rightSideNodes: processedPropType<GenericTreeNode[]>(Array),
branches: processedPropType<TreeBranch[]>(Array)
},
components: { Links },
setup(props) {
const { nodes, leftSideNodes, rightSideNodes } = toRefs(props);
const nodesComp = shallowRef<Component | "">(); const nodesComp = shallowRef<Component | "">();
watchEffect(() => { watchEffect(() => {
const currNodes = unwrapRef(nodes); const currNodes = props.nodes;
nodesComp.value = coerceComponent( nodesComp.value = coerceComponent(
jsx(() => (
<>
{currNodes.map(row => (
<span class="row tree-row" style="margin: 50px auto;">
{row.map(renderJSX)}
</span>
))}
</>
))
);
});
const leftNodesComp = shallowRef<Component | "">();
watchEffect(() => {
const currNodes = props.leftSideNodes;
leftNodesComp.value = currNodes
? coerceComponent(
jsx(() => ( jsx(() => (
<> <span class="left-side-nodes small">{currNodes.map(renderJSX)}</span>
{currNodes.map(row => (
<span class="row tree-row" style="margin: 50px auto;">
{row.map(renderJSX)}
</span>
))}
</>
)) ))
); )
}); : "";
});
const leftNodesComp = shallowRef<Component | "">(); const rightNodesComp = shallowRef<Component | "">();
watchEffect(() => { watchEffect(() => {
const currNodes = unwrapRef(leftSideNodes); const currNodes = props.rightSideNodes;
leftNodesComp.value = currNodes rightNodesComp.value = currNodes
? coerceComponent( ? coerceComponent(
jsx(() => ( jsx(() => <span class="side-nodes small">{currNodes.map(renderJSX)}</span>)
<span class="left-side-nodes small">{currNodes.map(renderJSX)}</span> )
)) : "";
)
: "";
});
const rightNodesComp = shallowRef<Component | "">();
watchEffect(() => {
const currNodes = unwrapRef(rightSideNodes);
rightNodesComp.value = currNodes
? coerceComponent(
jsx(() => <span class="side-nodes small">{currNodes.map(renderJSX)}</span>)
)
: "";
});
return {
unref,
nodesComp,
leftNodesComp,
rightNodesComp
};
}
}); });
</script> </script>

View file

@ -33,66 +33,32 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import MarkNode from "components/MarkNode.vue"; import type { CoercableComponent, StyleValue, Visibility } from "features/feature";
import Node from "components/Node.vue"; import { isHidden, isVisible } from "features/feature";
import type { CoercableComponent, StyleValue } from "features/feature";
import { isHidden, isVisible, Visibility } from "features/feature";
import { import {
computeOptionalComponent, computeOptionalComponent,
isCoercableComponent,
processedPropType,
setupHoldToClick setupHoldToClick
} from "util/vue"; } from "util/vue";
import type { PropType } from "vue"; import { toRef, unref } from "vue";
import { defineComponent, toRefs, unref } from "vue";
export default defineComponent({ const props = defineProps<{
props: { visibility: Visibility | boolean;
display: processedPropType<CoercableComponent>(Object, String, Function), canClick: boolean;
visibility: { id: string;
type: processedPropType<Visibility | boolean>(Number, Boolean), display?: CoercableComponent;
required: true style?: StyleValue;
}, classes?: Record<string, boolean>;
style: processedPropType<StyleValue>(String, Object, Array), onClick?: (e?: MouseEvent | TouchEvent) => void;
classes: processedPropType<Record<string, boolean>>(Object), onHold?: VoidFunction;
onClick: Function as PropType<(e?: MouseEvent | TouchEvent) => void>, color?: string;
onHold: Function as PropType<VoidFunction>, glowColor?: string;
color: processedPropType<string>(String), mark?: boolean | string;
glowColor: processedPropType<string>(String), }>();
canClick: {
type: processedPropType<boolean>(Boolean),
required: true
},
mark: processedPropType<boolean | string>(Boolean, String),
id: {
type: String,
required: true
}
},
components: {
MarkNode,
Node
},
setup(props) {
const { onClick, onHold, display } = toRefs(props);
const comp = computeOptionalComponent(display); const comp = computeOptionalComponent(toRef(props, "display"));
const { start, stop } = setupHoldToClick(onClick, onHold); const { start, stop } = setupHoldToClick(toRef(props, "onClick"), toRef(props, "onHold"));
return {
start,
stop,
comp,
unref,
Visibility,
isCoercableComponent,
isVisible,
isHidden
};
}
});
</script> </script>
<style scoped> <style scoped>

View file

@ -24,7 +24,7 @@
</button> </button>
</template> </template>
<script lang="tsx"> <script setup lang="tsx">
import "components/common/features.css"; import "components/common/features.css";
import MarkNode from "components/MarkNode.vue"; import MarkNode from "components/MarkNode.vue";
import Node from "components/Node.vue"; import Node from "components/Node.vue";
@ -32,94 +32,56 @@ import type { StyleValue } from "features/feature";
import { isHidden, isVisible, jsx, Visibility } from "features/feature"; import { isHidden, isVisible, jsx, Visibility } from "features/feature";
import type { GenericUpgrade } from "features/upgrades/upgrade"; import type { GenericUpgrade } from "features/upgrades/upgrade";
import { displayRequirements, Requirements } from "game/requirements"; import { displayRequirements, Requirements } from "game/requirements";
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "util/vue"; import { coerceComponent, isCoercableComponent } from "util/vue";
import type { Component, PropType, UnwrapRef } from "vue"; import type { Component, UnwrapRef } from "vue";
import { defineComponent, shallowRef, toRefs, unref, watchEffect } from "vue"; import { shallowRef, unref, watchEffect } from "vue";
export default defineComponent({ const props = defineProps<{
props: { display: UnwrapRef<GenericUpgrade["display"]>;
display: { visibility: Visibility | boolean;
type: processedPropType<UnwrapRef<GenericUpgrade["display"]>>(String, Object, Function), style?: StyleValue;
required: true classes?: Record<string, boolean>;
}, requirements: Requirements;
visibility: { canPurchase: boolean;
type: processedPropType<Visibility | boolean>(Number, Boolean), bought: boolean;
required: true mark?: boolean | string;
}, id: string;
style: processedPropType<StyleValue>(String, Object, Array), purchase?: VoidFunction;
classes: processedPropType<Record<string, boolean>>(Object), }>();
requirements: {
type: Object as PropType<Requirements>,
required: true
},
canPurchase: {
type: processedPropType<boolean>(Boolean),
required: true
},
bought: {
type: processedPropType<boolean>(Boolean),
required: true
},
mark: processedPropType<boolean | string>(Boolean, String),
id: {
type: String,
required: true
},
purchase: {
type: Function as PropType<VoidFunction>,
required: true
}
},
components: {
Node,
MarkNode
},
setup(props) {
const { display, requirements, bought } = toRefs(props);
const component = shallowRef<Component | string>(""); const component = shallowRef<Component | string>("");
watchEffect(() => { watchEffect(() => {
const currDisplay = unwrapRef(display); const currDisplay = props.display;
if (currDisplay == null) { if (currDisplay == null) {
component.value = ""; component.value = "";
return; return;
}
if (isCoercableComponent(currDisplay)) {
component.value = coerceComponent(currDisplay);
return;
}
const Title = coerceComponent(currDisplay.title || "", "h3");
const Description = coerceComponent(currDisplay.description, "div");
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
component.value = coerceComponent(
jsx(() => (
<span>
{currDisplay.title != null ? (
<div>
<Title />
</div>
) : null}
<Description />
{currDisplay.effectDisplay != null ? (
<div>
Currently: <EffectDisplay />
</div>
) : null}
{bought.value ? null : <><br />{displayRequirements(requirements.value)}</>}
</span>
))
);
});
return {
component,
unref,
Visibility,
isVisible,
isHidden
};
} }
if (isCoercableComponent(currDisplay)) {
component.value = coerceComponent(currDisplay);
return;
}
const Title = coerceComponent(currDisplay.title || "", "h3");
const Description = coerceComponent(currDisplay.description, "div");
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
component.value = coerceComponent(
jsx(() => (
<span>
{currDisplay.title != null ? (
<div>
<Title />
</div>
) : null}
<Description />
{currDisplay.effectDisplay != null ? (
<div>
Currently: <EffectDisplay />
</div>
) : null}
{props.bought ? null : <><br />{displayRequirements(props.requirements)}</>}
</span>
))
);
}); });
</script> </script>

View file

@ -10,7 +10,7 @@ import {
} from "features/feature"; } from "features/feature";
import type { ProcessedComputable } from "util/computed"; import type { ProcessedComputable } from "util/computed";
import { DoNotCache } from "util/computed"; import { DoNotCache } from "util/computed";
import type { Component, ComputedRef, DefineComponent, PropType, Ref, ShallowRef } from "vue"; import type { Component, DefineComponent, Ref, ShallowRef, UnwrapRef } from "vue";
import { import {
computed, computed,
defineComponent, defineComponent,
@ -175,22 +175,22 @@ export function getFirstFeature<
} }
export function computeComponent( export function computeComponent(
component: Ref<ProcessedComputable<CoercableComponent>>, component: Ref<CoercableComponent>,
defaultWrapper = "div" defaultWrapper = "div"
): ShallowRef<Component | ""> { ): ShallowRef<Component | ""> {
const comp = shallowRef<Component | "">(); const comp = shallowRef<Component | "">();
watchEffect(() => { watchEffect(() => {
comp.value = coerceComponent(unwrapRef(component), defaultWrapper); comp.value = coerceComponent(unref(component), defaultWrapper);
}); });
return comp as ShallowRef<Component | "">; return comp as ShallowRef<Component | "">;
} }
export function computeOptionalComponent( export function computeOptionalComponent(
component: Ref<ProcessedComputable<CoercableComponent | undefined> | undefined>, component: Ref<CoercableComponent | undefined>,
defaultWrapper = "div" defaultWrapper = "div"
): ShallowRef<Component | "" | null> { ): ShallowRef<Component | "" | null> {
const comp = shallowRef<Component | "" | null>(null); const comp = shallowRef<Component | "" | null>(null);
watchEffect(() => { watchEffect(() => {
const currComponent = unwrapRef(component); const currComponent = unref(component);
comp.value = comp.value =
currComponent === "" || currComponent == null currComponent === "" || currComponent == null
? null ? null
@ -199,12 +199,14 @@ export function computeOptionalComponent(
return comp; return comp;
} }
export function wrapRef<T>(ref: Ref<ProcessedComputable<T>>): ComputedRef<T> { export function deepUnref<T extends object>(refObject: T): { [K in keyof T]: UnwrapRef<T[K]> } {
return computed(() => unwrapRef(ref)); return (Object.keys(refObject) as (keyof T)[]).reduce(
} (acc, curr) => {
acc[curr] = unref(refObject[curr]) as UnwrapRef<T[keyof T]>;
export function unwrapRef<T>(ref: Ref<ProcessedComputable<T>>): T { return acc;
return unref<T>(unref(ref)); },
{} as { [K in keyof T]: UnwrapRef<T[K]> }
);
} }
export function setRefValue<T>(ref: Ref<T | Ref<T>>, value: T) { export function setRefValue<T>(ref: Ref<T | Ref<T>>, value: T) {
@ -222,14 +224,6 @@ export type PropTypes =
| typeof Function | typeof Function
| typeof Object | typeof Object
| typeof Array; | 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>>;
}
export function trackHover(element: VueFeature): Ref<boolean> { export function trackHover(element: VueFeature): Ref<boolean> {
const isHovered = ref(false); const isHovered = ref(false);
@ -245,8 +239,11 @@ export function trackHover(element: VueFeature): Ref<boolean> {
} }
export function kebabifyObject(object: Record<string, unknown>) { export function kebabifyObject(object: Record<string, unknown>) {
return Object.keys(object).reduce((acc, curr) => { return Object.keys(object).reduce(
acc[camelToKebab(curr)] = object[curr]; (acc, curr) => {
return acc; acc[camelToKebab(curr)] = object[curr];
}, {} as Record<string, unknown>); return acc;
},
{} as Record<string, unknown>
);
} }

File diff suppressed because it is too large Load diff