2022-12-05 02:26:21 +00:00
|
|
|
import Collapsible from "components/layout/Collapsible.vue";
|
2022-06-27 00:17:22 +00:00
|
|
|
import type { Clickable, ClickableOptions, GenericClickable } from "features/clickables/clickable";
|
|
|
|
import { createClickable } from "features/clickables/clickable";
|
|
|
|
import type { GenericConversion } from "features/conversion";
|
|
|
|
import type { CoercableComponent, JSXFunction, OptionsFunc, Replace } from "features/feature";
|
|
|
|
import { jsx, setDefault } from "features/feature";
|
2022-12-05 02:26:21 +00:00
|
|
|
import { GenericMilestone } from "features/milestones/milestone";
|
2022-03-04 03:39:48 +00:00
|
|
|
import { displayResource } from "features/resources/resource";
|
2022-06-27 00:17:22 +00:00
|
|
|
import type { GenericTree, GenericTreeNode, TreeNode, TreeNodeOptions } from "features/trees/tree";
|
|
|
|
import { createTreeNode } from "features/trees/tree";
|
|
|
|
import type { Modifier } from "game/modifiers";
|
|
|
|
import type { Persistent } from "game/persistence";
|
|
|
|
import { DefaultValue, persistent } from "game/persistence";
|
2022-03-04 03:39:48 +00:00
|
|
|
import player from "game/player";
|
2022-06-27 00:17:22 +00:00
|
|
|
import type { DecimalSource } from "util/bignum";
|
|
|
|
import Decimal, { format } from "util/bignum";
|
|
|
|
import type { WithRequired } from "util/common";
|
|
|
|
import type {
|
2022-01-14 04:25:47 +00:00
|
|
|
Computable,
|
2022-03-05 22:42:55 +00:00
|
|
|
GetComputableType,
|
2022-01-14 04:25:47 +00:00
|
|
|
GetComputableTypeWithDefault,
|
|
|
|
ProcessedComputable
|
2022-03-04 03:39:48 +00:00
|
|
|
} from "util/computed";
|
2022-06-27 00:17:22 +00:00
|
|
|
import { convertComputable, processComputable } from "util/computed";
|
2022-12-05 02:26:21 +00:00
|
|
|
import { getFirstFeature, renderColJSX, renderJSX } from "util/vue";
|
2022-06-27 00:17:22 +00:00
|
|
|
import type { Ref } from "vue";
|
|
|
|
import { computed, unref } from "vue";
|
2022-04-30 21:42:03 +00:00
|
|
|
import "./common.css";
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-07-10 06:44:45 +00:00
|
|
|
/** An object that configures a {@link ResetButton} */
|
2022-01-14 04:25:47 +00:00
|
|
|
export interface ResetButtonOptions extends ClickableOptions {
|
2022-07-10 06:44:45 +00:00
|
|
|
/** The conversion the button uses to calculate how much resources will be gained on click */
|
2022-01-14 04:25:47 +00:00
|
|
|
conversion: GenericConversion;
|
2022-07-10 06:44:45 +00:00
|
|
|
/** The tree this reset button is apart of */
|
2022-01-14 04:25:47 +00:00
|
|
|
tree: GenericTree;
|
2022-07-10 06:44:45 +00:00
|
|
|
/** The specific tree node associated with this reset button */
|
2022-01-14 04:25:47 +00:00
|
|
|
treeNode: GenericTreeNode;
|
2022-07-26 20:25:31 +00:00
|
|
|
/**
|
2022-07-10 06:44:45 +00:00
|
|
|
* Text to display on low conversion amounts, describing what "resetting" is in this context.
|
|
|
|
* Defaults to "Reset for ".
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
resetDescription?: Computable<string>;
|
2022-07-10 06:44:45 +00:00
|
|
|
/** Whether or not to show how much currency would be required to make the gain amount increase. */
|
2022-01-14 04:25:47 +00:00
|
|
|
showNextAt?: Computable<boolean>;
|
2022-07-10 06:44:45 +00:00
|
|
|
/**
|
|
|
|
* The content to display on the button.
|
|
|
|
* By default, this includes the reset description, and amount of currency to be gained.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
display?: Computable<CoercableComponent>;
|
2022-07-10 06:44:45 +00:00
|
|
|
/**
|
|
|
|
* Whether or not this button can currently be clicked.
|
|
|
|
* Defaults to checking the current gain amount is greater than {@link minimumGain}
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
canClick?: Computable<boolean>;
|
2022-07-10 06:44:45 +00:00
|
|
|
/**
|
|
|
|
* When {@link canClick} is left to its default, minimumGain is used to only enable the reset button when a sufficient amount of currency to gain is available.
|
|
|
|
*/
|
2022-04-11 02:16:20 +00:00
|
|
|
minimumGain?: Computable<DecimalSource>;
|
2022-07-10 06:44:45 +00:00
|
|
|
/** A persistent ref to track how much time has passed since the last time this tree node was reset. */
|
2022-05-24 04:34:59 +00:00
|
|
|
resetTime?: Persistent<DecimalSource>;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
2022-07-10 06:44:45 +00:00
|
|
|
/**
|
|
|
|
* A button that is used to control a conversion.
|
|
|
|
* It will show how much can be converted currently, and can show when that amount will go up, as well as handle only being clickable when a sufficient amount of currency can be gained.
|
|
|
|
* Assumes this button is associated with a specific node on a tree, and triggers that tree's reset propagation.
|
|
|
|
*/
|
2022-03-09 01:40:51 +00:00
|
|
|
export type ResetButton<T extends ResetButtonOptions> = Replace<
|
2022-01-14 04:25:47 +00:00
|
|
|
Clickable<T>,
|
|
|
|
{
|
|
|
|
resetDescription: GetComputableTypeWithDefault<T["resetDescription"], Ref<string>>;
|
|
|
|
showNextAt: GetComputableTypeWithDefault<T["showNextAt"], true>;
|
|
|
|
display: GetComputableTypeWithDefault<T["display"], Ref<JSX.Element>>;
|
|
|
|
canClick: GetComputableTypeWithDefault<T["canClick"], Ref<boolean>>;
|
2022-04-11 02:16:20 +00:00
|
|
|
minimumGain: GetComputableTypeWithDefault<T["minimumGain"], 1>;
|
2023-02-15 20:57:22 +00:00
|
|
|
onClick: (event?: MouseEvent | TouchEvent) => void;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
2022-07-10 06:44:45 +00:00
|
|
|
/** A type that matches any valid {@link ResetButton} object. */
|
2022-01-14 04:25:47 +00:00
|
|
|
export type GenericResetButton = Replace<
|
|
|
|
GenericClickable & ResetButton<ResetButtonOptions>,
|
|
|
|
{
|
|
|
|
resetDescription: ProcessedComputable<string>;
|
|
|
|
showNextAt: ProcessedComputable<boolean>;
|
|
|
|
display: ProcessedComputable<CoercableComponent>;
|
|
|
|
canClick: ProcessedComputable<boolean>;
|
2022-04-11 02:16:20 +00:00
|
|
|
minimumGain: ProcessedComputable<DecimalSource>;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
2022-07-10 06:44:45 +00:00
|
|
|
/**
|
|
|
|
* Lazily creates a reset button with the given options.
|
|
|
|
* @param optionsFunc A function that returns the options object for this reset button.
|
|
|
|
*/
|
2022-01-14 04:25:47 +00:00
|
|
|
export function createResetButton<T extends ClickableOptions & ResetButtonOptions>(
|
2022-04-11 00:04:56 +00:00
|
|
|
optionsFunc: OptionsFunc<T>
|
2022-01-14 04:25:47 +00:00
|
|
|
): ResetButton<T> {
|
2022-02-27 19:49:34 +00:00
|
|
|
return createClickable(() => {
|
|
|
|
const resetButton = optionsFunc();
|
|
|
|
|
|
|
|
processComputable(resetButton as T, "showNextAt");
|
|
|
|
setDefault(resetButton, "showNextAt", true);
|
2022-04-11 02:16:20 +00:00
|
|
|
setDefault(resetButton, "minimumGain", 1);
|
2022-02-27 19:49:34 +00:00
|
|
|
|
|
|
|
if (resetButton.resetDescription == null) {
|
|
|
|
resetButton.resetDescription = computed(() =>
|
|
|
|
Decimal.lt(resetButton.conversion.gainResource.value, 1e3) ? "Reset for " : ""
|
2022-01-14 04:25:47 +00:00
|
|
|
);
|
2022-02-27 19:49:34 +00:00
|
|
|
} else {
|
|
|
|
processComputable(resetButton as T, "resetDescription");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resetButton.display == null) {
|
|
|
|
resetButton.display = jsx(() => (
|
2022-01-14 04:25:47 +00:00
|
|
|
<span>
|
2022-02-27 19:49:34 +00:00
|
|
|
{unref(resetButton.resetDescription as ProcessedComputable<string>)}
|
2022-01-14 04:25:47 +00:00
|
|
|
<b>
|
|
|
|
{displayResource(
|
2022-02-27 19:49:34 +00:00
|
|
|
resetButton.conversion.gainResource,
|
2022-04-11 02:16:20 +00:00
|
|
|
Decimal.max(
|
|
|
|
unref(resetButton.conversion.actualGain),
|
|
|
|
unref(resetButton.minimumGain as ProcessedComputable<DecimalSource>)
|
|
|
|
)
|
2022-01-14 04:25:47 +00:00
|
|
|
)}
|
2022-02-27 19:49:34 +00:00
|
|
|
</b>{" "}
|
|
|
|
{resetButton.conversion.gainResource.displayName}
|
2022-12-21 03:26:25 +00:00
|
|
|
{unref(resetButton.showNextAt) != null ? (
|
2022-06-08 00:07:04 +00:00
|
|
|
<div>
|
|
|
|
<br />
|
|
|
|
{unref(resetButton.conversion.buyMax) ? "Next:" : "Req:"}{" "}
|
|
|
|
{displayResource(
|
|
|
|
resetButton.conversion.baseResource,
|
|
|
|
unref(resetButton.conversion.buyMax) ||
|
|
|
|
Decimal.floor(unref(resetButton.conversion.actualGain)).neq(1)
|
|
|
|
? unref(resetButton.conversion.nextAt)
|
|
|
|
: unref(resetButton.conversion.currentAt)
|
|
|
|
)}{" "}
|
|
|
|
{resetButton.conversion.baseResource.displayName}
|
|
|
|
</div>
|
|
|
|
) : null}
|
2022-01-14 04:25:47 +00:00
|
|
|
</span>
|
2022-02-27 19:49:34 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resetButton.canClick == null) {
|
|
|
|
resetButton.canClick = computed(() =>
|
2022-04-11 02:16:20 +00:00
|
|
|
Decimal.gte(
|
|
|
|
unref(resetButton.conversion.actualGain),
|
|
|
|
unref(resetButton.minimumGain as ProcessedComputable<DecimalSource>)
|
|
|
|
)
|
2022-01-14 04:25:47 +00:00
|
|
|
);
|
2022-02-27 19:49:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const onClick = resetButton.onClick;
|
2023-02-15 20:57:22 +00:00
|
|
|
resetButton.onClick = function (event?: MouseEvent | TouchEvent) {
|
2022-12-21 03:26:25 +00:00
|
|
|
if (unref(resetButton.canClick) === false) {
|
2022-03-03 03:09:37 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-02-27 19:49:34 +00:00
|
|
|
resetButton.conversion.convert();
|
|
|
|
resetButton.tree.reset(resetButton.treeNode);
|
2022-05-24 04:34:59 +00:00
|
|
|
if (resetButton.resetTime) {
|
|
|
|
resetButton.resetTime.value = resetButton.resetTime[DefaultValue];
|
|
|
|
}
|
2023-02-15 20:57:22 +00:00
|
|
|
onClick?.(event);
|
2022-02-27 19:49:34 +00:00
|
|
|
};
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
return resetButton;
|
|
|
|
}) as unknown as ResetButton<T>;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
2022-07-10 06:44:45 +00:00
|
|
|
/** An object that configures a {@link LayerTreeNode} */
|
2022-01-14 04:25:47 +00:00
|
|
|
export interface LayerTreeNodeOptions extends TreeNodeOptions {
|
2022-07-10 06:44:45 +00:00
|
|
|
/** The ID of the layer this tree node is associated with */
|
2022-01-14 04:25:47 +00:00
|
|
|
layerID: string;
|
2022-07-10 06:44:45 +00:00
|
|
|
/** The color to display this tree node as */
|
2022-03-05 22:42:55 +00:00
|
|
|
color: Computable<string>; // marking as required
|
2022-07-10 06:44:45 +00:00
|
|
|
/**
|
|
|
|
* The content to display in the tree node.
|
|
|
|
* Defaults to the layer's ID
|
|
|
|
*/
|
2022-04-24 04:54:23 +00:00
|
|
|
display?: Computable<CoercableComponent>;
|
2022-07-10 06:44:45 +00:00
|
|
|
/** Whether or not to append the layer to the tabs list.
|
|
|
|
* If set to false, then the tree node will instead always remove all tabs to its right and then add the layer tab.
|
|
|
|
* Defaults to true.
|
|
|
|
*/
|
2022-03-05 22:42:55 +00:00
|
|
|
append?: Computable<boolean>;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
2022-07-10 06:44:45 +00:00
|
|
|
/** A tree node that is associated with a given layer, and which opens the layer when clicked. */
|
2022-01-14 04:25:47 +00:00
|
|
|
export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
|
|
|
TreeNode<T>,
|
|
|
|
{
|
2022-03-05 22:42:55 +00:00
|
|
|
display: GetComputableTypeWithDefault<T["display"], T["layerID"]>;
|
|
|
|
append: GetComputableType<T["append"]>;
|
|
|
|
}
|
|
|
|
>;
|
2022-07-10 06:44:45 +00:00
|
|
|
/** A type that matches any valid {@link LayerTreeNode} object. */
|
2022-03-05 22:42:55 +00:00
|
|
|
export type GenericLayerTreeNode = Replace<
|
|
|
|
LayerTreeNode<LayerTreeNodeOptions>,
|
|
|
|
{
|
2022-04-24 04:54:23 +00:00
|
|
|
display: ProcessedComputable<CoercableComponent>;
|
2022-03-05 22:42:55 +00:00
|
|
|
append?: ProcessedComputable<boolean>;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
2022-07-10 06:44:45 +00:00
|
|
|
/**
|
|
|
|
* Lazily creates a tree node that's associated with a specific layer, with the given options.
|
|
|
|
* @param optionsFunc A function that returns the options object for this tree node.
|
|
|
|
*/
|
2022-02-27 19:49:34 +00:00
|
|
|
export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
|
2022-04-11 00:04:56 +00:00
|
|
|
optionsFunc: OptionsFunc<T>
|
2022-02-27 19:49:34 +00:00
|
|
|
): LayerTreeNode<T> {
|
|
|
|
return createTreeNode(() => {
|
|
|
|
const options = optionsFunc();
|
2022-03-05 22:42:55 +00:00
|
|
|
processComputable(options as T, "display");
|
|
|
|
setDefault(options, "display", options.layerID);
|
2022-02-27 19:49:34 +00:00
|
|
|
processComputable(options as T, "append");
|
|
|
|
return {
|
|
|
|
...options,
|
2022-04-24 04:54:23 +00:00
|
|
|
display: options.display,
|
2022-03-05 22:42:55 +00:00
|
|
|
onClick: unref((options as unknown as GenericLayerTreeNode).append)
|
|
|
|
? function () {
|
|
|
|
if (player.tabs.includes(options.layerID)) {
|
|
|
|
const index = player.tabs.lastIndexOf(options.layerID);
|
|
|
|
player.tabs.splice(index, 1);
|
|
|
|
} else {
|
|
|
|
player.tabs.push(options.layerID);
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
2022-03-05 22:42:55 +00:00
|
|
|
}
|
|
|
|
: function () {
|
|
|
|
player.tabs.splice(1, 1, options.layerID);
|
|
|
|
}
|
2022-02-27 19:49:34 +00:00
|
|
|
};
|
2022-01-25 04:23:30 +00:00
|
|
|
}) as unknown as LayerTreeNode<T>;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
2022-04-30 21:42:03 +00:00
|
|
|
|
2022-07-15 22:27:51 +00:00
|
|
|
/** An option object for a modifier display as a single section. **/
|
|
|
|
export interface Section {
|
|
|
|
/** The header for this modifier. **/
|
2022-12-24 01:55:41 +00:00
|
|
|
title: Computable<string>;
|
2022-07-15 22:27:51 +00:00
|
|
|
/** A subtitle for this modifier, e.g. to explain the context for the modifier. **/
|
2022-12-24 01:55:41 +00:00
|
|
|
subtitle?: Computable<string>;
|
2022-07-15 22:27:51 +00:00
|
|
|
/** The modifier to be displaying in this section. **/
|
|
|
|
modifier: WithRequired<Modifier, "description">;
|
|
|
|
/** The base value being modified. **/
|
|
|
|
base?: Computable<DecimalSource>;
|
|
|
|
/** The unit of measurement for the base. **/
|
|
|
|
unit?: string;
|
|
|
|
/** The label to call the base amount. Defaults to "Base". **/
|
|
|
|
baseText?: Computable<CoercableComponent>;
|
|
|
|
/** Whether or not this section should be currently visible to the player. **/
|
|
|
|
visible?: Computable<boolean>;
|
|
|
|
}
|
|
|
|
|
2022-07-10 06:44:45 +00:00
|
|
|
/**
|
|
|
|
* Takes an array of modifier "sections", and creates a JSXFunction that can render all those sections, and allow each section to be collapsed.
|
|
|
|
* Also returns a list of persistent refs that are used to control which sections are currently collapsed.
|
2022-07-26 20:25:31 +00:00
|
|
|
* @param sectionsFunc A function that returns the sections to display.
|
2023-02-15 06:09:06 +00:00
|
|
|
* @param smallerIsBetter Determines whether numbers larger or smaller than the base should be displayed as red.
|
2022-07-10 06:44:45 +00:00
|
|
|
*/
|
2022-04-30 21:42:03 +00:00
|
|
|
export function createCollapsibleModifierSections(
|
2023-02-15 06:09:06 +00:00
|
|
|
sectionsFunc: () => Section[],
|
|
|
|
smallerIsBetter = false
|
2022-07-28 03:56:34 +00:00
|
|
|
): [JSXFunction, Persistent<Record<number, boolean>>] {
|
2022-07-27 04:56:20 +00:00
|
|
|
const sections: Section[] = [];
|
|
|
|
const processed:
|
|
|
|
| {
|
|
|
|
base: ProcessedComputable<DecimalSource | undefined>[];
|
|
|
|
baseText: ProcessedComputable<CoercableComponent | undefined>[];
|
|
|
|
visible: ProcessedComputable<boolean | undefined>[];
|
2022-12-24 01:55:41 +00:00
|
|
|
title: ProcessedComputable<string | undefined>[];
|
|
|
|
subtitle: ProcessedComputable<string | undefined>[];
|
2022-07-27 04:56:20 +00:00
|
|
|
}
|
|
|
|
| Record<string, never> = {};
|
|
|
|
let calculated = false;
|
|
|
|
function calculateSections() {
|
|
|
|
if (!calculated) {
|
|
|
|
sections.push(...sectionsFunc());
|
|
|
|
processed.base = sections.map(s => convertComputable(s.base));
|
|
|
|
processed.baseText = sections.map(s => convertComputable(s.baseText));
|
|
|
|
processed.visible = sections.map(s => convertComputable(s.visible));
|
2022-12-24 01:55:41 +00:00
|
|
|
processed.title = sections.map(s => convertComputable(s.title));
|
|
|
|
processed.subtitle = sections.map(s => convertComputable(s.subtitle));
|
2022-07-27 04:56:20 +00:00
|
|
|
calculated = true;
|
|
|
|
}
|
|
|
|
return sections;
|
|
|
|
}
|
2022-04-30 21:42:03 +00:00
|
|
|
|
2022-07-28 03:56:34 +00:00
|
|
|
const collapsed = persistent<Record<number, boolean>>({});
|
2022-07-27 04:56:20 +00:00
|
|
|
const jsxFunc = jsx(() => {
|
|
|
|
const sections = calculateSections();
|
2022-04-30 21:42:03 +00:00
|
|
|
|
2022-08-22 13:43:15 +00:00
|
|
|
let firstVisibleSection = true;
|
2022-07-27 04:56:20 +00:00
|
|
|
const sectionJSX = sections.map((s, i) => {
|
|
|
|
if (unref(processed.visible[i]) === false) return null;
|
|
|
|
const header = (
|
|
|
|
<h3
|
2022-07-28 03:56:34 +00:00
|
|
|
onClick={() => (collapsed.value[i] = !collapsed.value[i])}
|
2022-07-27 04:56:20 +00:00
|
|
|
style="cursor: pointer"
|
|
|
|
>
|
2022-07-28 03:56:34 +00:00
|
|
|
<span
|
|
|
|
class={"modifier-toggle" + (unref(collapsed.value[i]) ? " collapsed" : "")}
|
|
|
|
>
|
2022-07-27 04:56:20 +00:00
|
|
|
▼
|
|
|
|
</span>
|
2022-12-24 02:14:12 +00:00
|
|
|
{unref(processed.title[i])}
|
|
|
|
{unref(processed.subtitle[i]) != null ? (
|
|
|
|
<span class="subtitle"> ({unref(processed.subtitle[i])})</span>
|
2022-12-24 01:55:41 +00:00
|
|
|
) : null}
|
2022-07-27 04:56:20 +00:00
|
|
|
</h3>
|
|
|
|
);
|
2022-07-26 20:25:31 +00:00
|
|
|
|
2022-07-28 03:56:34 +00:00
|
|
|
const modifiers = unref(collapsed.value[i]) ? null : (
|
2022-07-27 04:56:20 +00:00
|
|
|
<>
|
|
|
|
<div class="modifier-container">
|
2022-12-10 08:24:14 +00:00
|
|
|
<span class="modifier-description">
|
|
|
|
{renderJSX(unref(processed.baseText[i]) ?? "Base")}
|
|
|
|
</span>
|
2022-07-27 04:56:20 +00:00
|
|
|
<span class="modifier-amount">
|
|
|
|
{format(unref(processed.base[i]) ?? 1)}
|
2022-07-26 20:25:31 +00:00
|
|
|
{s.unit}
|
2022-07-27 04:56:20 +00:00
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
{renderJSX(unref(s.modifier.description))}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
2022-08-22 13:43:15 +00:00
|
|
|
const hasPreviousSection = !firstVisibleSection;
|
|
|
|
firstVisibleSection = false;
|
|
|
|
|
2023-02-15 06:09:06 +00:00
|
|
|
const base = unref(processed.base[i]) ?? 1;
|
|
|
|
const total = s.modifier.apply(base);
|
|
|
|
|
2022-07-27 04:56:20 +00:00
|
|
|
return (
|
|
|
|
<>
|
2022-08-22 13:43:15 +00:00
|
|
|
{hasPreviousSection ? <br /> : null}
|
2022-07-27 04:56:20 +00:00
|
|
|
<div>
|
|
|
|
{header}
|
|
|
|
<br />
|
|
|
|
{modifiers}
|
|
|
|
<hr />
|
2022-12-10 08:24:14 +00:00
|
|
|
<div class="modifier-container">
|
2023-02-15 06:09:06 +00:00
|
|
|
<span class="modifier-description">Total</span>
|
|
|
|
<span
|
|
|
|
class="modifier-amount"
|
|
|
|
style={
|
|
|
|
(
|
|
|
|
smallerIsBetter === true
|
|
|
|
? Decimal.gt(total, base ?? 1)
|
|
|
|
: Decimal.lt(total, base ?? 1)
|
|
|
|
)
|
|
|
|
? "color: var(--danger)"
|
|
|
|
: ""
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{formatSmall(total)}
|
2022-12-10 08:24:14 +00:00
|
|
|
{s.unit}
|
|
|
|
</span>
|
|
|
|
</div>
|
2022-07-27 04:56:20 +00:00
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
2022-04-30 21:42:03 +00:00
|
|
|
});
|
2022-07-27 04:56:20 +00:00
|
|
|
return <>{sectionJSX}</>;
|
2022-04-30 21:42:03 +00:00
|
|
|
});
|
2022-07-27 04:56:20 +00:00
|
|
|
return [jsxFunc, collapsed];
|
2022-04-30 21:42:03 +00:00
|
|
|
}
|
2022-06-28 14:52:49 +00:00
|
|
|
|
2022-07-10 06:44:45 +00:00
|
|
|
/**
|
|
|
|
* Creates an HTML string for a span that writes some given text in a given color.
|
|
|
|
* @param textToColor The content to change the color of
|
|
|
|
* @param color The color to change the content to look like. Defaults to the current theme's accent 2 variable.
|
|
|
|
*/
|
2022-07-18 02:32:32 +00:00
|
|
|
export function colorText(textToColor: string, color = "var(--accent2)"): JSX.Element {
|
2022-07-26 02:30:19 +00:00
|
|
|
return <span style={{ color }}>{textToColor}</span>;
|
2022-06-28 14:52:49 +00:00
|
|
|
}
|
2022-12-05 02:26:21 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a collapsible display of a list of milestones
|
|
|
|
* @param milestones A dictionary of the milestones to display, inserted in the order from easiest to hardest
|
|
|
|
*/
|
|
|
|
export function createCollapsibleMilestones(milestones: Record<string, GenericMilestone>) {
|
|
|
|
// Milestones are typically defined from easiest to hardest, and we want to show hardest first
|
|
|
|
const orderedMilestones = Object.values(milestones).reverse();
|
|
|
|
const collapseMilestones = persistent<boolean>(true);
|
|
|
|
const lockedMilestones = computed(() =>
|
|
|
|
orderedMilestones.filter(m => m.earned.value === false)
|
|
|
|
);
|
|
|
|
const { firstFeature, collapsedContent, hasCollapsedContent } = getFirstFeature(
|
|
|
|
orderedMilestones,
|
|
|
|
m => m.earned.value
|
|
|
|
);
|
|
|
|
const display = jsx(() => {
|
|
|
|
const milestonesToDisplay = [...lockedMilestones.value];
|
|
|
|
if (firstFeature.value) {
|
|
|
|
milestonesToDisplay.push(firstFeature.value);
|
|
|
|
}
|
|
|
|
return renderColJSX(
|
|
|
|
...milestonesToDisplay,
|
|
|
|
jsx(() => (
|
|
|
|
<Collapsible
|
|
|
|
collapsed={collapseMilestones}
|
|
|
|
content={collapsedContent}
|
|
|
|
display={
|
|
|
|
collapseMilestones.value
|
|
|
|
? "Show other completed milestones"
|
|
|
|
: "Hide other completed milestones"
|
|
|
|
}
|
|
|
|
v-show={unref(hasCollapsedContent)}
|
|
|
|
/>
|
|
|
|
))
|
|
|
|
);
|
|
|
|
});
|
|
|
|
return {
|
|
|
|
collapseMilestones,
|
|
|
|
display
|
|
|
|
};
|
|
|
|
}
|