Formatted all .ts files

This commit is contained in:
Donkie
2023-10-18 19:21:57 +02:00
parent 42cd250a92
commit f064ea589c
7 changed files with 321 additions and 332 deletions

View File

@@ -7,178 +7,170 @@ type MethodTypes = "get" | "delete" | "head" | "options";
type MethodTypesWithBody = "post" | "put" | "patch"; type MethodTypesWithBody = "post" | "put" | "patch";
const dataProvider = ( const dataProvider = (
apiUrl: string, apiUrl: string,
httpClient: AxiosInstance = axiosInstance, httpClient: AxiosInstance = axiosInstance
): Omit< ): Omit<Required<DataProvider>, "createMany" | "updateMany" | "deleteMany"> => ({
Required<DataProvider>, getList: async ({ resource, meta, pagination, sorters, filters }) => {
"createMany" | "updateMany" | "deleteMany" const url = `${apiUrl}/${resource}`;
> => ({
getList: async ({ resource, meta, pagination, sorters, filters }) => {
const url = `${apiUrl}/${resource}`;
const { headers: headersFromMeta, method } = meta ?? {}; const { headers: headersFromMeta, method } = meta ?? {};
const queryParams: Record<string, string | number> = meta?.queryParams ?? {}; const queryParams: Record<string, string | number> = meta?.queryParams ?? {};
const requestMethod = (method as MethodTypes) ?? "get"; const requestMethod = (method as MethodTypes) ?? "get";
if (pagination && pagination.mode == "server") { if (pagination && pagination.mode == "server") {
const pageSize = pagination.pageSize ?? 10; const pageSize = pagination.pageSize ?? 10;
const offset = ((pagination.current ?? 1) - 1) * pageSize; const offset = ((pagination.current ?? 1) - 1) * pageSize;
queryParams["limit"] = pageSize; queryParams["limit"] = pageSize;
queryParams["offset"] = offset; queryParams["offset"] = offset;
}
if (sorters && sorters.length > 0) {
queryParams["sort"] = sorters
.map((sort) => {
const field = sort.field;
return `${field}:${sort.order}`;
})
.join(",");
}
if (filters && filters.length > 0) {
filters.forEach((filter) => {
if (!("field" in filter)) {
throw Error("Filter must be a LogicalFilter.");
} }
const field = filter.field;
if (filter.value.length > 0) {
const filterValueArray = Array.isArray(filter.value) ? filter.value : [filter.value];
if (sorters && sorters.length > 0) { const filterValue = filterValueArray
queryParams["sort"] = sorters.map((sort) => { .map((value) => {
const field = sort.field if (value === "<empty>") {
return `${field}:${sort.order}`; return "";
}).join(",") }
return value;
})
.join(",");
queryParams[field] = filterValue;
} }
});
}
if (filters && filters.length > 0) { const { data, headers } = await httpClient[requestMethod](`${url}`, {
filters.forEach(filter => { headers: headersFromMeta,
if (!("field" in filter)) { params: queryParams,
throw Error("Filter must be a LogicalFilter.") });
}
const field = filter.field
if (filter.value.length > 0) {
const filterValueArray = Array.isArray(filter.value) ? filter.value : [filter.value]
const filterValue = filterValueArray.map((value) => { // console.log(url, requestMethod, queryParams, data, headers)
if (value === "<empty>") {
return ""
}
return value
}).join(",")
queryParams[field] = filterValue return {
} data,
}); total: parseInt(headers["x-total-count"]) ?? 100,
} };
},
const { data, headers } = await httpClient[requestMethod]( getMany: async () => {
`${url}`, throw new Error("getMany not implemented");
{ },
headers: headersFromMeta,
params: queryParams,
},
);
// console.log(url, requestMethod, queryParams, data, headers) create: async ({ resource, variables, meta }) => {
const url = `${apiUrl}/${resource}`;
return { const { headers, method } = meta ?? {};
data, const requestMethod = (method as MethodTypesWithBody) ?? "post";
total: parseInt(headers["x-total-count"]) ?? 100,
};
},
getMany: async () => { const { data } = await httpClient[requestMethod](url, variables, {
throw new Error("getMany not implemented"); headers,
}, });
create: async ({ resource, variables, meta }) => { return {
const url = `${apiUrl}/${resource}`; data,
};
},
const { headers, method } = meta ?? {}; update: async ({ resource, id, variables, meta }) => {
const requestMethod = (method as MethodTypesWithBody) ?? "post"; const url = `${apiUrl}/${resource}/${id}`;
const { data } = await httpClient[requestMethod](url, variables, { const { headers, method } = meta ?? {};
headers, const requestMethod = (method as MethodTypesWithBody) ?? "patch";
const { data } = await httpClient[requestMethod](url, variables, {
headers,
});
return {
data,
};
},
getOne: async ({ resource, id, meta }) => {
const url = `${apiUrl}/${resource}/${id}`;
const { headers, method } = meta ?? {};
const requestMethod = (method as MethodTypes) ?? "get";
const { data } = await httpClient[requestMethod](url, { headers });
return {
data,
};
},
deleteOne: async ({ resource, id, variables, meta }) => {
const url = `${apiUrl}/${resource}/${id}`;
const { headers, method } = meta ?? {};
const requestMethod = (method as MethodTypesWithBody) ?? "delete";
const { data } = await httpClient[requestMethod](url, {
data: variables,
headers,
});
return {
data,
};
},
getApiUrl: () => {
return apiUrl;
},
custom: async ({ url, method, payload, query, headers }) => {
let requestUrl = `${url}?`;
if (query) {
requestUrl = `${requestUrl}&${stringify(query)}`;
}
if (headers) {
httpClient.defaults.headers = {
...httpClient.defaults.headers,
...headers,
};
}
let axiosResponse;
switch (method) {
case "put":
case "post":
case "patch":
axiosResponse = await httpClient[method](url, payload);
break;
case "delete":
axiosResponse = await httpClient.delete(url, {
data: payload,
}); });
break;
default:
axiosResponse = await httpClient.get(requestUrl);
break;
}
return { const { data } = axiosResponse;
data,
};
},
update: async ({ resource, id, variables, meta }) => { return Promise.resolve({ data });
const url = `${apiUrl}/${resource}/${id}`; },
const { headers, method } = meta ?? {};
const requestMethod = (method as MethodTypesWithBody) ?? "patch";
const { data } = await httpClient[requestMethod](url, variables, {
headers,
});
return {
data,
};
},
getOne: async ({ resource, id, meta }) => {
const url = `${apiUrl}/${resource}/${id}`;
const { headers, method } = meta ?? {};
const requestMethod = (method as MethodTypes) ?? "get";
const { data } = await httpClient[requestMethod](url, { headers });
return {
data,
};
},
deleteOne: async ({ resource, id, variables, meta }) => {
const url = `${apiUrl}/${resource}/${id}`;
const { headers, method } = meta ?? {};
const requestMethod = (method as MethodTypesWithBody) ?? "delete";
const { data } = await httpClient[requestMethod](url, {
data: variables,
headers,
});
return {
data,
};
},
getApiUrl: () => {
return apiUrl;
},
custom: async ({
url,
method,
payload,
query,
headers,
}) => {
let requestUrl = `${url}?`;
if (query) {
requestUrl = `${requestUrl}&${stringify(query)}`;
}
if (headers) {
httpClient.defaults.headers = {
...httpClient.defaults.headers,
...headers,
};
}
let axiosResponse;
switch (method) {
case "put":
case "post":
case "patch":
axiosResponse = await httpClient[method](url, payload);
break;
case "delete":
axiosResponse = await httpClient.delete(url, {
data: payload,
});
break;
default:
axiosResponse = await httpClient.get(requestUrl);
break;
}
const { data } = axiosResponse;
return Promise.resolve({ data });
},
}); });
export default dataProvider; export default dataProvider;

View File

@@ -4,13 +4,13 @@ import { BaseKey, LiveEvent, LiveProvider } from "@refinedev/core";
* A spoolman websocket event. * A spoolman websocket event.
*/ */
interface Event { interface Event {
type: "updated" | "deleted" | "added"; type: "updated" | "deleted" | "added";
resource: "filament" | "spool" | "vendor"; resource: "filament" | "spool" | "vendor";
date: string; date: string;
payload: { payload: {
id: number; id: number;
[key: string]: unknown; [key: string]: unknown;
}; };
} }
/** /**
@@ -21,25 +21,24 @@ interface Event {
* @returns The WebSocket URL * @returns The WebSocket URL
*/ */
function toWebsocketURL(apiUrl: string) { function toWebsocketURL(apiUrl: string) {
if (apiUrl[0] === "/") { if (apiUrl[0] === "/") {
// Relative URL, e.g. "/api/v1/..." // Relative URL, e.g. "/api/v1/..."
// Get the current browser URL // Get the current browser URL
const currentURL = window.location.href; const currentURL = window.location.href;
// Split the URL to separate the protocol, host, and path // Split the URL to separate the protocol, host, and path
const urlParts = currentURL.split('/'); const urlParts = currentURL.split("/");
const host = urlParts[2]; const host = urlParts[2];
// Create the WebSocket URL by adding "ws://" as the protocol and the relative URL as the path // Create the WebSocket URL by adding "ws://" as the protocol and the relative URL as the path
return `ws://${host}${apiUrl}`; return `ws://${host}${apiUrl}`;
} } else {
else { // Absolute URL, e.g. "https://example.com/api/v1/..."
// Absolute URL, e.g. "https://example.com/api/v1/..."
// Replace the protocol with "ws://" // Replace the protocol with "ws://"
return apiUrl.replace(/^http/, 'ws'); return apiUrl.replace(/^http/, "ws");
} }
} }
/** /**
@@ -51,77 +50,72 @@ function toWebsocketURL(apiUrl: string) {
* @param id Specific ID to subscribe to, if any. If not specified, subscribes to all IDs. * @param id Specific ID to subscribe to, if any. If not specified, subscribes to all IDs.
* @returns A function to unsubscribe from the resource * @returns A function to unsubscribe from the resource
*/ */
function subscribeSingle(apiUrl: string, channel: string, resource: string, callback: (event: LiveEvent) => void, id?: BaseKey) { function subscribeSingle(
// Verify that WebSockets are supported apiUrl: string,
if (!('WebSocket' in window)) { channel: string,
console.warn("WebSockets are not supported in this browser. Live updates will not be available."); resource: string,
return () => { }; callback: (event: LiveEvent) => void,
} id?: BaseKey
) {
// Verify that WebSockets are supported
if (!("WebSocket" in window)) {
console.warn("WebSockets are not supported in this browser. Live updates will not be available.");
return () => {};
}
const websocketURL = id ? toWebsocketURL(`${apiUrl}/${resource}/${id}`) : toWebsocketURL(`${apiUrl}/${resource}`); const websocketURL = id ? toWebsocketURL(`${apiUrl}/${resource}/${id}`) : toWebsocketURL(`${apiUrl}/${resource}`);
const ws = new WebSocket(websocketURL); const ws = new WebSocket(websocketURL);
ws.onmessage = (message) => { ws.onmessage = (message) => {
const data: Event = JSON.parse(message.data); const data: Event = JSON.parse(message.data);
const type = data.type === "added" ? "created" : data.type; const type = data.type === "added" ? "created" : data.type;
const date = new Date(data.date); const date = new Date(data.date);
const liveEvent: LiveEvent = { const liveEvent: LiveEvent = {
channel: channel, channel: channel,
type: type, type: type,
payload: { payload: {
data: data.payload, data: data.payload,
ids: [data.payload.id], ids: [data.payload.id],
}, },
date: date, date: date,
}
callback(liveEvent);
}; };
return () => { callback(liveEvent);
ws.close(); };
};
return () => {
ws.close();
};
} }
const liveProvider = ( const liveProvider = (apiUrl: string): LiveProvider => ({
apiUrl: string, subscribe: ({ channel, params, callback }) => {
): LiveProvider => ({ const { resource, subscriptionType, id, ids } = params ?? {};
subscribe: ({ channel, params, callback }) => {
const {
resource,
subscriptionType,
id,
ids,
} = params ?? {};
if (!subscriptionType) { if (!subscriptionType) {
throw new Error( throw new Error("[useSubscription]: `subscriptionType` is required in `params`");
"[useSubscription]: `subscriptionType` is required in `params`", }
);
}
if (!resource) { if (!resource) {
throw new Error( throw new Error("[useSubscription]: `resource` is required in `params`");
"[useSubscription]: `resource` is required in `params`", }
);
}
let idList: BaseKey[]; let idList: BaseKey[];
if (ids) idList = ids; if (ids) idList = ids;
else if (id) idList = [id]; else if (id) idList = [id];
else { else {
// No ID specified, subscribe to all IDs // No ID specified, subscribe to all IDs
return [subscribeSingle(apiUrl, channel, resource, callback)] return [subscribeSingle(apiUrl, channel, resource, callback)];
} }
return idList.map((id) => { return idList.map((id) => {
return subscribeSingle(apiUrl, channel, resource, callback, id); return subscribeSingle(apiUrl, channel, resource, callback, id);
}); });
}, },
unsubscribe: (closers: (() => void)[]) => { unsubscribe: (closers: (() => void)[]) => {
closers.forEach((fn) => fn()); closers.forEach((fn) => fn());
}, },
}) });
export default liveProvider; export default liveProvider;

View File

@@ -1,17 +1,16 @@
import { ISpool } from "./model"; import { ISpool } from "./model";
export async function setSpoolArchived(spool: ISpool, archived: boolean) { export async function setSpoolArchived(spool: ISpool, archived: boolean) {
const apiEndpoint = import.meta.env.VITE_APIURL; const apiEndpoint = import.meta.env.VITE_APIURL;
const init: RequestInit = { const init: RequestInit = {
method: "PATCH", method: "PATCH",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
archived: archived, archived: archived,
}), }),
}; };
const request = new Request(apiEndpoint + "/spool/" + spool.id); const request = new Request(apiEndpoint + "/spool/" + spool.id);
await fetch(request, init); await fetch(request, init);
} }

View File

@@ -1,13 +1,13 @@
import { CrudFilter, CrudOperators } from "@refinedev/core"; import { CrudFilter, CrudOperators } from "@refinedev/core";
interface TypedCrudFilter<Obj> { interface TypedCrudFilter<Obj> {
field: keyof Obj; field: keyof Obj;
operator: Exclude<CrudOperators, "or" | "and">; operator: Exclude<CrudOperators, "or" | "and">;
value: string[]; value: string[];
} }
export function typeFilters<Obj>(filters: CrudFilter[]): TypedCrudFilter<Obj>[] { export function typeFilters<Obj>(filters: CrudFilter[]): TypedCrudFilter<Obj>[] {
return filters as TypedCrudFilter<Obj>[]; // <-- Unsafe cast return filters as TypedCrudFilter<Obj>[]; // <-- Unsafe cast
} }
/** /**
@@ -16,12 +16,15 @@ export function typeFilters<Obj>(filters: CrudFilter[]): TypedCrudFilter<Obj>[]
* @param field The field to get the filter values for. * @param field The field to get the filter values for.
* @returns An array of filter values for the given field. * @returns An array of filter values for the given field.
*/ */
export function getFiltersForField<Obj, Field extends keyof Obj>(filters: TypedCrudFilter<Obj>[], field: Field): string[] { export function getFiltersForField<Obj, Field extends keyof Obj>(
const filterValues: string[] = []; filters: TypedCrudFilter<Obj>[],
filters.forEach((filter) => { field: Field
if (filter.field === field) { ): string[] {
filterValues.push(...filter.value as string[]); const filterValues: string[] = [];
} filters.forEach((filter) => {
}); if (filter.field === field) {
return filterValues; filterValues.push(...(filter.value as string[]));
}
});
return filterValues;
} }

View File

@@ -3,74 +3,74 @@ import { CrudFilter, CrudSort } from "@refinedev/core";
import { isLocalStorageAvailable } from "./support"; import { isLocalStorageAvailable } from "./support";
interface Pagination { interface Pagination {
current: number; current: number;
pageSize: number; pageSize: number;
} }
export interface TableState { export interface TableState {
sorters: CrudSort[]; sorters: CrudSort[];
filters: CrudFilter[]; filters: CrudFilter[];
pagination: Pagination; pagination: Pagination;
showColumns?: string[]; showColumns?: string[];
} }
export function useInitialTableState(tableId: string): TableState { export function useInitialTableState(tableId: string): TableState {
const [initialState] = React.useState(() => { const [initialState] = React.useState(() => {
const savedSorters = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-sorters`) : null; const savedSorters = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-sorters`) : null;
const savedFilters = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-filters`) : null; const savedFilters = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-filters`) : null;
const savedPagination = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-pagination`) : null; const savedPagination = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-pagination`) : null;
const savedShowColumns = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-showColumns`) : null; const savedShowColumns = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-showColumns`) : null;
const sorters = savedSorters ? JSON.parse(savedSorters) : [{ field: "id", order: "asc" }]; const sorters = savedSorters ? JSON.parse(savedSorters) : [{ field: "id", order: "asc" }];
const filters = savedFilters ? JSON.parse(savedFilters) : []; const filters = savedFilters ? JSON.parse(savedFilters) : [];
const pagination = savedPagination ? JSON.parse(savedPagination) : { page: 1, pageSize: 20 }; const pagination = savedPagination ? JSON.parse(savedPagination) : { page: 1, pageSize: 20 };
const showColumns = savedShowColumns ? JSON.parse(savedShowColumns) : undefined; const showColumns = savedShowColumns ? JSON.parse(savedShowColumns) : undefined;
return { sorters, filters, pagination, showColumns }; return { sorters, filters, pagination, showColumns };
}); });
return initialState; return initialState;
} }
export function useStoreInitialState(tableId: string, state: TableState) { export function useStoreInitialState(tableId: string, state: TableState) {
React.useEffect(() => { React.useEffect(() => {
if (isLocalStorageAvailable) { if (isLocalStorageAvailable) {
localStorage.setItem(`${tableId}-sorters`, JSON.stringify(state.sorters)); localStorage.setItem(`${tableId}-sorters`, JSON.stringify(state.sorters));
} }
}, [tableId, state.sorters]); }, [tableId, state.sorters]);
React.useEffect(() => { React.useEffect(() => {
if (isLocalStorageAvailable) { if (isLocalStorageAvailable) {
localStorage.setItem(`${tableId}-filters`, JSON.stringify(state.filters)); localStorage.setItem(`${tableId}-filters`, JSON.stringify(state.filters));
} }
}, [tableId, state.filters]); }, [tableId, state.filters]);
React.useEffect(() => { React.useEffect(() => {
if (isLocalStorageAvailable) { if (isLocalStorageAvailable) {
localStorage.setItem(`${tableId}-pagination`, JSON.stringify(state.pagination)); localStorage.setItem(`${tableId}-pagination`, JSON.stringify(state.pagination));
} }
}, [tableId, state.pagination]); }, [tableId, state.pagination]);
React.useEffect(() => { React.useEffect(() => {
if (isLocalStorageAvailable) { if (isLocalStorageAvailable) {
if (state.showColumns === undefined) { if (state.showColumns === undefined) {
localStorage.removeItem(`${tableId}-showColumns`); localStorage.removeItem(`${tableId}-showColumns`);
} else { } else {
localStorage.setItem(`${tableId}-showColumns`, JSON.stringify(state.showColumns)); localStorage.setItem(`${tableId}-showColumns`, JSON.stringify(state.showColumns));
} }
} }
}, [tableId, state.showColumns]); }, [tableId, state.showColumns]);
} }
export function useSavedState<T>(id: string, defaultValue: T) { export function useSavedState<T>(id: string, defaultValue: T) {
const [state, setState] = React.useState<T>(() => { const [state, setState] = React.useState<T>(() => {
const savedState = isLocalStorageAvailable ? localStorage.getItem(`savedStates-${id}`) : null; const savedState = isLocalStorageAvailable ? localStorage.getItem(`savedStates-${id}`) : null;
return savedState ? JSON.parse(savedState) : defaultValue; return savedState ? JSON.parse(savedState) : defaultValue;
}); });
React.useEffect(() => { React.useEffect(() => {
if (isLocalStorageAvailable) { if (isLocalStorageAvailable) {
localStorage.setItem(`savedStates-${id}`, JSON.stringify(state)); localStorage.setItem(`savedStates-${id}`, JSON.stringify(state));
} }
}, [id, state]); }, [id, state]);
return [state, setState] as const; return [state, setState] as const;
} }

View File

@@ -1,10 +1,9 @@
import { CrudSort } from "@refinedev/core"; import { CrudSort } from "@refinedev/core";
import { SortOrder } from "antd/es/table/interface"; import { SortOrder } from "antd/es/table/interface";
interface TypedCrudSort<Obj> { interface TypedCrudSort<Obj> {
field: keyof Obj; field: keyof Obj;
order: "asc" | "desc"; order: "asc" | "desc";
} }
/** /**
@@ -13,14 +12,17 @@ interface TypedCrudSort<Obj> {
* @param field The field to get the sort order for. * @param field The field to get the sort order for.
* @returns The sort order for the given field, or undefined if the field is not being sorted. * @returns The sort order for the given field, or undefined if the field is not being sorted.
*/ */
export function getSortOrderForField<Obj, Field extends keyof Obj>(sorters: TypedCrudSort<Obj>[], field: Field): SortOrder | undefined { export function getSortOrderForField<Obj, Field extends keyof Obj>(
const sorter = sorters.find((s) => s.field === field); sorters: TypedCrudSort<Obj>[],
if (sorter) { field: Field
return sorter.order === "asc" ? "ascend" : "descend"; ): SortOrder | undefined {
} const sorter = sorters.find((s) => s.field === field);
return undefined; if (sorter) {
return sorter.order === "asc" ? "ascend" : "descend";
}
return undefined;
} }
export function typeSorters<Obj>(sorters: CrudSort[]): TypedCrudSort<Obj>[] { export function typeSorters<Obj>(sorters: CrudSort[]): TypedCrudSort<Obj>[] {
return sorters as TypedCrudSort<Obj>[]; // <-- Unsafe cast return sorters as TypedCrudSort<Obj>[]; // <-- Unsafe cast
} }

View File

@@ -1,12 +1,11 @@
function _isLocalStorageAvailable(): boolean { function _isLocalStorageAvailable(): boolean {
try { try {
localStorage.setItem('test', 'test'); localStorage.setItem("test", "test");
localStorage.removeItem('test'); localStorage.removeItem("test");
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
} }
} }
export const isLocalStorageAvailable = _isLocalStorageAvailable(); export const isLocalStorageAvailable = _isLocalStorageAvailable();