Complete the rewrite
Renderables no longer get wrapped in computed refs, because JSX.Elements don't like that (desyncs with the DOM) Relatedly, a lot of display functions got fairly simplified, removing unnecessary local components Added `MaybeGetter` utility type for something that may be a getter function or a static value (but not a ref) Made Achievement.vue use a Renderable for the display. The object of components can still be passed to `createAchievement` Made Challenge.vue use a Renderable for the display. The object of components can still be passed to `createChallenge` Fixed some issues introduced by the rewrite that broke particles systems
This commit is contained in:
parent
ffd9529e9a
commit
ff14443102
37 changed files with 504 additions and 483 deletions
|
@ -15,15 +15,16 @@
|
|||
|
||||
<script setup lang="tsx">
|
||||
import "components/common/fields.css";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { MaybeRef, ref, toRef, unref, watch } from "vue";
|
||||
import { ref, toRef, unref, watch } from "vue";
|
||||
import VueNextSelect from "vue-next-select";
|
||||
import "vue-next-select/dist/index.css";
|
||||
|
||||
export type SelectOption = { label: string; value: unknown };
|
||||
|
||||
const props = defineProps<{
|
||||
title?: MaybeRef<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
modelValue?: unknown;
|
||||
options: SelectOption[];
|
||||
placeholder?: string;
|
||||
|
|
|
@ -27,12 +27,13 @@
|
|||
|
||||
<script setup lang="tsx">
|
||||
import "components/common/fields.css";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { computed, MaybeRef, onMounted, shallowRef, unref } from "vue";
|
||||
import { computed, onMounted, shallowRef, unref } from "vue";
|
||||
import VueTextareaAutosize from "vue-textarea-autosize";
|
||||
|
||||
const props = defineProps<{
|
||||
title?: MaybeRef<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
modelValue?: string;
|
||||
textArea?: boolean;
|
||||
placeholder?: string;
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
|
||||
<script setup lang="tsx">
|
||||
import "components/common/fields.css";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { computed, MaybeRef } from "vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
title?: MaybeRef<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
modelValue?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -8,14 +8,15 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import type { MaybeRef, Ref } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
import Col from "./Column.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
collapsed: Ref<boolean>;
|
||||
display: MaybeRef<Renderable>;
|
||||
content: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
content: MaybeGetter<Renderable>;
|
||||
}>();
|
||||
|
||||
const Display = () => render(props.display);
|
||||
|
|
|
@ -17,7 +17,7 @@ import settings from "game/settings";
|
|||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal, { format, formatSmall, formatTime } from "util/bignum";
|
||||
import { WithRequired } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { render, Renderable, renderCol } from "util/vue";
|
||||
import type { ComputedRef, MaybeRef, MaybeRefOrGetter } from "vue";
|
||||
import { computed, ref, unref } from "vue";
|
||||
|
@ -43,7 +43,7 @@ export interface ResetButtonOptions extends ClickableOptions {
|
|||
* The content to display on the button.
|
||||
* By default, this includes the reset description, and amount of currency to be gained.
|
||||
*/
|
||||
display?: MaybeRefOrGetter<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/**
|
||||
* Whether or not this button can currently be clicked.
|
||||
* Defaults to checking the current gain amount is greater than {@link minimumGain}
|
||||
|
@ -126,38 +126,36 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
|||
Decimal.gte(unref(conversion.actualGain), unref(resetButton.minimumGain))
|
||||
),
|
||||
display:
|
||||
processGetter(display) ??
|
||||
computed(
|
||||
(): JSX.Element => (
|
||||
<span>
|
||||
{unref(resetButton.resetDescription)}
|
||||
<b>
|
||||
display ??
|
||||
((): JSX.Element => (
|
||||
<span>
|
||||
{unref(resetButton.resetDescription)}
|
||||
<b>
|
||||
{displayResource(
|
||||
conversion.gainResource,
|
||||
Decimal.max(
|
||||
unref(conversion.actualGain),
|
||||
unref(resetButton.minimumGain)
|
||||
)
|
||||
)}
|
||||
</b>{" "}
|
||||
{conversion.gainResource.displayName}
|
||||
{unref(resetButton.showNextAt) != null ? (
|
||||
<div>
|
||||
<br />
|
||||
{unref(conversion.buyMax) ? "Next:" : "Req:"}{" "}
|
||||
{displayResource(
|
||||
conversion.gainResource,
|
||||
Decimal.max(
|
||||
unref(conversion.actualGain),
|
||||
unref(resetButton.minimumGain)
|
||||
)
|
||||
)}
|
||||
</b>{" "}
|
||||
{conversion.gainResource.displayName}
|
||||
{unref(resetButton.showNextAt) != null ? (
|
||||
<div>
|
||||
<br />
|
||||
{unref(conversion.buyMax) ? "Next:" : "Req:"}{" "}
|
||||
{displayResource(
|
||||
conversion.baseResource,
|
||||
!unref<boolean>(conversion.buyMax) &&
|
||||
Decimal.gte(unref(conversion.actualGain), 1)
|
||||
? unref(conversion.currentAt)
|
||||
: unref(conversion.nextAt)
|
||||
)}{" "}
|
||||
{conversion.baseResource.displayName}
|
||||
</div>
|
||||
) : null}
|
||||
</span>
|
||||
)
|
||||
),
|
||||
conversion.baseResource,
|
||||
!unref<boolean>(conversion.buyMax) &&
|
||||
Decimal.gte(unref(conversion.actualGain), 1)
|
||||
? unref(conversion.currentAt)
|
||||
: unref(conversion.nextAt)
|
||||
)}{" "}
|
||||
{conversion.baseResource.displayName}
|
||||
</div>
|
||||
) : null}
|
||||
</span>
|
||||
)),
|
||||
onClick: function (e?: MouseEvent | TouchEvent) {
|
||||
if (unref(resetButton.canClick) === false) {
|
||||
return;
|
||||
|
@ -211,7 +209,7 @@ export function createLayerTreeNode<T extends LayerTreeNodeOptions>(optionsFunc:
|
|||
return {
|
||||
...(props as Omit<typeof props, keyof LayerTreeNodeOptions>),
|
||||
layerID,
|
||||
display: processGetter(display) ?? layerID,
|
||||
display: display ?? layerID,
|
||||
append: processGetter(append) ?? true,
|
||||
onClick() {
|
||||
if (unref<boolean>(layerTreeNode.append)) {
|
||||
|
@ -244,7 +242,7 @@ export interface Section {
|
|||
/** The unit of measurement for the base. **/
|
||||
unit?: string;
|
||||
/** The label to call the base amount. Defaults to "Base". **/
|
||||
baseText?: MaybeRefOrGetter<Renderable>;
|
||||
baseText?: MaybeGetter<Renderable>;
|
||||
/** Whether or not this section should be currently visible to the player. **/
|
||||
visible?: MaybeRefOrGetter<boolean>;
|
||||
/** Determines if numbers larger or smaller than the base should be displayed as red. */
|
||||
|
@ -258,12 +256,12 @@ export interface Section {
|
|||
*/
|
||||
export function createCollapsibleModifierSections(
|
||||
sectionsFunc: () => Section[]
|
||||
): [MaybeRef<Renderable>, Persistent<Record<number, boolean>>] {
|
||||
): [() => Renderable, Persistent<Record<number, boolean>>] {
|
||||
const sections: Section[] = [];
|
||||
const processed:
|
||||
| {
|
||||
base: MaybeRef<DecimalSource | undefined>[];
|
||||
baseText: (MaybeRef<Renderable> | undefined)[];
|
||||
baseText: (MaybeGetter<Renderable> | undefined)[];
|
||||
visible: MaybeRef<boolean | undefined>[];
|
||||
title: MaybeRef<string | undefined>[];
|
||||
subtitle: MaybeRef<string | undefined>[];
|
||||
|
@ -274,7 +272,7 @@ export function createCollapsibleModifierSections(
|
|||
if (!calculated) {
|
||||
sections.push(...sectionsFunc());
|
||||
processed.base = sections.map(s => processGetter(s.base));
|
||||
processed.baseText = sections.map(s => processGetter(s.baseText));
|
||||
processed.baseText = sections.map(s => s.baseText);
|
||||
processed.visible = sections.map(s => processGetter(s.visible));
|
||||
processed.title = sections.map(s => processGetter(s.title));
|
||||
processed.subtitle = sections.map(s => processGetter(s.subtitle));
|
||||
|
@ -284,7 +282,7 @@ export function createCollapsibleModifierSections(
|
|||
}
|
||||
|
||||
const collapsed = persistent<Record<number, boolean>>({}, false);
|
||||
const jsxFunc = computed(() => {
|
||||
const jsxFunc = () => {
|
||||
const sections = calculateSections();
|
||||
|
||||
let firstVisibleSection = true;
|
||||
|
@ -364,7 +362,7 @@ export function createCollapsibleModifierSections(
|
|||
);
|
||||
});
|
||||
return <>{sectionJSX}</>;
|
||||
});
|
||||
};
|
||||
return [jsxFunc, collapsed];
|
||||
}
|
||||
|
||||
|
|
|
@ -48,8 +48,9 @@ import settings from "game/settings";
|
|||
import { DecimalSource } from "lib/break_eternity";
|
||||
import Decimal, { format, formatWhole } from "util/bignum";
|
||||
import { Direction } from "util/common";
|
||||
import { render, Renderable, renderCol, renderRow } from "util/vue";
|
||||
import { Renderable, render, renderCol, renderRow } from "util/vue";
|
||||
import { ComputedRef, Ref, computed, ref, unref } from "vue";
|
||||
import { addMark } from "wrappers/marks/mark";
|
||||
import { addTooltip } from "wrappers/tooltips/tooltip";
|
||||
import f from "./f";
|
||||
|
||||
|
@ -71,7 +72,7 @@ const layer = createLayer(id, () => {
|
|||
title: "Lore",
|
||||
titleStyle: { color: "#FE0000" },
|
||||
display: "DEEP LORE!",
|
||||
bodyStyle: { "backgroundColor": "#0000EE" },
|
||||
bodyStyle: { backgroundColor: "#0000EE" },
|
||||
color: "rgb(75, 220, 19)"
|
||||
}));
|
||||
|
||||
|
@ -121,11 +122,14 @@ const layer = createLayer(id, () => {
|
|||
})),
|
||||
completionLimit: 3,
|
||||
display: {
|
||||
description: (): Renderable => <>
|
||||
Makes the game 0% harder<br/>{formatWhole(funChallenge.completions.value)}/{
|
||||
funChallenge.completionLimit
|
||||
} completions
|
||||
</>,
|
||||
description: (): Renderable => (
|
||||
<>
|
||||
Makes the game 0% harder
|
||||
<br />
|
||||
{formatWhole(funChallenge.completions.value)}/{funChallenge.completionLimit}{" "}
|
||||
completions
|
||||
</>
|
||||
),
|
||||
goal: "Have 20 points I guess",
|
||||
reward: "Says hi",
|
||||
effectDisplay: format(funEffect.value) + "x"
|
||||
|
@ -147,7 +151,10 @@ const layer = createLayer(id, () => {
|
|||
height: "400px"
|
||||
}
|
||||
}));
|
||||
const funEffect = computed(() => Decimal.add(points.value, 1).tetrate(0.02));
|
||||
addMark(funChallenge, () => ({
|
||||
mark: funChallenge.maxed
|
||||
}));
|
||||
const funEffect = computed((): Decimal => Decimal.add(points.value, 1).tetrate(0.02));
|
||||
|
||||
const generatorUpgrade = createUpgrade(() => ({
|
||||
display: {
|
||||
|
@ -280,7 +287,7 @@ const layer = createLayer(id, () => {
|
|||
spentOnBuyables.value = Decimal.sub(spentOnBuyables.value, cost);
|
||||
}
|
||||
}));
|
||||
const buyablesDisplay = computed(() => (
|
||||
const buyablesDisplay = () => (
|
||||
<Column>
|
||||
<Row>
|
||||
<Toggle
|
||||
|
@ -321,7 +328,7 @@ const layer = createLayer(id, () => {
|
|||
}}
|
||||
/>
|
||||
</Column>
|
||||
));
|
||||
);
|
||||
|
||||
const longBoi = createBar(() => ({
|
||||
fillStyle: { backgroundColor: "#FFFFFF" },
|
||||
|
@ -442,12 +449,11 @@ const layer = createLayer(id, () => {
|
|||
resetDescription: "Melt your points into "
|
||||
}));
|
||||
const resetButtonTooltip = addTooltip(resetButton, () => ({
|
||||
display: computed(() =>
|
||||
display: () =>
|
||||
createModifierSection({
|
||||
title: "Modifiers",
|
||||
modifier: conversionModifier
|
||||
})
|
||||
),
|
||||
}),
|
||||
pinnable: true,
|
||||
direction: Direction.Down,
|
||||
style: { width: "400px", textAlign: "left" }
|
||||
|
|
|
@ -36,7 +36,13 @@ const layer = createLayer(id, () => {
|
|||
const clickable = createClickable(() => ({
|
||||
display: {
|
||||
title: "Clicky clicky!",
|
||||
description: () => <>Current state:<br/>{clickableState.value}</>
|
||||
description: () => (
|
||||
<>
|
||||
Current state:
|
||||
<br />
|
||||
{clickableState.value}
|
||||
</>
|
||||
)
|
||||
},
|
||||
initialState: "Start",
|
||||
canClick() {
|
||||
|
|
|
@ -18,12 +18,13 @@ import "components/common/features.css";
|
|||
import Node from "components/Node.vue";
|
||||
import type { Visibility } from "features/feature";
|
||||
import { isHidden, isVisible } from "features/feature";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { MaybeRef, unref, type CSSProperties } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
id: string;
|
||||
components: MaybeRef<Renderable>[];
|
||||
components: MaybeGetter<Renderable>[];
|
||||
wrappers: ((el: () => Renderable) => Renderable)[];
|
||||
visibility?: MaybeRef<Visibility | boolean>;
|
||||
style?: MaybeRef<CSSProperties>;
|
||||
|
|
|
@ -29,35 +29,7 @@ const props = defineProps<{
|
|||
small: Achievement["small"];
|
||||
}>();
|
||||
|
||||
const Component = () => {
|
||||
if (props.display == null) {
|
||||
return null;
|
||||
} else if (
|
||||
isRef(props.display) ||
|
||||
typeof props.display === "string" ||
|
||||
isJSXElement(props.display)
|
||||
) {
|
||||
return render(props.display);
|
||||
} else {
|
||||
const { requirement, effectDisplay, optionsDisplay } = props.display;
|
||||
return (
|
||||
<span>
|
||||
{requirement ?
|
||||
render(requirement, el => <h3>{el}</h3>) :
|
||||
displayRequirements(props.requirements ?? [])}
|
||||
{effectDisplay ? (
|
||||
<div>
|
||||
{render(effectDisplay, el => <b>{el}</b>)}
|
||||
</div>
|
||||
) : null}
|
||||
{optionsDisplay != null ? (
|
||||
<div class="equal-spaced">
|
||||
{render(optionsDisplay)}
|
||||
</div>
|
||||
) : null}
|
||||
</span>);
|
||||
}
|
||||
};
|
||||
const Component = () => props.display == null ? <></> : render(props.display);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from "game/requirements";
|
||||
import settings, { registerSettingField } from "game/settings";
|
||||
import { camelToTitle } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import {
|
||||
isJSXElement,
|
||||
|
@ -24,7 +24,7 @@ import {
|
|||
vueFeatureMixin,
|
||||
VueFeatureOptions
|
||||
} from "util/vue";
|
||||
import { computed, isRef, MaybeRef, MaybeRefOrGetter, unref, watchEffect } from "vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref, watchEffect } from "vue";
|
||||
import { useToast } from "vue-toastification";
|
||||
import Achievement from "./Achievement.vue";
|
||||
|
||||
|
@ -50,14 +50,15 @@ export interface AchievementOptions extends VueFeatureOptions {
|
|||
requirements?: Requirements;
|
||||
/** The display to use for this achievement. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** Description of the requirement(s) for this achievement. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */
|
||||
requirement?: MaybeRefOrGetter<Renderable>;
|
||||
requirement?: MaybeGetter<Renderable>;
|
||||
/** Description of what will change (if anything) for achieving this. */
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
/** Any additional things to display on this achievement, such as a toggle for it's effect. */
|
||||
optionsDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
optionsDisplay?: MaybeGetter<Renderable>;
|
||||
};
|
||||
/** Toggles a smaller design for the feature. */
|
||||
small?: MaybeRefOrGetter<boolean>;
|
||||
|
@ -76,13 +77,7 @@ export interface Achievement extends VueFeature {
|
|||
/** A function that is called when the achievement is completed. */
|
||||
onComplete?: VoidFunction;
|
||||
/** The display to use for this achievement. */
|
||||
display?:
|
||||
| MaybeRef<Renderable>
|
||||
| {
|
||||
requirement?: MaybeRef<Renderable>;
|
||||
effectDisplay?: MaybeRef<Renderable>;
|
||||
optionsDisplay?: MaybeRef<Renderable>;
|
||||
};
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** Toggles a smaller design for the feature. */
|
||||
small?: MaybeRef<boolean>;
|
||||
/** An image to display as the background for this achievement. */
|
||||
|
@ -105,7 +100,15 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
const earned = persistent<boolean>(false, false);
|
||||
return createLazyProxy(() => {
|
||||
const options = optionsFunc?.() ?? ({} as T);
|
||||
const { requirements, display, small, image, showPopups, onComplete, ...props } = options;
|
||||
const {
|
||||
requirements,
|
||||
display: _display,
|
||||
small,
|
||||
image,
|
||||
showPopups,
|
||||
onComplete,
|
||||
...props
|
||||
} = options;
|
||||
|
||||
const vueFeature = vueFeatureMixin("achievement", options, () => (
|
||||
<Achievement
|
||||
|
@ -117,12 +120,35 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
/>
|
||||
));
|
||||
|
||||
let display: MaybeGetter<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
const { requirement, effectDisplay, optionsDisplay } = _display;
|
||||
display = () => (
|
||||
<span>
|
||||
{requirement == null
|
||||
? displayRequirements(requirements ?? [])
|
||||
: render(requirement, el => <h3>{el}</h3>)}
|
||||
{effectDisplay == null ? null : (
|
||||
<div>
|
||||
{render(effectDisplay, el => (
|
||||
<b>{el}</b>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{optionsDisplay != null ? (
|
||||
<div class="equal-spaced">{render(optionsDisplay)}</div>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
} else if (_display != null) {
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const achievement = {
|
||||
type: AchievementType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof AchievementOptions>),
|
||||
...vueFeature,
|
||||
visibility: computed(() => {
|
||||
const display = unref((achievement as Achievement).display);
|
||||
switch (settings.msDisplay) {
|
||||
default:
|
||||
case AchievementDisplay.All:
|
||||
|
@ -131,9 +157,9 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
if (
|
||||
unref(earned) &&
|
||||
!(
|
||||
display != null &&
|
||||
typeof display === "object" &&
|
||||
"optionsDisplay" in display
|
||||
_display != null &&
|
||||
typeof _display === "object" &&
|
||||
!isJSXElement(_display)
|
||||
)
|
||||
) {
|
||||
return Visibility.None;
|
||||
|
@ -153,19 +179,7 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
small: processGetter(small),
|
||||
image: processGetter(image),
|
||||
showPopups: processGetter(showPopups) ?? true,
|
||||
display:
|
||||
display == null
|
||||
? undefined
|
||||
: isRef(display) ||
|
||||
typeof display === "string" ||
|
||||
typeof display === "function" ||
|
||||
isJSXElement(display)
|
||||
? processGetter(display)
|
||||
: {
|
||||
requirement: processGetter(display.requirement),
|
||||
effectDisplay: processGetter(display.effectDisplay),
|
||||
optionsDisplay: processGetter(display.optionsDisplay)
|
||||
},
|
||||
display,
|
||||
requirements:
|
||||
requirements == null
|
||||
? undefined
|
||||
|
@ -181,19 +195,18 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
earned.value = true;
|
||||
achievement.onComplete?.();
|
||||
if (achievement.display != null && unref(achievement.showPopups) === true) {
|
||||
const display = achievement.display;
|
||||
let Display;
|
||||
if (isRef(display) || typeof display === "string" || isJSXElement(display)) {
|
||||
Display = () => render(display);
|
||||
} else if (display.requirement != null) {
|
||||
Display = () => render(display.requirement!);
|
||||
} else {
|
||||
Display = () => displayRequirements(achievement.requirements ?? []);
|
||||
let display = achievement.display;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
if (_display.requirement != null) {
|
||||
display = _display.requirement;
|
||||
} else {
|
||||
display = displayRequirements(requirements ?? []);
|
||||
}
|
||||
}
|
||||
toast.info(
|
||||
<div>
|
||||
<h3>Achievement earned!</h3>
|
||||
<div>{Display()}</div>
|
||||
<div>{render(display)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Bar from "features/bars/Bar.vue";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import { Direction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import { CSSProperties, MaybeRef, MaybeRefOrGetter } from "vue";
|
||||
|
@ -30,7 +30,7 @@ export interface BarOptions extends VueFeatureOptions {
|
|||
/** The progress value of the bar, from 0 to 1. */
|
||||
progress: MaybeRefOrGetter<DecimalSource>;
|
||||
/** The display to use for this bar. */
|
||||
display?: MaybeRefOrGetter<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
}
|
||||
|
||||
/** An object that represents a feature that displays some sort of progress or completion or resource with a cap. */
|
||||
|
@ -52,7 +52,7 @@ export interface Bar extends VueFeature {
|
|||
/** The progress value of the bar, from 0 to 1. */
|
||||
progress: MaybeRef<DecimalSource>;
|
||||
/** The display to use for this bar. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
type: typeof BarType;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ export function createBar<T extends BarOptions>(optionsFunc: () => T) {
|
|||
textStyle: processGetter(textStyle),
|
||||
fillStyle: processGetter(fillStyle),
|
||||
progress: processGetter(progress),
|
||||
display: processGetter(display)
|
||||
display
|
||||
} satisfies Bar;
|
||||
|
||||
return bar;
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
<script setup lang="tsx">
|
||||
import "components/common/features.css";
|
||||
import { getHighNotifyStyle, getNotifyStyle } from "game/notifications";
|
||||
import { displayRequirements } from "game/requirements";
|
||||
import { render } from "util/vue";
|
||||
import type { Component } from "vue";
|
||||
import { computed, unref } from "vue";
|
||||
|
@ -61,34 +60,7 @@ const notifyStyle = computed(() => {
|
|||
return {};
|
||||
});
|
||||
|
||||
const Component = () => {
|
||||
if (props.display == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof props.display === "object" && "description" in props.display) {
|
||||
const { title, description, goal, reward, effectDisplay } = props.display;
|
||||
return <span>
|
||||
{title != null ? (<div>{render(title, el => <h3>{el}</h3>)}</div>) : null}
|
||||
{render(description, el => <div>{el}</div>)}
|
||||
<div>
|
||||
<br />
|
||||
Goal: {goal == null ? displayRequirements(props.requirements) : render(goal, el => <h3>{el}</h3>)}
|
||||
</div>
|
||||
{reward != null ? (
|
||||
<div>
|
||||
<br />
|
||||
Reward: {render(reward)}
|
||||
</div>
|
||||
) : null}
|
||||
{effectDisplay != null ? (
|
||||
<div>
|
||||
Currently: {render(effectDisplay)}
|
||||
</div>
|
||||
) : null}
|
||||
</span>;
|
||||
}
|
||||
return render(props.display);
|
||||
}
|
||||
const Component = () => props.display == null ? <></> : render(props.display);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -4,13 +4,20 @@ import type { Reset } from "features/reset";
|
|||
import { globalBus } from "game/events";
|
||||
import type { Persistent } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import { Requirements, maxRequirementsMet } from "game/requirements";
|
||||
import { Requirements, displayRequirements, maxRequirementsMet } from "game/requirements";
|
||||
import settings, { registerSettingField } from "game/settings";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal from "util/bignum";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, VueFeatureOptions, vueFeatureMixin } from "util/vue";
|
||||
import {
|
||||
Renderable,
|
||||
VueFeature,
|
||||
VueFeatureOptions,
|
||||
isJSXElement,
|
||||
render,
|
||||
vueFeatureMixin
|
||||
} from "util/vue";
|
||||
import type { MaybeRef, MaybeRefOrGetter, Ref, WatchStopHandle } from "vue";
|
||||
import { computed, unref, watch } from "vue";
|
||||
import Challenge from "./Challenge.vue";
|
||||
|
@ -32,18 +39,19 @@ export interface ChallengeOptions extends VueFeatureOptions {
|
|||
completionLimit?: MaybeRefOrGetter<DecimalSource>;
|
||||
/** The display to use for this challenge. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRefOrGetter<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRefOrGetter<Renderable>;
|
||||
description: MaybeGetter<Renderable>;
|
||||
/** A description of the current goal for this challenge. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */
|
||||
goal?: MaybeRefOrGetter<Renderable>;
|
||||
goal?: MaybeGetter<Renderable>;
|
||||
/** A description of what will change upon completing this challenge. */
|
||||
reward?: MaybeRefOrGetter<Renderable>;
|
||||
reward?: MaybeGetter<Renderable>;
|
||||
/** A description of the current effect of this challenge. */
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
};
|
||||
/** A function that is called when the challenge is completed. */
|
||||
onComplete?: VoidFunction;
|
||||
|
@ -70,20 +78,7 @@ export interface Challenge extends VueFeature {
|
|||
/** The maximum number of times the challenge can be completed. */
|
||||
completionLimit?: MaybeRef<DecimalSource>;
|
||||
/** The display to use for this challenge. */
|
||||
display?:
|
||||
| MaybeRef<Renderable>
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRef<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRef<Renderable>;
|
||||
/** A description of the current goal for this challenge. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */
|
||||
goal?: MaybeRef<Renderable>;
|
||||
/** A description of what will change upon completing this challenge. */
|
||||
reward?: MaybeRef<Renderable>;
|
||||
/** A description of the current effect of this challenge. */
|
||||
effectDisplay?: MaybeRef<Renderable>;
|
||||
};
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** The current amount of times this challenge can be completed. */
|
||||
canComplete: Ref<DecimalSource>;
|
||||
/** The current number of times this challenge has been completed. */
|
||||
|
@ -118,7 +113,7 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T
|
|||
requirements,
|
||||
canStart,
|
||||
completionLimit,
|
||||
display,
|
||||
display: _display,
|
||||
reset,
|
||||
onComplete,
|
||||
onEnter,
|
||||
|
@ -139,6 +134,41 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T
|
|||
/>
|
||||
));
|
||||
|
||||
let display: MaybeGetter<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
const { title, description, goal, reward, effectDisplay } = _display;
|
||||
display = () => (
|
||||
<span>
|
||||
{title != null ? (
|
||||
<div>
|
||||
{render(title, el => (
|
||||
<h3>{el}</h3>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{render(description, el => (
|
||||
<div>{el}</div>
|
||||
))}
|
||||
<div>
|
||||
<br />
|
||||
Goal:{" "}
|
||||
{goal == null
|
||||
? displayRequirements(challenge.requirements)
|
||||
: render(goal, el => <h3>{el}</h3>)}
|
||||
</div>
|
||||
{reward != null ? (
|
||||
<div>
|
||||
<br />
|
||||
Reward: {render(reward)}
|
||||
</div>
|
||||
) : null}
|
||||
{effectDisplay != null ? <div>Currently: {render(effectDisplay)}</div> : null}
|
||||
</span>
|
||||
);
|
||||
} else if (_display != null) {
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const challenge = {
|
||||
type: ChallengeType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof ChallengeOptions>),
|
||||
|
@ -157,18 +187,7 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T
|
|||
onComplete,
|
||||
onEnter,
|
||||
onExit,
|
||||
display:
|
||||
display == null
|
||||
? undefined
|
||||
: typeof display === "object" && "description" in display
|
||||
? {
|
||||
title: processGetter(display.title),
|
||||
description: processGetter(display.description),
|
||||
goal: processGetter(display.goal),
|
||||
reward: processGetter(display.reward),
|
||||
effectDisplay: processGetter(display.effectDisplay)
|
||||
}
|
||||
: processGetter(display),
|
||||
display,
|
||||
toggle: function () {
|
||||
if (active.value) {
|
||||
if (
|
||||
|
|
|
@ -5,11 +5,10 @@ import { persistent } from "game/persistence";
|
|||
import Decimal, { DecimalSource } from "lib/break_eternity";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import { Direction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable, VueFeature, vueFeatureMixin } from "util/vue";
|
||||
import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin } from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, Ref, ref, unref } from "vue";
|
||||
import { JSX } from "vue/jsx-runtime";
|
||||
import { Bar, BarOptions, createBar } from "../bars/bar";
|
||||
import { type Clickable, ClickableOptions } from "./clickable";
|
||||
|
||||
|
@ -39,7 +38,7 @@ export interface Action extends VueFeature {
|
|||
/** Whether or not the action may be performed. */
|
||||
canClick: MaybeRef<boolean>;
|
||||
/** The display to use for this action. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** A function that is called when the action is clicked. */
|
||||
onClick: (amount: DecimalSource) => void;
|
||||
/** Whether or not the player is holding down the action. Actions will be considered clicked as soon as the cooldown completes when being held down. */
|
||||
|
@ -62,8 +61,16 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) {
|
|||
const progress = persistent<DecimalSource>(0);
|
||||
return createLazyProxy(() => {
|
||||
const options = optionsFunc?.() ?? ({} as T);
|
||||
const { style, duration, canClick, autoStart, display, barOptions, onClick, ...props } =
|
||||
options;
|
||||
const {
|
||||
style,
|
||||
duration,
|
||||
canClick,
|
||||
autoStart,
|
||||
display: _display,
|
||||
barOptions,
|
||||
onClick,
|
||||
...props
|
||||
} = options;
|
||||
|
||||
const processedCanClick = processGetter(canClick) ?? true;
|
||||
const processedStyle = processGetter(style);
|
||||
|
@ -78,29 +85,24 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) {
|
|||
...(barOptions as Omit<typeof barOptions, keyof VueFeature>)
|
||||
}));
|
||||
|
||||
let Component: () => JSX.Element;
|
||||
if (typeof display === "object" && "description" in display) {
|
||||
const title = processGetter(display.title);
|
||||
const description = processGetter(display.description);
|
||||
|
||||
const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>));
|
||||
const Description = () => render(description, el => <div>{el}</div>);
|
||||
|
||||
Component = () => {
|
||||
return (
|
||||
<span>
|
||||
{title != null ? (
|
||||
<div>
|
||||
<Title />
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
} else if (display != null) {
|
||||
const processedDisplay = processGetter(display);
|
||||
Component = () => render(processedDisplay);
|
||||
let display: MaybeGetter<Renderable>;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
display = () => (
|
||||
<span>
|
||||
{_display.title != null ? (
|
||||
<div>
|
||||
{render(_display.title, el => (
|
||||
<h3>{el}</h3>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{render(_display.description, el => (
|
||||
<div>{el}</div>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
} else if (_display != null) {
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const action = {
|
||||
|
@ -135,14 +137,14 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) {
|
|||
unref(processedCanClick) && Decimal.gte(progress.value, unref(action.duration))
|
||||
),
|
||||
autoStart: processGetter(autoStart) ?? false,
|
||||
display: computed(() => (
|
||||
display: () => (
|
||||
<>
|
||||
<div style="flex-grow: 1" />
|
||||
{display == null ? null : <Component />}
|
||||
{display == null ? null : render(display)}
|
||||
<div style="flex-grow: 1" />
|
||||
{render(progressBar)}
|
||||
</>
|
||||
)),
|
||||
),
|
||||
progressBar,
|
||||
onClick: function () {
|
||||
if (unref(action.canClick) === false) {
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import Clickable from "features/clickables/Clickable.vue";
|
||||
import type { BaseLayer } from "game/layers";
|
||||
import type { Unsubscribe } from "nanoevents";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import {
|
||||
isJSXElement,
|
||||
render,
|
||||
Renderable,
|
||||
VueFeature,
|
||||
vueFeatureMixin,
|
||||
VueFeatureOptions
|
||||
} from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||
|
||||
/** A symbol used to identify {@link Clickable} features. */
|
||||
|
@ -17,12 +24,13 @@ export interface ClickableOptions extends VueFeatureOptions {
|
|||
canClick?: MaybeRefOrGetter<boolean>;
|
||||
/** The display to use for this clickable. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRefOrGetter<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRefOrGetter<Renderable>;
|
||||
description: MaybeGetter<Renderable>;
|
||||
};
|
||||
/** A function that is called when the clickable is clicked. */
|
||||
onClick?: (e?: MouseEvent | TouchEvent) => void;
|
||||
|
@ -39,7 +47,7 @@ export interface Clickable extends VueFeature {
|
|||
/** Whether or not the clickable may be clicked. */
|
||||
canClick: MaybeRef<boolean>;
|
||||
/** The display to use for this clickable. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
type: typeof ClickableType;
|
||||
}
|
||||
|
@ -53,26 +61,20 @@ export function createClickable<T extends ClickableOptions>(optionsFunc?: () =>
|
|||
const options = optionsFunc?.() ?? ({} as T);
|
||||
const { canClick, display: _display, onClick: onClick, onHold: onHold, ...props } = options;
|
||||
|
||||
let display: MaybeRef<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && "description" in _display) {
|
||||
const title = processGetter(_display.title);
|
||||
const description = processGetter(_display.description);
|
||||
|
||||
const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>));
|
||||
const Description = () => render(description, el => <div>{el}</div>);
|
||||
|
||||
display = computed(() => (
|
||||
let display: MaybeGetter<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
display = () => (
|
||||
<span>
|
||||
{title != null ? (
|
||||
{_display.title != null ? (
|
||||
<div>
|
||||
<Title />
|
||||
{render(_display.title, el => <h3>{el}</h3>)}
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
{render(_display.description, el => <div>{el}</div>)}
|
||||
</span>
|
||||
));
|
||||
);
|
||||
} else if (_display != null) {
|
||||
display = processGetter(_display);
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const clickable = {
|
||||
|
|
|
@ -11,11 +11,11 @@ import {
|
|||
} from "game/requirements";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal, { formatWhole } from "util/bignum";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin } from "util/vue";
|
||||
import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { computed, isRef, unref } from "vue";
|
||||
import { computed, unref } from "vue";
|
||||
import { ClickableOptions } from "./clickable";
|
||||
|
||||
/** A symbol used to identify {@link Repeatable} features. */
|
||||
|
@ -31,14 +31,15 @@ export interface RepeatableOptions extends ClickableOptions {
|
|||
initialAmount?: DecimalSource;
|
||||
/** The display to use for this repeatable. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRefOrGetter<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRefOrGetter<Renderable>;
|
||||
description: MaybeGetter<Renderable>;
|
||||
/** A description of the current effect of this repeatable, based off its amount. */
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
/** Whether or not to show the current amount of this repeatable at the bottom of the display. */
|
||||
showAmount?: boolean;
|
||||
};
|
||||
|
@ -53,7 +54,7 @@ export interface Repeatable extends VueFeature {
|
|||
/** The initial amount this repeatable has on a new save / after reset. */
|
||||
initialAmount?: DecimalSource;
|
||||
/** The display to use for this repeatable. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** Whether or not the repeatable may be clicked. */
|
||||
canClick: Ref<boolean>;
|
||||
/** A function that is called when the repeatable is clicked. */
|
||||
|
@ -119,25 +120,19 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () =>
|
|||
}
|
||||
|
||||
let display;
|
||||
if (typeof _display === "object" && !isRef(_display) && !isJSXElement(_display)) {
|
||||
const title = processGetter(_display.title);
|
||||
const description = processGetter(_display.description);
|
||||
const effectDisplay = processGetter(_display.effectDisplay);
|
||||
const showAmount = processGetter(_display.showAmount);
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
const { title, description, effectDisplay, showAmount } = _display;
|
||||
|
||||
const Title = title == null ? null : () => render(title, el => <h3>{el}</h3>);
|
||||
const Description = () => render(description, el => <>{el}</>);
|
||||
const EffectDisplay =
|
||||
effectDisplay == null ? null : () => render(effectDisplay, el => <>{el}</>);
|
||||
|
||||
display = computed(() => (
|
||||
display = () => (
|
||||
<span>
|
||||
{Title == null ? null : (
|
||||
{title == null ? null : (
|
||||
<div>
|
||||
<Title />
|
||||
{render(title, el => (
|
||||
<h3>{el}</h3>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Description />
|
||||
{render(description)}
|
||||
{showAmount === false ? null : (
|
||||
<div>
|
||||
<br />
|
||||
|
@ -147,10 +142,10 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () =>
|
|||
) : undefined}
|
||||
</div>
|
||||
)}
|
||||
{EffectDisplay == null ? null : (
|
||||
{effectDisplay == null ? null : (
|
||||
<div>
|
||||
<br />
|
||||
Currently: <EffectDisplay />
|
||||
Currently: {render(effectDisplay)}
|
||||
</div>
|
||||
)}
|
||||
{unref(repeatable.maxed) ? null : (
|
||||
|
@ -160,12 +155,9 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () =>
|
|||
</div>
|
||||
)}
|
||||
</span>
|
||||
));
|
||||
);
|
||||
} else if (_display != null) {
|
||||
const processedDisplay = processGetter(_display);
|
||||
display = computed(() => render(processedDisplay));
|
||||
} else {
|
||||
display = undefined;
|
||||
display = _display;
|
||||
}
|
||||
|
||||
amount[DefaultValue] = initialAmount ?? 0;
|
||||
|
|
|
@ -10,9 +10,16 @@ import {
|
|||
requirementsMet
|
||||
} from "game/requirements";
|
||||
import { isFunction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, VueFeatureOptions, render, vueFeatureMixin } from "util/vue";
|
||||
import {
|
||||
Renderable,
|
||||
VueFeature,
|
||||
VueFeatureOptions,
|
||||
isJSXElement,
|
||||
render,
|
||||
vueFeatureMixin
|
||||
} from "util/vue";
|
||||
import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { computed, unref } from "vue";
|
||||
import Clickable from "./Clickable.vue";
|
||||
|
@ -27,14 +34,15 @@ export const UpgradeType = Symbol("Upgrade");
|
|||
export interface UpgradeOptions extends VueFeatureOptions, ClickableOptions {
|
||||
/** The display to use for this upgrade. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRefOrGetter<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRefOrGetter<Renderable>;
|
||||
description: MaybeGetter<Renderable>;
|
||||
/** A description of the current effect of the achievement. Useful when the effect changes dynamically. */
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
};
|
||||
/** The requirements to purchase this upgrade. */
|
||||
requirements: Requirements;
|
||||
|
@ -47,7 +55,7 @@ export interface Upgrade extends VueFeature {
|
|||
/** The requirements to purchase this upgrade. */
|
||||
requirements: Requirements;
|
||||
/** The display to use for this upgrade. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** Whether or not this upgrade has been purchased. */
|
||||
bought: Persistent<boolean>;
|
||||
/** Whether or not the upgrade can currently be purchased. */
|
||||
|
@ -92,30 +100,23 @@ export function createUpgrade<T extends UpgradeOptions>(optionsFunc: () => T) {
|
|||
requirements.push(createVisibilityRequirement(vueFeature.visibility));
|
||||
}
|
||||
|
||||
let display: MaybeRef<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && "description" in _display) {
|
||||
const title = processGetter(_display.title);
|
||||
const description = processGetter(_display.description);
|
||||
const effectDisplay = processGetter(_display.effectDisplay);
|
||||
let display;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
const { title, description, effectDisplay } = _display;
|
||||
|
||||
const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>));
|
||||
const Description = () => render(description, el => <div>{el}</div>);
|
||||
const EffectDisplay = () =>
|
||||
effectDisplay == null ? <></> : render(effectDisplay, el => <>{el}</>);
|
||||
|
||||
display = computed(() => (
|
||||
display = () => (
|
||||
<span>
|
||||
{title != null ? (
|
||||
<div>
|
||||
<Title />
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
{effectDisplay != null ? (
|
||||
<div>
|
||||
Currently: <EffectDisplay />
|
||||
{render(title, el => (
|
||||
<h3>{el}</h3>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{render(description, el => (
|
||||
<div>{el}</div>
|
||||
))}
|
||||
{effectDisplay != null ? <div>Currently: {render(effectDisplay)}</div> : null}
|
||||
{bought.value ? null : (
|
||||
<>
|
||||
<br />
|
||||
|
@ -123,9 +124,9 @@ export function createUpgrade<T extends UpgradeOptions>(optionsFunc: () => T) {
|
|||
</>
|
||||
)}
|
||||
</span>
|
||||
));
|
||||
);
|
||||
} else if (_display != null) {
|
||||
display = processGetter(_display);
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const upgrade = {
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { BaseLayer } from "game/layers";
|
|||
import { createBooleanRequirement } from "game/requirements";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal from "util/bignum";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable } from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||
|
@ -310,7 +310,7 @@ export function setupPassiveGeneration(
|
|||
export function createCanConvertRequirement(
|
||||
conversion: Conversion,
|
||||
minGainAmount: MaybeRefOrGetter<DecimalSource> = 1,
|
||||
display?: MaybeRefOrGetter<Renderable>
|
||||
display?: MaybeGetter<Renderable>
|
||||
) {
|
||||
const computedMinGainAmount = processGetter(minGainAmount);
|
||||
return createBooleanRequirement(
|
||||
|
|
|
@ -47,9 +47,12 @@ export function findFeatures(obj: object, ...types: symbol[]): unknown[] {
|
|||
const handleObject = (obj: object) => {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value: unknown = obj[key as keyof typeof obj];
|
||||
if (value != null && typeof value === "object") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (types.includes((value as Record<string, any>).type)) {
|
||||
if (
|
||||
value != null &&
|
||||
typeof value === "object" &&
|
||||
(value as Record<string, unknown>).__v_isVNode !== true
|
||||
) {
|
||||
if (types.includes((value as Record<string, unknown>).type as symbol)) {
|
||||
objects.push(value);
|
||||
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
||||
handleObject(value as Record<string, unknown>);
|
||||
|
@ -66,7 +69,7 @@ export function getFirstFeature<T extends VueFeature>(
|
|||
filter: (feature: T) => boolean
|
||||
): {
|
||||
firstFeature: Ref<T | undefined>;
|
||||
collapsedContent: MaybeRef<Renderable>;
|
||||
collapsedContent: () => Renderable;
|
||||
hasCollapsedContent: Ref<boolean>;
|
||||
} {
|
||||
const filteredFeatures = computed(() =>
|
||||
|
@ -74,7 +77,7 @@ export function getFirstFeature<T extends VueFeature>(
|
|||
);
|
||||
return {
|
||||
firstFeature: computed(() => filteredFeatures.value[0]),
|
||||
collapsedContent: computed(() => renderCol(...filteredFeatures.value.slice(1))),
|
||||
collapsedContent: () => renderCol(...filteredFeatures.value.slice(1)),
|
||||
hasCollapsedContent: computed(() => filteredFeatures.value.length > 1)
|
||||
};
|
||||
}
|
||||
|
@ -90,13 +93,13 @@ export function excludeFeatures(obj: Record<string, unknown>, ...types: symbol[]
|
|||
const handleObject = (obj: Record<string, unknown>) => {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key];
|
||||
if (value != null && typeof value === "object") {
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
typeof (value as Record<string, any>).type === "symbol" &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
!types.includes((value as Record<string, any>).type)
|
||||
) {
|
||||
if (
|
||||
value != null &&
|
||||
typeof value === "object" &&
|
||||
(value as Record<string, unknown>).__v_isVNode !== true
|
||||
) {
|
||||
const type = (value as Record<string, unknown>).type;
|
||||
if (typeof type === "symbol" && !types.includes(type)) {
|
||||
objects.push(value);
|
||||
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
||||
handleObject(value as Record<string, unknown>);
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Column from "components/layout/Column.vue";
|
||||
import Row from "components/layout/Row.vue";
|
||||
import Clickable from "features/clickables/Clickable.vue";
|
||||
import { getUniqueID, Visibility } from "features/feature";
|
||||
import type { Persistent, State } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import { isFunction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import {
|
||||
isJSXElement,
|
||||
render,
|
||||
Renderable,
|
||||
VueFeature,
|
||||
vueFeatureMixin,
|
||||
VueFeatureOptions
|
||||
} from "util/vue";
|
||||
import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { computed, isRef, unref } from "vue";
|
||||
import Column from "components/layout/Column.vue";
|
||||
import Row from "components/layout/Row.vue";
|
||||
import Clickable from "features/clickables/Clickable.vue";
|
||||
import { computed, unref } from "vue";
|
||||
|
||||
/** A symbol used to identify {@link Grid} features. */
|
||||
export const GridType = Symbol("Grid");
|
||||
|
@ -39,7 +46,7 @@ export interface GridCell extends VueFeature {
|
|||
/** The persistent state of this cell. */
|
||||
state: State;
|
||||
/** The main text that appears in the display. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** A function that is called when the cell is clicked. */
|
||||
onClick?: (e?: MouseEvent | TouchEvent) => void;
|
||||
/** A function that is called when the cell is held down. */
|
||||
|
@ -65,10 +72,13 @@ export interface GridOptions extends VueFeatureOptions {
|
|||
/** A getter for the CSS classes for a cell. */
|
||||
getClasses?: CellMaybeRefOrGetter<Record<string, boolean>>;
|
||||
/** A getter for the display component for a cell. */
|
||||
getDisplay: CellMaybeRefOrGetter<Renderable> | {
|
||||
getTitle?: CellMaybeRefOrGetter<Renderable>;
|
||||
getDescription: CellMaybeRefOrGetter<Renderable>
|
||||
};
|
||||
getDisplay:
|
||||
| Renderable
|
||||
| ((row: number, col: number, state: State) => Renderable)
|
||||
| {
|
||||
getTitle?: Renderable | ((row: number, col: number, state: State) => Renderable);
|
||||
getDescription: Renderable | ((row: number, col: number, state: State) => Renderable);
|
||||
};
|
||||
/** A function that is called when a cell is clicked. */
|
||||
onClick?: (row: number, col: number, state: State, e?: MouseEvent | TouchEvent) => void;
|
||||
/** A function that is called when a cell is held down. */
|
||||
|
@ -96,7 +106,7 @@ export interface Grid extends VueFeature {
|
|||
/** A getter for the CSS classes for a cell. */
|
||||
getClasses?: ProcessedCellRefOrGetter<Record<string, boolean>>;
|
||||
/** A getter for the display component for a cell. */
|
||||
getDisplay: ProcessedCellRefOrGetter<Renderable>;
|
||||
getDisplay: Renderable | ((row: number, col: number, state: State) => Renderable);
|
||||
/** Get the auto-generated ID for identifying a specific cell of this grid that appears in the DOM. Will not persist between refreshes or updates. */
|
||||
getID: (row: number, col: number, state: State) => string;
|
||||
/** Get the persistent state of the given cell. */
|
||||
|
@ -214,7 +224,7 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell {
|
|||
return grid.getState(row, col);
|
||||
}
|
||||
case "id":
|
||||
return target.id = target.id ?? getUniqueID("gridcell");
|
||||
return (target.id = target.id ?? getUniqueID("gridcell"));
|
||||
case "components":
|
||||
return [
|
||||
computed(() => (
|
||||
|
@ -227,7 +237,7 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell {
|
|||
))
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
if (typeof key === "symbol") {
|
||||
return (grid as any)[key];
|
||||
}
|
||||
|
@ -264,12 +274,10 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell {
|
|||
return (grid as any)[key];
|
||||
},
|
||||
set(target, key, value) {
|
||||
console.log("!!?", key, value)
|
||||
if (typeof key !== "string") {
|
||||
return false;
|
||||
}
|
||||
key = `set${key.slice(0, 1).toUpperCase() + key.slice(1)}`;
|
||||
console.log(key, grid[key])
|
||||
if (key in grid && isFunction((grid as any)[key]) && (grid as any)[key].length <= 3) {
|
||||
(grid as any)[key].call(grid, row, col, value);
|
||||
return true;
|
||||
|
@ -334,20 +342,23 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) {
|
|||
} = options;
|
||||
|
||||
let getDisplay;
|
||||
if (typeof _getDisplay === "object" && !isRef(_getDisplay) && !isJSXElement(_getDisplay)) {
|
||||
if (typeof _getDisplay === "object" && !isJSXElement(_getDisplay)) {
|
||||
const { getTitle, getDescription } = _getDisplay;
|
||||
const getProcessedTitle = convertCellMaybeRefOrGetter(getTitle);
|
||||
const getProcessedDescription = convertCellMaybeRefOrGetter(getDescription);
|
||||
getDisplay = function(row: number, col: number, state: State) {
|
||||
const title = typeof getProcessedTitle === "function" ? getProcessedTitle(row, col, state) : unref(getProcessedTitle);
|
||||
const description = typeof getProcessedDescription === "function" ? getProcessedDescription(row, col, state) : unref(getProcessedDescription);
|
||||
return <>
|
||||
{title}
|
||||
{description}
|
||||
</>;
|
||||
}
|
||||
getDisplay = function (row: number, col: number, state: State) {
|
||||
const title = typeof getTitle === "function" ? getTitle(row, col, state) : getTitle;
|
||||
const description =
|
||||
typeof getDescription === "function"
|
||||
? getDescription(row, col, state)
|
||||
: getDescription;
|
||||
return (
|
||||
<>
|
||||
{title}
|
||||
{description}
|
||||
</>
|
||||
);
|
||||
};
|
||||
} else {
|
||||
getDisplay = convertCellMaybeRefOrGetter(_getDisplay);
|
||||
getDisplay = _getDisplay;
|
||||
}
|
||||
|
||||
const grid = {
|
||||
|
@ -357,10 +368,13 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) {
|
|||
<Column>
|
||||
{new Array(unref(grid.rows)).fill(0).map((_, row) => (
|
||||
<Row>
|
||||
{new Array(unref(grid.cols)).fill(0).map((_, col) =>
|
||||
render(grid.cells[row][col]))}
|
||||
</Row>))}
|
||||
</Column>)),
|
||||
{new Array(unref(grid.cols))
|
||||
.fill(0)
|
||||
.map((_, col) => render(grid.cells[row][col]))}
|
||||
</Row>
|
||||
))}
|
||||
</Column>
|
||||
)),
|
||||
cellState,
|
||||
cells: new Proxy({} as GridCell[][], {
|
||||
get(target, key: PropertyKey) {
|
||||
|
@ -422,8 +436,10 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) {
|
|||
cols: processGetter(cols),
|
||||
getVisibility: convertCellMaybeRefOrGetter(getVisibility ?? true),
|
||||
getCanClick: convertCellMaybeRefOrGetter(getCanClick ?? true),
|
||||
getStartState: typeof getStartState === "function" && getStartState.length > 0 ?
|
||||
getStartState : processGetter(getStartState),
|
||||
getStartState:
|
||||
typeof getStartState === "function" && getStartState.length > 0
|
||||
? getStartState
|
||||
: processGetter(getStartState),
|
||||
getStyle: convertCellMaybeRefOrGetter(getStyle),
|
||||
getClasses: convertCellMaybeRefOrGetter(getClasses),
|
||||
getDisplay,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Infobox from "features/infoboxes/Infobox.vue";
|
||||
import type { Persistent } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import { CSSProperties, MaybeRef, MaybeRefOrGetter } from "vue";
|
||||
|
@ -20,9 +20,9 @@ export interface InfoboxOptions extends VueFeatureOptions {
|
|||
/** CSS to apply to the body of the infobox. */
|
||||
bodyStyle?: MaybeRefOrGetter<CSSProperties>;
|
||||
/** A header to appear at the top of the display. */
|
||||
title: MaybeRefOrGetter<Renderable>;
|
||||
title: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
}
|
||||
|
||||
/** An object that represents a feature that displays information in a collapsible way. */
|
||||
|
@ -34,9 +34,9 @@ export interface Infobox extends VueFeature {
|
|||
/** CSS to apply to the body of the infobox. */
|
||||
bodyStyle?: MaybeRef<CSSProperties>;
|
||||
/** A header to appear at the top of the display. */
|
||||
title: MaybeRef<Renderable>;
|
||||
title: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** Whether or not this infobox is collapsed. */
|
||||
collapsed: Persistent<boolean>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
|
@ -70,8 +70,8 @@ export function createInfobox<T extends InfoboxOptions>(optionsFunc: () => T) {
|
|||
color: processGetter(color) ?? "--layer-color",
|
||||
titleStyle: processGetter(titleStyle),
|
||||
bodyStyle: processGetter(bodyStyle),
|
||||
title: processGetter(title),
|
||||
display: processGetter(display)
|
||||
title,
|
||||
display
|
||||
} satisfies Infobox;
|
||||
|
||||
return infobox;
|
||||
|
|
|
@ -41,6 +41,7 @@ onMounted(() => {
|
|||
});
|
||||
onBeforeUnmount(() => {
|
||||
app.value?.destroy();
|
||||
app.value = null;
|
||||
});
|
||||
|
||||
let isDirty = true;
|
||||
|
|
|
@ -3,8 +3,9 @@ import type { EmitterConfigV3 } from "@pixi/particle-emitter";
|
|||
import { Emitter, upgradeConfig } from "@pixi/particle-emitter";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import { Ref, shallowRef } from "vue";
|
||||
import { Ref, shallowRef, unref } from "vue";
|
||||
import Particles from "./Particles.vue";
|
||||
import { processGetter } from "util/computed";
|
||||
|
||||
/** A symbol used to identify {@link Particles} features. */
|
||||
export const ParticlesType = Symbol("Particles");
|
||||
|
@ -47,7 +48,10 @@ export interface Particles extends VueFeature {
|
|||
export function createParticles<T extends ParticlesOptions>(optionsFunc?: () => T) {
|
||||
return createLazyProxy(() => {
|
||||
const options = optionsFunc?.() ?? ({} as T);
|
||||
const { onContainerResized, onHotReload, ...props } = options;
|
||||
const { onContainerResized, onHotReload, style: _style, ...props } = options;
|
||||
|
||||
const style = processGetter(_style);
|
||||
options.style = () => ({ position: "static", ...(unref(style) ?? {}) });
|
||||
|
||||
let emittersToAdd: {
|
||||
resolve: (value: Emitter | PromiseLike<Emitter>) => void;
|
||||
|
@ -57,6 +61,7 @@ export function createParticles<T extends ParticlesOptions>(optionsFunc?: () =>
|
|||
function onInit(app: Application) {
|
||||
emittersToAdd.forEach(({ resolve, config }) => resolve(new Emitter(app.stage, config)));
|
||||
emittersToAdd = [];
|
||||
particles.app.value = app;
|
||||
}
|
||||
|
||||
const particles = {
|
||||
|
|
|
@ -19,15 +19,16 @@ import Sticky from "components/layout/Sticky.vue";
|
|||
import type { Resource } from "features/resources/resource";
|
||||
import ResourceVue from "features/resources/Resource.vue";
|
||||
import Decimal from "util/bignum";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { Renderable } from "util/vue";
|
||||
import { computed, MaybeRefOrGetter, ref, StyleValue, toValue } from "vue";
|
||||
import { computed, ref, StyleValue, toValue } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
resource: Resource;
|
||||
color?: string;
|
||||
classes?: Record<string, boolean>;
|
||||
style?: StyleValue;
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
}>();
|
||||
|
||||
const displayRef = ref<Element | null>(null);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import { MaybeRef, MaybeRefOrGetter } from "vue";
|
||||
import { JSX } from "vue/jsx-runtime";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
|
||||
/** A symbol used to identify {@link Tab} features. */
|
||||
export const TabType = Symbol("Tab");
|
||||
|
@ -12,7 +10,7 @@ export const TabType = Symbol("Tab");
|
|||
*/
|
||||
export interface TabOptions extends VueFeatureOptions {
|
||||
/** The display to use for this tab. */
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,7 +19,7 @@ export interface TabOptions extends VueFeatureOptions {
|
|||
*/
|
||||
export interface Tab extends VueFeature {
|
||||
/** The display to use for this tab. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
type: typeof TabType;
|
||||
}
|
||||
|
@ -38,8 +36,8 @@ export function createTab<T extends TabOptions>(optionsFunc: () => T) {
|
|||
const tab = {
|
||||
type: TabType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof TabOptions>),
|
||||
...vueFeatureMixin("tab", options, (): JSX.Element => render(tab.display)),
|
||||
display: processGetter(display)
|
||||
...vueFeatureMixin("tab", options, display),
|
||||
display
|
||||
} satisfies Tab;
|
||||
|
||||
return tab;
|
||||
|
|
|
@ -4,7 +4,7 @@ import TabButton from "features/tabs/TabButton.vue";
|
|||
import TabFamily from "features/tabs/TabFamily.vue";
|
||||
import type { Persistent } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
|
@ -20,9 +20,9 @@ export const TabFamilyType = Symbol("TabFamily");
|
|||
*/
|
||||
export interface TabButtonOptions extends VueFeatureOptions {
|
||||
/** The tab to display when this button is clicked. */
|
||||
tab: Tab | MaybeRefOrGetter<Renderable>;
|
||||
tab: Tab | MaybeGetter<Renderable>;
|
||||
/** The label on this button. */
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** The color of the glow effect to display when this button is active. */
|
||||
glowColor?: MaybeRefOrGetter<string>;
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ export interface TabButtonOptions extends VueFeatureOptions {
|
|||
*/
|
||||
export interface TabButton extends VueFeature {
|
||||
/** The tab to display when this button is clicked. */
|
||||
tab: Tab | MaybeRef<Renderable>;
|
||||
tab: Tab | MaybeGetter<Renderable>;
|
||||
/** The label on this button. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** The color of the glow effect to display when this button is active. */
|
||||
glowColor?: MaybeRef<string>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
|
@ -64,7 +64,7 @@ export interface TabFamily extends VueFeature {
|
|||
/** All the tabs within this family. */
|
||||
tabs: Record<string, TabButton>;
|
||||
/** The currently active tab, if any. */
|
||||
activeTab: Ref<Tab | MaybeRef<Renderable> | null>;
|
||||
activeTab: Ref<Tab | MaybeGetter<Renderable> | null>;
|
||||
/** The name of the tab that is currently active. */
|
||||
selected: Persistent<string>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
|
@ -106,16 +106,17 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
|||
const tabButton = {
|
||||
type: TabButtonType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof TabButtonOptions>),
|
||||
...vueFeatureMixin("tabButton", options, () =>
|
||||
...vueFeatureMixin("tabButton", options, () => (
|
||||
<TabButton
|
||||
display={tabButton.display}
|
||||
glowColor={tabButton.glowColor}
|
||||
active={unref(tabButton.tab) === unref(tabFamily.activeTab)}
|
||||
onSelectTab={() => tabFamily.selected.value = tab}
|
||||
/>),
|
||||
tab: processGetter(buttonTab),
|
||||
onSelectTab={() => (tabFamily.selected.value = tab)}
|
||||
/>
|
||||
)),
|
||||
tab: buttonTab,
|
||||
glowColor: processGetter(glowColor),
|
||||
display: processGetter(display)
|
||||
display
|
||||
} satisfies TabButton;
|
||||
|
||||
parsedTabs[tab] = tabButton;
|
||||
|
@ -124,7 +125,7 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
|||
buttonContainerClasses: processGetter(buttonContainerClasses),
|
||||
buttonContainerStyle: processGetter(buttonContainerStyle),
|
||||
selected,
|
||||
activeTab: computed((): Tab | MaybeRef<Renderable> | null => {
|
||||
activeTab: computed((): Tab | MaybeGetter<Renderable> | null => {
|
||||
if (
|
||||
selected.value in tabFamily.tabs &&
|
||||
isVisible(tabFamily.tabs[selected.value].visibility ?? true)
|
||||
|
|
|
@ -7,11 +7,11 @@ import TreeNode from "features/trees/TreeNode.vue";
|
|||
import { noPersist } from "game/persistence";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal, { format, formatWhole } from "util/bignum";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { computed, ref, shallowRef, unref } from "vue";
|
||||
import { ref, shallowRef, unref } from "vue";
|
||||
|
||||
/** A symbol used to identify {@link TreeNode} features. */
|
||||
export const TreeNodeType = Symbol("TreeNode");
|
||||
|
@ -27,7 +27,7 @@ export interface TreeNodeOptions extends VueFeatureOptions {
|
|||
/** The background color for this node. */
|
||||
color?: MaybeRefOrGetter<string>;
|
||||
/** The label to display on this tree node. */
|
||||
display?: MaybeRefOrGetter<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** The color of the glow effect shown to notify the user there's something to do with this node. */
|
||||
glowColor?: MaybeRefOrGetter<string>;
|
||||
/** A reset object attached to this node, used for propagating resets through the tree. */
|
||||
|
@ -47,7 +47,7 @@ export interface TreeNode extends VueFeature {
|
|||
/** The background color for this node. */
|
||||
color?: MaybeRef<string>;
|
||||
/** The label to display on this tree node. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** The color of the glow effect shown to notify the user there's something to do with this node. */
|
||||
glowColor?: MaybeRef<string>;
|
||||
/** A reset object attached to this node, used for propagating resets through the tree. */
|
||||
|
@ -84,7 +84,7 @@ export function createTreeNode<T extends TreeNodeOptions>(optionsFunc?: () => T)
|
|||
)),
|
||||
canClick: processGetter(canClick) ?? true,
|
||||
color: processGetter(color),
|
||||
display: processGetter(display),
|
||||
display,
|
||||
glowColor: processGetter(glowColor),
|
||||
onClick:
|
||||
onClick == null
|
||||
|
@ -265,9 +265,9 @@ export function createResourceTooltip(
|
|||
resource: Resource,
|
||||
requiredResource: Resource | null = null,
|
||||
requirement: MaybeRefOrGetter<DecimalSource> = 0
|
||||
): Ref<string> {
|
||||
): () => string {
|
||||
const req = processGetter(requirement);
|
||||
return computed(() => {
|
||||
return () => {
|
||||
if (requiredResource == null || Decimal.gte(resource.value, unref(req))) {
|
||||
return displayResource(resource) + " " + resource.displayName;
|
||||
}
|
||||
|
@ -280,5 +280,5 @@ export function createResourceTooltip(
|
|||
? formatWhole(requiredResource.value)
|
||||
: format(requiredResource.value, requiredResource.precision)
|
||||
})`;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -275,7 +275,18 @@ export function makeDraggable<T, S extends MakeDraggableOptions<T>>(
|
|||
const position = persistent<NodePosition>({ x: 0, y: 0 });
|
||||
const draggable = createLazyProxy(() => {
|
||||
const options = optionsFunc();
|
||||
const { id, nodeBeingDragged, hasDragged, dragDelta, startDrag, endDrag, onMouseDown, onMouseUp, initialPosition, ...props } = options;
|
||||
const {
|
||||
id,
|
||||
nodeBeingDragged,
|
||||
hasDragged,
|
||||
dragDelta,
|
||||
startDrag,
|
||||
endDrag,
|
||||
onMouseDown,
|
||||
onMouseUp,
|
||||
initialPosition,
|
||||
...props
|
||||
} = options;
|
||||
|
||||
position[DefaultValue] = initialPosition ?? position[DefaultValue];
|
||||
|
||||
|
@ -323,7 +334,7 @@ export function makeDraggable<T, S extends MakeDraggableOptions<T>>(
|
|||
|
||||
runAfterEvaluation(element, el => {
|
||||
draggable.id; // Ensure draggable gets evaluated
|
||||
(el as VueFeature & { draggable: Draggable<T> }).draggable = draggable;
|
||||
(el as VueFeature & { draggable: Draggable<T> }).draggable = draggable;
|
||||
element.wrappers.push(el => (
|
||||
<Draggable
|
||||
mouseDown={draggable.onMouseDown}
|
||||
|
|
|
@ -5,9 +5,9 @@ import { persistent } from "game/persistence";
|
|||
import player from "game/player";
|
||||
import type { Emitter } from "nanoevents";
|
||||
import { createNanoEvents } from "nanoevents";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { Renderable } from "util/vue";
|
||||
import {
|
||||
computed,
|
||||
type CSSProperties,
|
||||
|
@ -108,7 +108,7 @@ export interface LayerOptions {
|
|||
* The layout of this layer's features.
|
||||
* When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is displayed.
|
||||
*/
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** An object of classes that should be applied to the display. */
|
||||
classes?: MaybeRefOrGetter<Record<string, boolean>>;
|
||||
/** Styles that should be applied to the display. */
|
||||
|
@ -127,7 +127,7 @@ export interface LayerOptions {
|
|||
* The layout of this layer's features.
|
||||
* When the layer is open in {@link game/player.PlayerData.tabs}, but the tab is {@link Layer.minimized} this is the content that is displayed.
|
||||
*/
|
||||
minimizedDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
minimizedDisplay?: MaybeGetter<Renderable>;
|
||||
/**
|
||||
* Whether or not to force the go back button to be hidden.
|
||||
* If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}.
|
||||
|
@ -169,7 +169,7 @@ export interface Layer extends BaseLayer {
|
|||
* The layout of this layer's features.
|
||||
* When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is displayed.
|
||||
*/
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** An object of classes that should be applied to the display. */
|
||||
classes?: MaybeRef<Record<string, boolean>>;
|
||||
/** Styles that should be applied to the display. */
|
||||
|
@ -188,7 +188,7 @@ export interface Layer extends BaseLayer {
|
|||
* The layout of this layer's features.
|
||||
* When the layer is open in {@link game/player.PlayerData.tabs}, but the tab is {@link Layer.minimized} this is the content that is displayed.
|
||||
*/
|
||||
minimizedDisplay?: MaybeRef<Renderable>;
|
||||
minimizedDisplay?: MaybeGetter<Renderable>;
|
||||
/**
|
||||
* Whether or not to force the go back button to be hidden.
|
||||
* If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}.
|
||||
|
@ -261,7 +261,7 @@ export function createLayer<T extends LayerOptions>(
|
|||
...baseLayer,
|
||||
...(props as Omit<typeof props, keyof LayerOptions>),
|
||||
color: processGetter(color),
|
||||
display: processGetter(display),
|
||||
display,
|
||||
classes: processGetter(classes),
|
||||
style: computed((): CSSProperties => {
|
||||
let width = unref(layer.minWidth);
|
||||
|
@ -293,7 +293,7 @@ export function createLayer<T extends LayerOptions>(
|
|||
forceHideGoBack: processGetter(forceHideGoBack),
|
||||
minWidth: processGetter(minWidth) ?? 600,
|
||||
minimizable: processGetter(minimizable) ?? true,
|
||||
minimizedDisplay: processGetter(minimizedDisplay)
|
||||
minimizedDisplay
|
||||
} satisfies Layer;
|
||||
|
||||
return layer;
|
||||
|
@ -370,21 +370,21 @@ export function reloadLayer(layer: Layer): void {
|
|||
*/
|
||||
export function setupLayerModal(layer: Layer): {
|
||||
openModal: VoidFunction;
|
||||
modal: Ref<JSX.Element>;
|
||||
modal: () => JSX.Element;
|
||||
} {
|
||||
const showModal = ref(false);
|
||||
return {
|
||||
openModal: () => (showModal.value = true),
|
||||
modal: computed(() => (
|
||||
modal: () => (
|
||||
<Modal
|
||||
modelValue={showModal.value}
|
||||
onUpdate:modelValue={value => (showModal.value = value)}
|
||||
v-slots={{
|
||||
header: () => <h2>{unref(layer.name)}</h2>,
|
||||
body: () => render(layer.display)
|
||||
body: typeof layer.display ? layer.display : () => layer.display
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import settings from "game/settings";
|
|||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal, { formatSmall } from "util/bignum";
|
||||
import type { RequiredKeys, WithRequired } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||
|
@ -32,7 +32,7 @@ export interface Modifier {
|
|||
* A description of this modifier.
|
||||
* @see {@link createModifierSection}.
|
||||
*/
|
||||
description?: MaybeRef<Renderable>;
|
||||
description?: MaybeGetter<Renderable>;
|
||||
}
|
||||
|
||||
/** Utility type that represents the output of all modifiers that represent a single operation. */
|
||||
|
@ -46,7 +46,7 @@ export interface AdditiveModifierOptions {
|
|||
/** The amount to add to the input value. */
|
||||
addend: MaybeRefOrGetter<DecimalSource>;
|
||||
/** Description of what this modifier is doing. */
|
||||
description?: MaybeRefOrGetter<Renderable>;
|
||||
description?: MaybeGetter<Renderable>;
|
||||
/** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */
|
||||
enabled?: MaybeRefOrGetter<boolean>;
|
||||
/** Determines if numbers larger or smaller than 0 should be displayed as red. */
|
||||
|
@ -64,7 +64,6 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op
|
|||
const { addend, description, enabled, smallerIsBetter } = optionsFunc();
|
||||
|
||||
const processedAddend = processGetter(addend);
|
||||
const processedDescription = processGetter(description);
|
||||
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
||||
return {
|
||||
apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)),
|
||||
|
@ -72,13 +71,11 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op
|
|||
getFormula: (gain: FormulaSource) => Formula.add(gain, processedAddend),
|
||||
enabled: processedEnabled,
|
||||
description:
|
||||
processedDescription == null
|
||||
description == null
|
||||
? undefined
|
||||
: computed(() => (
|
||||
: () => (
|
||||
<div class="modifier-container">
|
||||
<span class="modifier-description">
|
||||
{render(processedDescription)}
|
||||
</span>
|
||||
<span class="modifier-description">{render(description)}</span>
|
||||
<span
|
||||
class="modifier-amount"
|
||||
style={
|
||||
|
@ -95,7 +92,7 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op
|
|||
{formatSmall(unref(processedAddend))}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
)
|
||||
};
|
||||
}) as S;
|
||||
}
|
||||
|
@ -105,7 +102,7 @@ export interface MultiplicativeModifierOptions {
|
|||
/** The amount to multiply the input value by. */
|
||||
multiplier: MaybeRefOrGetter<DecimalSource>;
|
||||
/** Description of what this modifier is doing. */
|
||||
description?: MaybeRefOrGetter<Renderable> | undefined;
|
||||
description?: MaybeGetter<Renderable> | undefined;
|
||||
/** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */
|
||||
enabled?: MaybeRefOrGetter<boolean> | undefined;
|
||||
/** Determines if numbers larger or smaller than 1 should be displayed as red. */
|
||||
|
@ -124,7 +121,6 @@ export function createMultiplicativeModifier<
|
|||
const { multiplier, description, enabled, smallerIsBetter } = optionsFunc();
|
||||
|
||||
const processedMultiplier = processGetter(multiplier);
|
||||
const processedDescription = processGetter(description);
|
||||
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
||||
return {
|
||||
apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
|
||||
|
@ -132,13 +128,11 @@ export function createMultiplicativeModifier<
|
|||
getFormula: (gain: FormulaSource) => Formula.times(gain, processedMultiplier),
|
||||
enabled: processedEnabled,
|
||||
description:
|
||||
processedDescription == null
|
||||
description == null
|
||||
? undefined
|
||||
: computed(() => (
|
||||
: () => (
|
||||
<div class="modifier-container">
|
||||
<span class="modifier-description">
|
||||
{render(processedDescription)}
|
||||
</span>
|
||||
<span class="modifier-description">{render(description)}</span>
|
||||
<span
|
||||
class="modifier-amount"
|
||||
style={
|
||||
|
@ -154,7 +148,7 @@ export function createMultiplicativeModifier<
|
|||
×{formatSmall(unref(processedMultiplier))}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
)
|
||||
};
|
||||
}) as S;
|
||||
}
|
||||
|
@ -164,7 +158,7 @@ export interface ExponentialModifierOptions {
|
|||
/** The amount to raise the input value to the power of. */
|
||||
exponent: MaybeRefOrGetter<DecimalSource>;
|
||||
/** Description of what this modifier is doing. */
|
||||
description?: MaybeRefOrGetter<Renderable> | undefined;
|
||||
description?: MaybeGetter<Renderable> | undefined;
|
||||
/** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */
|
||||
enabled?: MaybeRefOrGetter<boolean> | undefined;
|
||||
/** Add 1 before calculating, then remove it afterwards. This prevents low numbers from becoming lower. */
|
||||
|
@ -186,7 +180,6 @@ export function createExponentialModifier<
|
|||
optionsFunc();
|
||||
|
||||
const processedExponent = processGetter(exponent);
|
||||
const processedDescription = processGetter(description);
|
||||
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
||||
return {
|
||||
apply: (gain: DecimalSource) => {
|
||||
|
@ -217,12 +210,12 @@ export function createExponentialModifier<
|
|||
: Formula.pow(gain, processedExponent),
|
||||
enabled: processedEnabled,
|
||||
description:
|
||||
processedDescription == null
|
||||
description == null
|
||||
? undefined
|
||||
: computed(() => (
|
||||
: () => (
|
||||
<div class="modifier-container">
|
||||
<span class="modifier-description">
|
||||
{render(processedDescription)}
|
||||
{render(description)}
|
||||
{supportLowNumbers ? " (+1 effective)" : null}
|
||||
</span>
|
||||
<span
|
||||
|
@ -240,7 +233,7 @@ export function createExponentialModifier<
|
|||
^{formatSmall(unref(processedExponent))}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
)
|
||||
};
|
||||
}) as S;
|
||||
}
|
||||
|
@ -286,14 +279,13 @@ export function createSequentialModifier<
|
|||
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
|
||||
: undefined,
|
||||
description: modifiers.some(m => m.description != null)
|
||||
? computed(() =>
|
||||
? () =>
|
||||
(
|
||||
modifiers
|
||||
.filter(m => unref(m.enabled) !== false)
|
||||
.map(m => unref(m.description))
|
||||
.filter(d => d) as MaybeRef<Renderable>[]
|
||||
.filter(d => d) as MaybeGetter<Renderable>[]
|
||||
).map(m => render(m))
|
||||
)
|
||||
: undefined
|
||||
};
|
||||
}) as S;
|
||||
|
@ -312,7 +304,7 @@ export interface ModifierSectionOptions {
|
|||
/** The unit of the value being modified, if any. */
|
||||
unit?: string;
|
||||
/** The label to use for the base value. Defaults to "Base". */
|
||||
baseText?: MaybeRefOrGetter<Renderable>;
|
||||
baseText?: MaybeGetter<Renderable>;
|
||||
/** Determines if numbers larger or smaller than the base should be displayed as red. */
|
||||
smallerIsBetter?: boolean;
|
||||
}
|
||||
|
@ -332,7 +324,6 @@ export function createModifierSection({
|
|||
smallerIsBetter
|
||||
}: ModifierSectionOptions) {
|
||||
const total = modifier.apply(base ?? 1);
|
||||
const processedBaseText = processGetter(baseText);
|
||||
return (
|
||||
<div style={{ "--unit": settings.alignUnits && unit != null ? "'" + unit + "'" : "" }}>
|
||||
<h3>
|
||||
|
@ -341,7 +332,7 @@ export function createModifierSection({
|
|||
</h3>
|
||||
<br />
|
||||
<div class="modifier-container">
|
||||
<span class="modifier-description">{render(processedBaseText ?? "Base")}</span>
|
||||
<span class="modifier-description">{render(baseText ?? "Base")}</span>
|
||||
<span class="modifier-amount">
|
||||
{formatSmall(base ?? 1)}
|
||||
{unit}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { isVisible, Visibility } from "features/feature";
|
||||
import { displayResource, Resource } from "features/resources/resource";
|
||||
import Decimal, { DecimalSource } from "lib/break_eternity";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { joinJSX, render, Renderable } from "util/vue";
|
||||
import { joinJSX, Renderable } from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||
import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas";
|
||||
import type { GenericFormula, InvertibleIntegralFormula } from "./formulas/types";
|
||||
|
@ -262,16 +262,16 @@ export function createVisibilityRequirement(
|
|||
*/
|
||||
export function createBooleanRequirement(
|
||||
requirement: MaybeRefOrGetter<boolean>,
|
||||
display?: MaybeRefOrGetter<Renderable>
|
||||
display?: MaybeGetter<Renderable>
|
||||
): Requirement {
|
||||
return createLazyProxy(() => {
|
||||
const processedDisplay = processGetter(display);
|
||||
const partialDisplay =
|
||||
display == null ? undefined : typeof display === "function" ? display : () => display;
|
||||
return {
|
||||
requirementMet: processGetter(requirement),
|
||||
partialDisplay: processedDisplay == null ? undefined : () => render(processedDisplay),
|
||||
display:
|
||||
processedDisplay == null ? undefined : () => <>Req: {render(processedDisplay)}</>,
|
||||
visibility: processedDisplay == null ? Visibility.None : Visibility.Visible,
|
||||
partialDisplay,
|
||||
display: display == null ? undefined : () => <>Req: {partialDisplay}</>,
|
||||
visibility: display == null ? Visibility.None : Visibility.Visible,
|
||||
requiresPay: false
|
||||
};
|
||||
});
|
||||
|
|
|
@ -2,10 +2,10 @@ import projInfo from "data/projInfo.json";
|
|||
import { Themes } from "data/themes";
|
||||
import { globalBus } from "game/events";
|
||||
import LZString from "lz-string";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { decodeSave, hardReset } from "util/save";
|
||||
import { Renderable } from "util/vue";
|
||||
import { MaybeRef, MaybeRefOrGetter, reactive, watch } from "vue";
|
||||
import { reactive, watch } from "vue";
|
||||
|
||||
/** The player's settings object. */
|
||||
export interface Settings {
|
||||
|
@ -101,22 +101,22 @@ export function loadSettings(): void {
|
|||
}
|
||||
|
||||
/** A list of fields to append to the settings modal. */
|
||||
export const settingFields: MaybeRef<Renderable>[] = reactive([]);
|
||||
export const settingFields: MaybeGetter<Renderable>[] = reactive([]);
|
||||
/** Register a field to be displayed in the settings modal. */
|
||||
export function registerSettingField(component: MaybeRefOrGetter<Renderable>) {
|
||||
settingFields.push(processGetter(component));
|
||||
export function registerSettingField(component: MaybeGetter<Renderable>) {
|
||||
settingFields.push(component);
|
||||
}
|
||||
|
||||
/** A list of components to show in the info modal. */
|
||||
export const infoComponents: MaybeRef<Renderable>[] = reactive([]);
|
||||
export const infoComponents: MaybeGetter<Renderable>[] = reactive([]);
|
||||
/** Register a component to be displayed in the info modal. */
|
||||
export function registerInfoComponent(component: MaybeRefOrGetter<Renderable>) {
|
||||
infoComponents.push(processGetter(component));
|
||||
export function registerInfoComponent(component: MaybeGetter<Renderable>) {
|
||||
infoComponents.push(component);
|
||||
}
|
||||
|
||||
/** A list of components to add to the root of the page. */
|
||||
export const gameComponents: MaybeRef<Renderable>[] = reactive([]);
|
||||
export const gameComponents: MaybeGetter<Renderable>[] = reactive([]);
|
||||
/** Register a component to be displayed at the root of the page. */
|
||||
export function registerGameComponent(component: MaybeRefOrGetter<Renderable>) {
|
||||
gameComponents.push(processGetter(component));
|
||||
export function registerGameComponent(component: MaybeGetter<Renderable>) {
|
||||
gameComponents.push(component);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { isFunction } from "util/common";
|
|||
import type { ComputedRef } from "vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
export type MaybeGetter<T> = T | (() => T);
|
||||
|
||||
export function processGetter<T>(obj: T): T extends () => infer S ? ComputedRef<S> : T {
|
||||
if (isFunction(obj)) {
|
||||
return computed(obj) as ReturnType<typeof processGetter<T>>;
|
||||
|
|
|
@ -6,9 +6,9 @@ import Col from "components/layout/Column.vue";
|
|||
import Row from "components/layout/Row.vue";
|
||||
import { getUniqueID, Visibility } from "features/feature";
|
||||
import VueFeatureComponent from "features/VueFeature.vue";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { isRef, onUnmounted, ref, unref } from "vue";
|
||||
import { isRef, onUnmounted, ref, toValue } from "vue";
|
||||
import { JSX } from "vue/jsx-runtime";
|
||||
import { camelToKebab } from "./common";
|
||||
|
||||
|
@ -35,7 +35,7 @@ export interface VueFeature {
|
|||
/** CSS to apply to this feature. */
|
||||
style?: MaybeRef<CSSProperties>;
|
||||
/** The components to render inside the vue feature */
|
||||
components: MaybeRef<Renderable>[];
|
||||
components: MaybeGetter<Renderable>[];
|
||||
/** The components to render wrapped around the vue feature */
|
||||
wrappers: ((el: () => Renderable) => Renderable)[];
|
||||
/** Used to identify Vue Features */
|
||||
|
@ -45,14 +45,14 @@ export interface VueFeature {
|
|||
export function vueFeatureMixin(
|
||||
featureName: string,
|
||||
options: VueFeatureOptions,
|
||||
component?: MaybeRefOrGetter<Renderable>
|
||||
component?: MaybeGetter<Renderable>
|
||||
) {
|
||||
return {
|
||||
id: getUniqueID(featureName),
|
||||
visibility: processGetter(options.visibility),
|
||||
classes: processGetter(options.classes),
|
||||
style: processGetter(options.style),
|
||||
components: component == null ? [] : [processGetter(component)],
|
||||
components: component == null ? [] : [component],
|
||||
wrappers: [] as ((el: () => Renderable) => Renderable)[],
|
||||
[VueFeature]: true
|
||||
} satisfies VueFeature;
|
||||
|
@ -60,15 +60,15 @@ export function vueFeatureMixin(
|
|||
|
||||
export function render(object: VueFeature, wrapper?: (el: Renderable) => Renderable): JSX.Element;
|
||||
export function render<T extends Renderable>(
|
||||
object: MaybeRef<Renderable>,
|
||||
object: MaybeGetter<Renderable>,
|
||||
wrapper?: (el: Renderable) => T
|
||||
): T;
|
||||
export function render(
|
||||
object: VueFeature | MaybeRef<Renderable>,
|
||||
object: VueFeature | MaybeGetter<Renderable>,
|
||||
wrapper?: (el: Renderable) => Renderable
|
||||
): Renderable;
|
||||
export function render(
|
||||
object: VueFeature | MaybeRef<Renderable>,
|
||||
object: VueFeature | MaybeGetter<Renderable>,
|
||||
wrapper?: (el: Renderable) => Renderable
|
||||
) {
|
||||
if (typeof object === "object" && VueFeature in object) {
|
||||
|
@ -85,20 +85,24 @@ export function render(
|
|||
);
|
||||
}
|
||||
|
||||
object = unref(object);
|
||||
object = toValue(object);
|
||||
return wrapper?.(object) ?? object;
|
||||
}
|
||||
|
||||
export function renderRow(...objects: (VueFeature | MaybeRef<Renderable>)[]): JSX.Element {
|
||||
export function renderRow(
|
||||
...objects: (VueFeature | MaybeGetter<Renderable>)[]
|
||||
): JSX.Element {
|
||||
return <Row>{objects.map(obj => render(obj))}</Row>;
|
||||
}
|
||||
|
||||
export function renderCol(...objects: (VueFeature | MaybeRef<Renderable>)[]): JSX.Element {
|
||||
export function renderCol(
|
||||
...objects: (VueFeature | MaybeGetter<Renderable>)[]
|
||||
): JSX.Element {
|
||||
return <Col>{objects.map(obj => render(obj))}</Col>;
|
||||
}
|
||||
|
||||
export function joinJSX(
|
||||
objects: (VueFeature | MaybeRef<Renderable>)[],
|
||||
objects: (VueFeature | MaybeGetter<Renderable>)[],
|
||||
joiner: JSX.Element
|
||||
): JSX.Element {
|
||||
return objects.reduce<JSX.Element>(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { type OptionsFunc } from "features/feature";
|
||||
import { processGetter } from "util/computed";
|
||||
import { createLazyProxy, runAfterEvaluation } from "util/proxies";
|
||||
import type { VueFeature } from "util/vue";
|
||||
|
@ -23,9 +22,9 @@ export interface Mark {
|
|||
* @param element The renderable feature to display the tooltip on.
|
||||
* @param options Mark options.
|
||||
*/
|
||||
export function addMark<T extends MarkOptions>(
|
||||
export function addMark(
|
||||
element: VueFeature,
|
||||
optionsFunc: OptionsFunc<T, Mark>
|
||||
optionsFunc: () => MarkOptions
|
||||
): asserts element is VueFeature & { mark: Mark } {
|
||||
const mark = createLazyProxy(() => {
|
||||
const options = optionsFunc();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { isVisible, type OptionsFunc } from "features/feature";
|
||||
import { isVisible } from "features/feature";
|
||||
import { deletePersistent, persistent } from "game/persistence";
|
||||
import { Direction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy, runAfterEvaluation } from "util/proxies";
|
||||
import { Renderable, vueFeatureMixin, type VueFeature, type VueFeatureOptions } from "util/vue";
|
||||
import { MaybeRef, MaybeRefOrGetter, type Ref } from "vue";
|
||||
|
@ -21,7 +21,7 @@ export interface TooltipOptions extends VueFeatureOptions {
|
|||
/** Whether or not this tooltip can be pinned, meaning it'll stay visible even when not hovered. */
|
||||
pinnable?: boolean;
|
||||
/** The text to display inside the tooltip. */
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** The direction in which to display the tooltip */
|
||||
direction?: MaybeRefOrGetter<Direction>;
|
||||
/** The x offset of the tooltip, in px. */
|
||||
|
@ -35,7 +35,7 @@ export interface Tooltip extends VueFeature {
|
|||
/** Whether or not this tooltip can be pinned, meaning it'll stay visible even when not hovered. */
|
||||
pinnable?: boolean;
|
||||
/** The text to display inside the tooltip. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** The direction in which to display the tooltip */
|
||||
direction?: MaybeRef<Direction>;
|
||||
/** The x offset of the tooltip, in px. */
|
||||
|
@ -51,9 +51,9 @@ export interface Tooltip extends VueFeature {
|
|||
* @param element The renderable feature to display the tooltip on.
|
||||
* @param options Tooltip options.
|
||||
*/
|
||||
export function addTooltip<T extends TooltipOptions>(
|
||||
export function addTooltip(
|
||||
element: VueFeature,
|
||||
optionsFunc: OptionsFunc<T, Tooltip>
|
||||
optionsFunc: () => TooltipOptions
|
||||
): asserts element is VueFeature & { tooltip: Tooltip } {
|
||||
const pinned = persistent<boolean>(false, false);
|
||||
const tooltip = createLazyProxy(() => {
|
||||
|
@ -69,7 +69,7 @@ export function addTooltip<T extends TooltipOptions>(
|
|||
...vueFeatureMixin("tooltip", options),
|
||||
pinnable: pinnable ?? true,
|
||||
pinned: pinnable === false ? undefined : pinned,
|
||||
display: processGetter(display),
|
||||
display,
|
||||
direction: processGetter(direction ?? Direction.Up),
|
||||
xoffset: processGetter(xoffset),
|
||||
yoffset: processGetter(yoffset)
|
||||
|
|
|
@ -9,15 +9,16 @@ import {
|
|||
} from "game/modifiers";
|
||||
import Decimal, { DecimalSource } from "util/bignum";
|
||||
import { WithRequired } from "util/common";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { beforeAll, describe, expect, test } from "vitest";
|
||||
import { MaybeRefOrGetter, Ref, ref, unref } from "vue";
|
||||
import "../utils";
|
||||
import { render, Renderable } from "util/vue";
|
||||
|
||||
export type ModifierConstructorOptions = {
|
||||
[S in "addend" | "multiplier" | "exponent"]: MaybeRefOrGetter<DecimalSource>;
|
||||
} & {
|
||||
description?: MaybeRefOrGetter<Renderable>;
|
||||
description?: MaybeGetter<Renderable>;
|
||||
enabled?: MaybeRefOrGetter<boolean>;
|
||||
smallerIsBetter?: boolean;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue