This commit is contained in:
2026-03-04 17:35:14 +01:00
parent f73b56cdc4
commit db26e26495
113 changed files with 12433 additions and 169 deletions
@@ -0,0 +1,37 @@
import React from "react";
type Param = {
/**
* Custom Event Name
*/
name: string;
};
/**
* # Dispatch Custom Event
*/
export default function useCustomEventDispatch<
T extends { [key: string]: any } = { [key: string]: any }
>({ name }: Param) {
const dispatchCustomEvent = React.useCallback((value: T | string) => {
let dataParsed = typeof value == "object" ? value : undefined;
const str = typeof value == "string" ? value : undefined;
if (str) {
try {
dataParsed = JSON.parse(str);
} catch (error) {}
}
const event = new CustomEvent(name, {
detail: {
data: dataParsed,
message: str,
},
});
window.dispatchEvent(event);
}, []);
return { dispatchCustomEvent };
}
@@ -0,0 +1,39 @@
import React from "react";
type Param = {
/**
* Custom Event Name
*/
name: string;
};
/**
* # Listen For Custom Event
*/
export default function useCustomEventListener<
T extends { [key: string]: any } = { [key: string]: any }
>({ name }: Param) {
const [data, setData] = React.useState<T | undefined>(undefined);
const [message, setMessage] = React.useState<string | undefined>(undefined);
const dataEventListenerCallback = React.useCallback((e: Event) => {
const customEvent = e as CustomEvent;
const eventPayloadMessage = customEvent.detail.message as
| string
| undefined;
const eventPayloadData = customEvent.detail.data as T | undefined;
if (eventPayloadMessage) setMessage(eventPayloadMessage);
if (eventPayloadData) setData(eventPayloadData);
}, []);
React.useEffect(() => {
window.addEventListener(name, dataEventListenerCallback, false);
return function () {
window.removeEventListener(name, dataEventListenerCallback);
};
}, []);
return { data, message };
}
@@ -0,0 +1,79 @@
import React from "react";
type Param = {
elementRef?: React.RefObject<Element | undefined>;
className?: string;
elId?: string;
options?: IntersectionObserverInit;
removeIntersected?: boolean;
delay?: number;
};
let timeout: any;
export default function useIntersectionObserver({
elementRef,
className,
options,
removeIntersected,
delay,
elId,
}: Param) {
const [isIntersecting, setIsIntersecting] = React.useState(false);
const [refresh, setRefresh] = React.useState(0);
const observerTriggerDelay = delay || 200;
const observerCallback: IntersectionObserverCallback = React.useCallback(
(entries, observer) => {
const entry = entries[0];
window.clearTimeout(timeout);
if (entry.isIntersecting) {
timeout = setTimeout(() => {
setIsIntersecting(true);
if (removeIntersected) {
observer.unobserve(entry.target);
}
}, observerTriggerDelay);
} else {
setIsIntersecting(false);
}
},
[]
);
React.useEffect(() => {
const element = elId
? document.getElementById(elId)
: elementRef?.current;
const elements = className
? document.querySelectorAll(`.${className}`)
: null;
if (!element && !className && refresh < 5) {
requestAnimationFrame(() => {
setTimeout(() => {
setRefresh(refresh + 1);
}, 2000);
});
return;
}
const observer = new IntersectionObserver(observerCallback, {
rootMargin: "0px 0px 0px 0px",
...options,
});
if (elements) {
elements.forEach((el) => {
observer.observe(el);
});
} else if (element) {
observer.observe(element);
}
}, [refresh]);
return { isIntersecting };
}
+31
View File
@@ -0,0 +1,31 @@
import React from "react";
export type UseLocalStorageParam<
T extends { [key: string]: any } = { [key: string]: any }
> = {
key: keyof T;
};
/**
* # Use Local Storage
*/
export default function useLocalStorage<
T extends Record<string, any> | undefined = undefined
>(param?: UseLocalStorageParam) {
const [data, setData] =
React.useState<T extends undefined ? string | null : T>();
React.useEffect(() => {
if (param?.key) {
const value = localStorage.getItem(param.key as string);
try {
const jsonValue = JSON.parse(value || "");
setData(jsonValue as any);
} catch (error) {
setData(value as any);
}
}
}, []);
return { data };
}
+29
View File
@@ -0,0 +1,29 @@
import React from "react";
type Params = {
timeout?: number;
};
let timeout: any;
export default function useReady(params?: Params) {
const [ready, setReady] = React.useState(false);
const finalTimeout = params?.timeout || 300;
React.useEffect(() => {
requestAnimationFrame(() => {
window.clearTimeout(timeout);
timeout = setTimeout(() => {
setReady(true);
}, finalTimeout);
});
return function () {
window.clearTimeout(timeout);
};
}, []);
return { ready };
}
+35
View File
@@ -0,0 +1,35 @@
import React from "react";
type Params = {
initialLoading?: boolean;
initialReady?: boolean;
initialOpen?: boolean;
};
export type UseStatusStatusType = {
msg?: string;
error?: boolean;
};
export default function useStatus(params?: Params) {
const [refresh, setRefresh] = React.useState(0);
const [loading, setLoading] = React.useState(
params?.initialLoading || false,
);
const [status, setStatus] = React.useState<UseStatusStatusType>({});
const [ready, setReady] = React.useState(params?.initialReady || false);
const [open, setOpen] = React.useState(params?.initialOpen || false);
return {
refresh,
setRefresh,
loading,
setLoading,
status,
setStatus,
ready,
setReady,
open,
setOpen,
};
}
+209
View File
@@ -0,0 +1,209 @@
import React, { useRef } from "react";
export type UseWebsocketHookParams = {
debounce?: number;
url: string;
disableReconnect?: boolean;
/** Interval to ping the websocket. So that the connection doesn't go down. Default 30000ms (30 seconds) */
keepAliveDuration?: number;
refreshConnection?: number;
};
export const WebSocketEventNames = ["wsDataEvent", "wsMessageEvent"] as const;
/**
* # Use Websocket Hook
* @event wsDataEvent Listen for event named `wsDataEvent` on `window` to receive Data events
* @event wsMessageEvent Listen for event named `wsMessageEvent` on `window` to receive Message events
*
* @example window.addEventLiatener("wsDataEvent", (e)=>{
* console.log(e.detail.data) // type object
* })
* @example window.addEventLiatener("wsMessageEvent", (e)=>{
* console.log(e.detail.message) // type string
* })
*/
export default function useWebSocket<
T extends { [key: string]: any } = { [key: string]: any }
>({
url,
debounce,
disableReconnect,
keepAliveDuration,
refreshConnection,
}: UseWebsocketHookParams) {
const DEBOUNCE = debounce || 500;
const KEEP_ALIVE_DURATION = keepAliveDuration || 1000 * 30;
const KEEP_ALIVE_TIMEOUT = 1000 * 60 * 3;
const KEEP_ALIVE_MESSAGE = "twui::ping";
let uptime = 0;
let tries = useRef(0);
// const queue: string[] = [];
const msgInterval = useRef<any>(null);
const sendInterval = useRef<any>(null);
const keepAliveInterval = useRef<any>(null);
const [socket, setSocket] = React.useState<WebSocket | undefined>(
undefined
);
const messageQueueRef = React.useRef<string[]>([]);
const sendMessageQueueRef = React.useRef<string[]>([]);
/**
* # Dispatch Custom Event
*/
const dispatchCustomEvent = React.useCallback(
(evtName: (typeof WebSocketEventNames)[number], value: string | T) => {
const event = new CustomEvent(evtName, {
detail: {
data: value,
message: value,
},
});
window.dispatchEvent(event);
},
[]
);
/**
* # Connect to Websocket
*/
const connect = React.useCallback(() => {
const domain = window.location.origin;
const wsURL = url.startsWith(`ws`)
? url
: domain.replace(/^http/, "ws") + ("/" + url).replace(/\/\//g, "/");
if (!wsURL) return;
let ws = new WebSocket(wsURL);
ws.onerror = (ev) => {
console.log(`Websocket ERROR:`);
};
ws.onmessage = (ev) => {
messageQueueRef.current.push(ev.data);
};
ws.onopen = (ev) => {
window.clearInterval(keepAliveInterval.current);
keepAliveInterval.current = window.setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(KEEP_ALIVE_MESSAGE);
}
}, KEEP_ALIVE_DURATION);
setSocket(ws);
console.log(`Websocket connected to ${wsURL}`);
};
ws.onclose = (ev) => {
console.log("Websocket closed!", {
code: ev.code,
reason: ev.reason,
wasClean: ev.wasClean,
});
if (disableReconnect) return;
console.log("Attempting to reconnect ...");
console.log("URL:", url);
window.clearInterval(keepAliveInterval.current);
console.log("tries", tries);
if (tries.current >= 3) {
return;
}
console.log("Attempting to reconnect ...");
tries.current += 1;
connect();
};
}, []);
/**
* # Initial Connection
*/
React.useEffect(() => {
if (socket) return;
connect();
}, []);
React.useEffect(() => {
if (!socket) return;
sendInterval.current = setInterval(handleSendMessageQueue, DEBOUNCE);
msgInterval.current = setInterval(handleReceivedMessageQueue, DEBOUNCE);
return function () {
window.clearInterval(sendInterval.current);
window.clearInterval(msgInterval.current);
};
}, [socket]);
/**
* Received Message Queue Handler
*/
const handleReceivedMessageQueue = React.useCallback(() => {
try {
const msg = messageQueueRef.current.shift();
if (!msg) return;
const jsonData = JSON.parse(msg);
dispatchCustomEvent("wsMessageEvent", msg);
dispatchCustomEvent("wsDataEvent", jsonData);
} catch (error) {
console.log("Unable to parse string. Returning string.");
}
}, []);
/**
* Send Message Queue Handler
*/
const handleSendMessageQueue = React.useCallback(() => {
if (!socket || socket.readyState !== WebSocket.OPEN) {
window.clearInterval(sendInterval.current);
return;
}
const newMessage = sendMessageQueueRef.current.shift();
if (!newMessage) return;
socket.send(newMessage);
}, [socket]);
/**
* # Send Data Function
*/
const sendData = React.useCallback(
(data: T) => {
try {
const queueItemJSON = JSON.stringify(data);
const existingQueue = sendMessageQueueRef.current.find(
(q) => q == queueItemJSON
);
if (!existingQueue) {
sendMessageQueueRef.current.push(queueItemJSON);
}
} catch (error: any) {
console.log("Error Sending socket message", error.message);
}
},
[socket]
);
return { socket, sendData };
}
@@ -0,0 +1,40 @@
import React from "react";
import { WebSocketEventNames } from "./useWebSocket";
type Param = {
listener?: (typeof WebSocketEventNames)[number];
};
/**
* # Use Websocket Data Event Handler Hook
*/
export default function useWebSocketEventHandler<
T extends { [key: string]: any } = { [key: string]: any }
>(param?: Param) {
const [data, setData] = React.useState<T | undefined>(undefined);
const [message, setMessage] = React.useState<string | undefined>(undefined);
React.useEffect(() => {
const dataEventListenerCallback = (e: Event) => {
const customEvent = e as CustomEvent;
const data = customEvent.detail.data as T | undefined;
const __msg = customEvent.detail.message as string | undefined;
if (data) setData(data);
if (__msg && typeof __msg == "string") setMessage(__msg);
};
const messageEventName: (typeof WebSocketEventNames)[number] =
param?.listener || "wsDataEvent";
window.addEventListener(messageEventName, dataEventListenerCallback);
return function () {
window.removeEventListener(
messageEventName,
dataEventListenerCallback
);
};
}, []);
return { data, message };
}
+24
View File
@@ -0,0 +1,24 @@
import { useCallback, useEffect, useState } from "react";
export default function useWindowFocus() {
const [isWindowFocused, setIsWindowFocused] = useState(false);
const windowFocusCb = useCallback(() => {
setIsWindowFocused(true);
}, []);
const windowBlurCb = useCallback(() => {
setIsWindowFocused(false);
}, []);
useEffect(() => {
window.addEventListener("focus", windowFocusCb);
window.addEventListener("blur", windowBlurCb);
return function () {
window.removeEventListener("focus", windowFocusCb);
window.removeEventListener("blur", windowBlurCb);
};
}, []);
return { isWindowFocused };
}