Skip to content

Commit

Permalink
Improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
BlueManCZ committed Jan 12, 2025
1 parent c7565ae commit b120d09
Show file tree
Hide file tree
Showing 19 changed files with 184 additions and 149 deletions.
81 changes: 36 additions & 45 deletions src/client/apps/contwatch-client/app/[lang]/APIModels.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { APIModel } from "@repo/types/APIModel";
import { jsonFetcher } from "@repo/utils/communication";
import { Endpoint } from "@repo/utils/endpoints";
import { getApiEndpoint } from "@repo/utils/getApiEndpoint";
import { io } from "socket.io-client";
import type { MutatorCallback } from "swr";
Expand All @@ -9,72 +8,64 @@ import type { MutatorOptions } from "swr/_internal";
import { fetchJson } from "../../src/utils";
import { customMutate, customSWR } from "./swrUtils";

// TODO: Replace biome-ignore with biome-ignore-start when biome 2.0 is released
// https://github.com/biomejs/biome/pull/4649
export type APIModelEndpointConfig = { key: string; id?: number | string; params?: Record<string, string> };
export type APIModelEndpointConfigOverride = Omit<APIModelEndpointConfig, "key">;

// biome-ignore lint/complexity/noStaticOnlyClass: We use this static class as a base class for other API classes to abstract away the endpoints
export class API {
static endpoint(id?: number) {
return getApiEndpoint("/override-this", id);
export class APIModelEndpoint {
private readonly config: APIModelEndpointConfig;

constructor(config: APIModelEndpointConfig) {
this.config = config;
}

private _configMerge(config?: APIModelEndpointConfigOverride) {
return {
...this.config,
...config,
};
}

static fetch<T extends APIModel | number>(): Promise<T[]>;
static fetch<T extends APIModel | number>(id: number): Promise<T>;
static fetch<T extends APIModel | number>(id?: number) {
// biome-ignore lint/complexity/noThisInStatic: We are overriding endpoint in subclasses
return fetchJson<T>(this.endpoint(id));
public endpoint(config?: APIModelEndpointConfigOverride): string {
const c = this._configMerge(config);
return `${c.key}${c.id ? `/${c.id}` : ""}${c.params ? `?${new URLSearchParams(c.params).toString()}` : ""}`;
}

static mutate<Data = unknown, T = Data>(
id?: number,
public fetch<T>(config?: APIModelEndpointConfigOverride) {
return fetchJson<T>(this.endpoint(config));
}

public mutate<Data = unknown, T = Data>(
config?: APIModelEndpointConfigOverride,
data?: T | Promise<T> | MutatorCallback<T>,
opts?: boolean | MutatorOptions<Data, T>,
) {
// biome-ignore lint/complexity/noThisInStatic: We are overriding endpoint in subclasses
return customMutate(this.endpoint(id), data, opts);
return customMutate(this.endpoint(config), data, opts);
}

static async update<T extends APIModel>(data: T, onSuccess?: (response: Response) => void) {
// biome-ignore lint/complexity/noThisInStatic: We are overriding endpoint in subclasses
return this.mutate(data.id, data, {
optimisticData: data,
public async update<T extends APIModel | APIModel[]>(
data: T | Promise<T> | MutatorCallback<T>,
onSuccess?: (response: Response) => void,
config?: APIModelEndpointConfigOverride,
) {
return this.mutate(config, data, {
optimisticData: data instanceof Function ? undefined : data,
revalidate: false,
populateCache: true,
rollbackOnError: false,
}).then(() =>
// biome-ignore lint/complexity/noThisInStatic: We are overriding endpoint in subclasses
jsonFetcher(this.endpoint(data.id), "PUT", data).then((r) => {
// biome-ignore lint/complexity/noThisInStatic: We are overriding endpoint in subclasses
this.mutate(data.id, data).then(() => {
jsonFetcher(this.endpoint(config), "PUT", data).then((r) => {
this.mutate(config, data).then(() => {
onSuccess?.(r);
});
}),
);
}

public static use<T>(): { data: T[] }; // TODO: Can return undefined
public static use<T>(id: number): { data: T }; // TODO: Can return undefined
public static use<T>(id?: number) {
// biome-ignore lint/complexity/noThisInStatic: We are overriding endpoint in subclasses
return customSWR<T>(this.endpoint(id));
public use<T>(config?: APIModelEndpointConfigOverride) {
return customSWR<T>(this.endpoint(config));
}
}

export class Handlers extends API {
static override endpoint = (id?: number) => getApiEndpoint(Endpoint.handlers, id);
}

export class Attributes extends API {
static override endpoint = (id?: number) => getApiEndpoint(Endpoint.attributes, id);
}

export class DataStats extends API {
static override endpoint = () => getApiEndpoint(Endpoint.dataStats);
}

export const socket = io();

socket.on("mutate", (endpoint: string) => {
console.log("Mutating", endpoint);
customMutate(getApiEndpoint(endpoint));
});
socket.on("mutate", (endpoint: string) => customMutate(getApiEndpoint(endpoint)));
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Endpoint } from "@repo/utils/endpoints";
import { getApiEndpoint } from "@repo/utils/getApiEndpoint";

import { APIModelEndpoint } from "./APIModels";

export const Handlers = new APIModelEndpoint({ key: getApiEndpoint(Endpoint.handlers) });
export const Attributes = new APIModelEndpoint({ key: getApiEndpoint(Endpoint.attributes) });
export const DataStats = new APIModelEndpoint({ key: getApiEndpoint(Endpoint.dataStats) });
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ssrTranslation } from "@repo/utils/ssrTranslation";
import Link from "next/link";
import { SWRConfig } from "swr";

import { Attributes, Handlers } from "../../APIModels";
import { Attributes, Handlers } from "../../APIModelsDefinitions";
import { HandlersWrapper } from "../components/HandlersWrapper/HandlersWrapper";
import { HandlerWidget } from "../components/HandlerWidget/HandlerWidget";

Expand All @@ -23,19 +23,19 @@ export default async function HandlersPage({ params }: HandlersPageParams) {
const handlerId = Number.parseInt((await params).handlerId);

// Fetch one handler object for fallback
const fallbackHandler = await Handlers.fetch<HandlerModel>(handlerId);
const fallbackHandler = await Handlers.fetch<HandlerModel>({ id: handlerId });

// Fetch all attribute objects for fallback
const attributesFallback: Record<string, AttributeModel> = {};
for (const attribute of await Attributes.fetch<AttributeModel>()) {
attributesFallback[Attributes.endpoint(attribute.id)] = attribute;
for (const attribute of await Attributes.fetch<AttributeModel[]>()) {
attributesFallback[Attributes.endpoint({ id: attribute.id })] = attribute;
}

return (
<SWRConfig
value={{
fallback: {
[Handlers.endpoint(handlerId)]: fallbackHandler,
[Handlers.endpoint({ id: handlerId })]: fallbackHandler,
...attributesFallback,
},
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { useTranslation } from "@repo/utils/useTranslation";
import { useRouter } from "next/navigation";
import { type FC, useState } from "react";

import { Attributes, DataStats } from "../../../APIModels";
import { Attributes, DataStats } from "../../../APIModelsDefinitions";
import { useModel } from "../../../swrUtils";
import styles from "./AttributeWidget.module.scss";

Expand All @@ -44,14 +44,19 @@ export const AttributeWidget: FC<AttributeWidgetProps> = ({

const {
model: attribute = bareAttribute,
setModel: setAttribute,
resetModel: resetAttribute,
originalValue: originalAttribute,
set: setAttribute,
reset: resetAttribute,
original: originalAttribute,
lock: lockAttribute,
unlock: unlockAttribute,
} = useModel(Attributes.use<AttributeModel>(bareAttribute.id));
commit: commitAttribute,
} = useModel<AttributeModel>(Attributes, { id: bareAttribute.id });

const { data: dataStats } = DataStats.use<DataStatModel>();
const { model: dataStats } = useModel<DataStatModel[]>(DataStats, {
params: {
attribute: bareAttribute.id.toString(),
},
});

const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: bareAttribute.id,
Expand All @@ -71,16 +76,13 @@ export const AttributeWidget: FC<AttributeWidgetProps> = ({
return attribute.name;
};

const minStat = dataStats?.find((stat) => stat.type === "min" && stat.attribute === attribute?.id);
const maxStat = dataStats?.find((stat) => stat.type === "max" && stat.attribute === attribute?.id);
const minStat = dataStats?.find((stat) => stat.type === "min");
const maxStat = dataStats?.find((stat) => stat.type === "max");

const onPopupSave = () => {
if (attribute) {
Attributes.update(attribute).then(() => {
setEditPopupOpen(false);
});
}
}
commitAttribute();
setEditPopupOpen(false);
};

return (
<>
Expand Down Expand Up @@ -182,14 +184,15 @@ export const AttributeWidget: FC<AttributeWidgetProps> = ({
setEditPopupOpen(false);
}}
onEnter={onPopupSave}
title={originalAttribute.label ?? originalAttribute.name}
title={originalAttribute?.label ?? originalAttribute?.name}
>
<Column padding={"block"} gap={"1rem"}>
<Input
title={t("Custom label")}
value={attribute.label}
onValueChange={(value) =>
setAttribute((a) => {
if (!a) return a;
return {
...a,
label: value,
Expand All @@ -203,6 +206,7 @@ export const AttributeWidget: FC<AttributeWidgetProps> = ({
value={attribute.unit}
onValueChange={(value) =>
setAttribute((a) => {
if (!a) return a;
return {
...a,
unit: value,
Expand All @@ -215,6 +219,7 @@ export const AttributeWidget: FC<AttributeWidgetProps> = ({
value={attribute.icon}
onValueChange={(value) =>
setAttribute((a) => {
if (!a) return a;
return {
...a,
icon: value as IconType,
Expand All @@ -240,15 +245,7 @@ export const AttributeWidget: FC<AttributeWidgetProps> = ({
variant={"red"}
/>

<Button
grow
onClick={() => {
if (attribute)
Attributes.update(attribute).then(() => {
setEditPopupOpen(false);
});
}}
>
<Button grow onClick={onPopupSave}>
{t("Save")}
</Button>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ import { Icon } from "@repo/ui/Icon";
import { Text } from "@repo/ui/Text";
import { bemClassNames } from "@repo/utils/bemClassNames";
import { useTranslation } from "@repo/utils/useTranslation";
import debounce from "lodash.debounce";
import { DateTime } from "luxon";
import Link from "next/link";
import type { FC } from "react";

import { Handlers } from "../../../APIModels";
import { Handlers } from "../../../APIModelsDefinitions";
import { useModel } from "../../../swrUtils";
import { AttributeWidget } from "../AttributeWidget/AttributeWidget";
import styles from "./HandlerWidget.module.scss";
Expand All @@ -41,14 +40,14 @@ type HandlerWidgetProps = {

const bem = bemClassNames(styles);

const debouncedHandlerUpdate = debounce((handler: HandlerModel) => {
Handlers.update(handler);
}, 100);

export const HandlerWidget: FC<HandlerWidgetProps> = ({ handlerId, editMode }) => {
const { t } = useTranslation();

const { model: handler, setModel: setHandlerState } = useModel(Handlers.use<HandlerModel>(handlerId));
const {
model: handler,
set: setHandlerState,
commit: commitHandler,
} = useModel<HandlerModel>(Handlers, { id: handlerId });
const attributeIds = handler?.attributes.map((a) => a.id) ?? [];

const sensors = useSensors(
Expand All @@ -73,18 +72,17 @@ export const HandlerWidget: FC<HandlerWidgetProps> = ({ handlerId, editMode }) =

if (active.id !== over?.id) {
setHandlerState((handler) => {
if (!handler) return handler;

const oldIndex = handler.attributes.findIndex((attribute) => attribute.id === active.id);
const newIndex = handler.attributes.findIndex((attribute) => attribute.id === over?.id);

const newHandler = {
return {
...handler,
attributes: arrayMove(handler.attributes, oldIndex, newIndex),
};

debouncedHandlerUpdate(newHandler);

return newHandler;
});
commitHandler();
}
};

Expand Down Expand Up @@ -146,13 +144,15 @@ export const HandlerWidget: FC<HandlerWidgetProps> = ({ handlerId, editMode }) =
key={bareAttribute.id}
onAttributeDelete={() => {
setHandlerState((handler) => {
if (!handler) return handler;

const removedAttribute = handler.attributes.find(
(attribute) => attribute.id === bareAttribute.id,
);
const newAttributes = handler.attributes.filter(
(attribute) => attribute.id !== bareAttribute.id,
);
const newHandler = {
return {
...handler,
attributes: newAttributes,
availableAttributes: [
Expand All @@ -163,9 +163,8 @@ export const HandlerWidget: FC<HandlerWidgetProps> = ({ handlerId, editMode }) =
...handler.availableAttributes,
],
};
debouncedHandlerUpdate(newHandler);
return newHandler;
});
commitHandler();
}}
{...{ bareAttribute, editMode }}
/>
Expand Down Expand Up @@ -203,24 +202,25 @@ export const HandlerWidget: FC<HandlerWidgetProps> = ({ handlerId, editMode }) =
variant={"circle"}
onClick={() => {
setHandlerState((handler) => {
if (!handler) return handler;

const newAttributes = [
...handler.attributes,
{
id: -1,
name: attribute.name,
},
];
const newHandler = {
return {
...handler,
attributes: newAttributes,
availableAttributes:
handler.availableAttributes.filter(
(a) => a.name !== attribute.name,
),
};
debouncedHandlerUpdate(newHandler);
return newHandler;
});
commitHandler();
}}
/>
</Flex>
Expand Down
Loading

0 comments on commit b120d09

Please sign in to comment.