2022-03-04 03:39:48 +00:00
|
|
|
import Col from "components/layout/Column.vue";
|
|
|
|
import Row from "components/layout/Row.vue";
|
2022-01-14 04:25:47 +00:00
|
|
|
import {
|
|
|
|
CoercableComponent,
|
|
|
|
Component as ComponentKey,
|
2022-02-27 19:49:34 +00:00
|
|
|
GatherProps,
|
|
|
|
GenericComponent,
|
2022-03-27 18:47:36 +00:00
|
|
|
JSXFunction,
|
|
|
|
Visibility
|
2022-03-04 03:39:48 +00:00
|
|
|
} from "features/feature";
|
2022-01-25 04:25:34 +00:00
|
|
|
import {
|
|
|
|
Component,
|
|
|
|
computed,
|
|
|
|
ComputedRef,
|
|
|
|
DefineComponent,
|
|
|
|
defineComponent,
|
2022-02-27 19:49:34 +00:00
|
|
|
isRef,
|
2022-03-11 23:01:22 +00:00
|
|
|
onUnmounted,
|
2022-01-25 04:25:34 +00:00
|
|
|
PropType,
|
|
|
|
ref,
|
|
|
|
Ref,
|
2022-02-27 19:49:34 +00:00
|
|
|
ShallowRef,
|
|
|
|
shallowRef,
|
2022-01-25 04:25:34 +00:00
|
|
|
unref,
|
2022-02-27 19:49:34 +00:00
|
|
|
watchEffect
|
2022-01-25 04:25:34 +00:00
|
|
|
} from "vue";
|
2022-02-27 19:49:34 +00:00
|
|
|
import { DoNotCache, ProcessedComputable } from "./computed";
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
export function coerceComponent(
|
|
|
|
component: CoercableComponent,
|
|
|
|
defaultWrapper = "span"
|
|
|
|
): DefineComponent {
|
|
|
|
if (typeof component === "function") {
|
|
|
|
return defineComponent({ render: component });
|
|
|
|
}
|
2022-01-14 04:25:47 +00:00
|
|
|
if (typeof component === "string") {
|
2022-02-27 19:49:34 +00:00
|
|
|
if (component.length > 0) {
|
|
|
|
component = component.trim();
|
|
|
|
if (component.charAt(0) !== "<") {
|
|
|
|
component = `<${defaultWrapper}>${component}</${defaultWrapper}>`;
|
|
|
|
}
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
return defineComponent({ template: component });
|
|
|
|
}
|
|
|
|
return defineComponent({ render: () => ({}) });
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
return component;
|
|
|
|
}
|
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
export type VueFeature = {
|
|
|
|
[ComponentKey]: GenericComponent;
|
|
|
|
[GatherProps]: () => Record<string, unknown>;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function render(object: VueFeature | CoercableComponent): JSX.Element | DefineComponent {
|
|
|
|
if (isCoercableComponent(object)) {
|
|
|
|
if (typeof object === "function") {
|
|
|
|
return (object as JSXFunction)();
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
2022-02-27 19:49:34 +00:00
|
|
|
return coerceComponent(object);
|
|
|
|
}
|
|
|
|
const Component = object[ComponentKey];
|
|
|
|
return <Component {...object[GatherProps]()} />;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
export function renderRow(...objects: (VueFeature | CoercableComponent)[]): JSX.Element {
|
2022-02-27 22:41:39 +00:00
|
|
|
return <Row>{objects.map(render)}</Row>;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
2022-02-27 19:49:34 +00:00
|
|
|
export function renderCol(...objects: (VueFeature | CoercableComponent)[]): JSX.Element {
|
2022-02-27 22:41:39 +00:00
|
|
|
return <Col>{objects.map(render)}</Col>;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
|
2022-04-16 05:55:35 +00:00
|
|
|
export function renderJSX(object: VueFeature | CoercableComponent): JSX.Element {
|
|
|
|
if (isCoercableComponent(object)) {
|
|
|
|
if (typeof object === "function") {
|
|
|
|
return (object as JSXFunction)();
|
|
|
|
}
|
|
|
|
if (typeof object === "string") {
|
|
|
|
return <>{object}</>;
|
|
|
|
}
|
|
|
|
// TODO why is object typed as never?
|
|
|
|
const Comp = object as DefineComponent;
|
|
|
|
return <Comp />;
|
|
|
|
}
|
|
|
|
const Component = object[ComponentKey];
|
|
|
|
return <Component {...object[GatherProps]()} />;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function renderRowJSX(...objects: (VueFeature | CoercableComponent)[]): JSX.Element {
|
|
|
|
return <Row>{objects.map(renderJSX)}</Row>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function renderColJSX(...objects: (VueFeature | CoercableComponent)[]): JSX.Element {
|
|
|
|
return <Col>{objects.map(renderJSX)}</Col>;
|
|
|
|
}
|
|
|
|
|
2022-01-14 04:25:47 +00:00
|
|
|
export function isCoercableComponent(component: unknown): component is CoercableComponent {
|
|
|
|
if (typeof component === "string") {
|
|
|
|
return true;
|
|
|
|
} else if (typeof component === "object") {
|
|
|
|
if (component == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return "render" in component || "component" in component;
|
2022-02-27 19:49:34 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
} else if (typeof component === "function" && (component as any)[DoNotCache] === true) {
|
|
|
|
return true;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setupHoldToClick(
|
2022-03-27 05:14:35 +00:00
|
|
|
onClick?: Ref<((e?: MouseEvent | TouchEvent) => void) | undefined>,
|
2022-01-14 04:25:47 +00:00
|
|
|
onHold?: Ref<VoidFunction | undefined>
|
|
|
|
): {
|
2022-03-27 05:14:35 +00:00
|
|
|
start: (e: MouseEvent | TouchEvent) => void;
|
2022-01-14 04:25:47 +00:00
|
|
|
stop: VoidFunction;
|
|
|
|
handleHolding: VoidFunction;
|
|
|
|
} {
|
2022-01-25 04:25:34 +00:00
|
|
|
const interval = ref<null | number>(null);
|
2022-03-27 05:14:35 +00:00
|
|
|
const event = ref<MouseEvent | TouchEvent | undefined>(undefined);
|
2022-01-14 04:25:47 +00:00
|
|
|
|
2022-03-27 05:14:35 +00:00
|
|
|
function start(e: MouseEvent | TouchEvent) {
|
2022-01-25 04:25:34 +00:00
|
|
|
if (!interval.value) {
|
|
|
|
interval.value = setInterval(handleHolding, 250);
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
2022-03-27 05:14:35 +00:00
|
|
|
event.value = e;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
function stop() {
|
2022-01-25 04:25:34 +00:00
|
|
|
if (interval.value) {
|
|
|
|
clearInterval(interval.value);
|
|
|
|
interval.value = null;
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
function handleHolding() {
|
|
|
|
if (onHold && onHold.value) {
|
|
|
|
onHold.value();
|
|
|
|
} else if (onClick && onClick.value) {
|
2022-03-27 05:14:35 +00:00
|
|
|
onClick.value(event.value);
|
2022-01-14 04:25:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:01:22 +00:00
|
|
|
onUnmounted(stop);
|
|
|
|
|
2022-01-14 04:25:47 +00:00
|
|
|
return { start, stop, handleHolding };
|
|
|
|
}
|
2022-01-25 04:25:34 +00:00
|
|
|
|
2022-03-27 18:47:36 +00:00
|
|
|
export function getFirstFeature<T extends { visibility: ProcessedComputable<Visibility> }>(
|
|
|
|
features: T[],
|
|
|
|
filter: (feature: T) => boolean
|
|
|
|
): { firstFeature: Ref<T | undefined>; hiddenFeatures: Ref<T[]> } {
|
|
|
|
const filteredFeatures = computed(() =>
|
|
|
|
features.filter(
|
|
|
|
feature => unref(feature.visibility) === Visibility.Visible && filter(feature)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
firstFeature: computed(() => filteredFeatures.value[0]),
|
|
|
|
hiddenFeatures: computed(() => filteredFeatures.value.slice(1))
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-01-25 04:25:34 +00:00
|
|
|
export function computeComponent(
|
2022-02-27 19:49:34 +00:00
|
|
|
component: Ref<ProcessedComputable<CoercableComponent>>,
|
|
|
|
defaultWrapper = "div"
|
|
|
|
): ShallowRef<Component | JSXFunction | ""> {
|
|
|
|
const comp = shallowRef<Component | JSXFunction | "">();
|
|
|
|
watchEffect(() => {
|
|
|
|
comp.value = coerceComponent(unwrapRef(component), defaultWrapper);
|
2022-01-25 04:25:34 +00:00
|
|
|
});
|
2022-02-27 19:49:34 +00:00
|
|
|
return comp as ShallowRef<Component | JSXFunction | "">;
|
2022-01-25 04:25:34 +00:00
|
|
|
}
|
|
|
|
export function computeOptionalComponent(
|
2022-02-27 19:49:34 +00:00
|
|
|
component: Ref<ProcessedComputable<CoercableComponent | undefined> | undefined>,
|
|
|
|
defaultWrapper = "div"
|
|
|
|
): ShallowRef<Component | JSXFunction | "" | null> {
|
|
|
|
const comp = shallowRef<Component | JSXFunction | "" | null>(null);
|
|
|
|
watchEffect(() => {
|
|
|
|
const currComponent = unwrapRef(component);
|
|
|
|
comp.value = currComponent == null ? null : coerceComponent(currComponent, defaultWrapper);
|
2022-01-25 04:25:34 +00:00
|
|
|
});
|
2022-02-27 19:49:34 +00:00
|
|
|
return comp;
|
2022-01-25 04:25:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function wrapRef<T>(ref: Ref<ProcessedComputable<T>>): ComputedRef<T> {
|
|
|
|
return computed(() => unwrapRef(ref));
|
|
|
|
}
|
|
|
|
|
|
|
|
export function unwrapRef<T>(ref: Ref<ProcessedComputable<T>>): T {
|
2022-02-27 19:49:34 +00:00
|
|
|
return unref<T>(unref(ref));
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setRefValue<T>(ref: Ref<T | Ref<T>>, value: T) {
|
|
|
|
if (isRef(ref.value)) {
|
|
|
|
ref.value.value = value;
|
|
|
|
} else {
|
|
|
|
ref.value = value;
|
|
|
|
}
|
2022-01-25 04:25:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-09 01:40:51 +00:00
|
|
|
export type PropTypes =
|
2022-01-25 04:25:34 +00:00
|
|
|
| typeof Boolean
|
|
|
|
| typeof String
|
|
|
|
| typeof Number
|
|
|
|
| typeof Function
|
|
|
|
| typeof Object
|
|
|
|
| typeof Array;
|
|
|
|
// TODO Unfortunately, the typescript engine gives up on typing completely when you use this method,
|
|
|
|
// Even though it has the same typing as when doing it manually
|
|
|
|
export function processedPropType<T>(...types: PropTypes[]): PropType<ProcessedComputable<T>> {
|
|
|
|
if (!types.includes(Object)) {
|
|
|
|
types.push(Object);
|
|
|
|
}
|
|
|
|
return types as PropType<ProcessedComputable<T>>;
|
|
|
|
}
|