chromatic-lattice/src/features/clickables/clickable.ts
2023-02-25 16:48:36 -08:00

162 lines
5.2 KiB
TypeScript

import ClickableComponent from "features/clickables/Clickable.vue";
import { Decorator } from "features/decorators";
import type {
CoercableComponent,
GenericComponent,
OptionsFunc,
Replace,
StyleValue
} from "features/feature";
import { Component, GatherProps, getUniqueID, setDefault, Visibility } from "features/feature";
import type { BaseLayer } from "game/layers";
import type { Unsubscribe } from "nanoevents";
import type {
Computable,
GetComputableType,
GetComputableTypeWithDefault,
ProcessedComputable
} from "util/computed";
import { processComputable } from "util/computed";
import { createLazyProxy } from "util/proxies";
import { computed, unref } from "vue";
export const ClickableType = Symbol("Clickable");
export interface ClickableOptions {
visibility?: Computable<Visibility | boolean>;
canClick?: Computable<boolean>;
classes?: Computable<Record<string, boolean>>;
style?: Computable<StyleValue>;
mark?: Computable<boolean | string>;
display?: Computable<
| CoercableComponent
| {
title?: CoercableComponent;
description: CoercableComponent;
}
>;
small?: boolean;
onClick?: (e?: MouseEvent | TouchEvent) => void;
onHold?: VoidFunction;
}
export interface BaseClickable {
id: string;
type: typeof ClickableType;
[Component]: typeof ClickableComponent;
[GatherProps]: () => Record<string, unknown>;
}
export type Clickable<T extends ClickableOptions> = Replace<
T & BaseClickable,
{
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
canClick: GetComputableTypeWithDefault<T["canClick"], true>;
classes: GetComputableType<T["classes"]>;
style: GetComputableType<T["style"]>;
mark: GetComputableType<T["mark"]>;
display: GetComputableType<T["display"]>;
}
>;
export type GenericClickable = Replace<
Clickable<ClickableOptions>,
{
visibility: ProcessedComputable<Visibility | boolean>;
canClick: ProcessedComputable<boolean>;
}
>;
export function createClickable<T extends ClickableOptions>(
optionsFunc?: OptionsFunc<T, BaseClickable, GenericClickable>,
...decorators: Decorator<T, BaseClickable, GenericClickable>[]
): Clickable<T> {
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
return createLazyProxy(() => {
const clickable = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
clickable.id = getUniqueID("clickable-");
clickable.type = ClickableType;
clickable[Component] = ClickableComponent;
for (const decorator of decorators) {
decorator.preConstruct?.(clickable);
}
Object.assign(clickable, decoratedData);
processComputable(clickable as T, "visibility");
setDefault(clickable, "visibility", Visibility.Visible);
processComputable(clickable as T, "canClick");
setDefault(clickable, "canClick", true);
processComputable(clickable as T, "classes");
processComputable(clickable as T, "style");
processComputable(clickable as T, "mark");
processComputable(clickable as T, "display");
if (clickable.onClick) {
const onClick = clickable.onClick.bind(clickable);
clickable.onClick = function (e) {
if (unref(clickable.canClick) !== false) {
onClick(e);
}
};
}
if (clickable.onHold) {
const onHold = clickable.onHold.bind(clickable);
clickable.onHold = function () {
if (unref(clickable.canClick) !== false) {
onHold();
}
};
}
for (const decorator of decorators) {
decorator.postConstruct?.(clickable);
}
const decoratedProps = decorators.reduce((current, next) => Object.assign(current, next.getGatheredProps?.(clickable)), {});
clickable[GatherProps] = function (this: GenericClickable) {
const {
display,
visibility,
style,
classes,
onClick,
onHold,
canClick,
small,
mark,
id
} = this;
return {
display,
visibility,
style: unref(style),
classes,
onClick,
onHold,
canClick,
small,
mark,
id,
...decoratedProps
};
};
return clickable as unknown as Clickable<T>;
});
}
export function setupAutoClick(
layer: BaseLayer,
clickable: GenericClickable,
autoActive: Computable<boolean> = true
): Unsubscribe {
const isActive: ProcessedComputable<boolean> =
typeof autoActive === "function" ? computed(autoActive) : autoActive;
return layer.on("update", () => {
if (unref(isActive) && unref(clickable.canClick)) {
clickable.onClick?.();
}
});
}