Skip to content

Commit 0c1dd3c

Browse files
committed
SHADCN: new shadcn directory for components.
1 parent 69d2b79 commit 0c1dd3c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+6787
-0
lines changed

src/shared/lib/audio-service.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
class AudioService {
2+
private audio: HTMLAudioElement;
3+
private isUnlocked: boolean = false;
4+
5+
constructor() {
6+
this.audio = new Audio('/sounds/notification.mp3');
7+
this.audio.volume = 0.5;
8+
9+
this.audio.preload = 'auto';
10+
this.audio.load();
11+
}
12+
13+
/**
14+
* Call this on the first user interaction (click/keydown).
15+
* It plays a silent snippet to "bless" the audio element in the browser's eyes.
16+
*/
17+
async unlock() {
18+
if (this.isUnlocked) return;
19+
20+
try {
21+
const originalVolume = this.audio.volume;
22+
this.audio.volume = 0;
23+
24+
await this.audio.play();
25+
26+
this.audio.pause();
27+
this.audio.currentTime = 0;
28+
this.audio.volume = originalVolume;
29+
this.isUnlocked = true;
30+
} catch (e) {
31+
// This is expected if called programmatically without user gesture.
32+
// We swallow the error and wait for the next interaction.
33+
console.debug('Audio unlock pending user interaction...');
34+
}
35+
}
36+
37+
async play() {
38+
try {
39+
this.audio.currentTime = 0;
40+
await this.audio.play();
41+
} catch (e) {
42+
console.warn(
43+
'Notification sound blocked. User has not interacted with the page yet.',
44+
e,
45+
);
46+
}
47+
}
48+
}
49+
50+
export const audioService = new AudioService();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as curlconverter from 'curlconverter';
2+
3+
export interface ParsedCurlCommand {
4+
url?: string;
5+
raw_url?: string;
6+
method?: string;
7+
queries?: Record<string, string | string[]>;
8+
headers?: Record<string, string>;
9+
data?: unknown;
10+
}
11+
12+
export function toJsonObject(curl: string): ParsedCurlCommand {
13+
return curlconverter.toJsonObject(curl) as ParsedCurlCommand;
14+
}

src/shared/lib/event-bus.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
type EventHandler = (data?: any) => void;
2+
3+
/**
4+
* A simple, global event bus for decoupled communication between different parts of the application.
5+
* This allows the globally-scoped QueryClient to notify React components of events without
6+
* creating a direct dependency.
7+
*/
8+
class EventBus {
9+
private events: Record<string, EventHandler[]> = {};
10+
11+
/**
12+
* Subscribes to an event.
13+
* @param event The name of the event.
14+
* @param callback The function to execute when the event is dispatched.
15+
*/
16+
public on(event: string, callback: EventHandler): void {
17+
if (!this.events[event]) {
18+
this.events[event] = [];
19+
}
20+
this.events[event].push(callback);
21+
}
22+
23+
/**
24+
* Unsubscribes from an event.
25+
* @param event The name of the event.
26+
* @param callback The callback function to remove.
27+
*/
28+
public off(event: string, callback: EventHandler): void {
29+
if (!this.events[event]) {
30+
return;
31+
}
32+
this.events[event] = this.events[event].filter((cb) => cb !== callback);
33+
}
34+
35+
/**
36+
* Dispatches an event, calling all subscribed callbacks.
37+
* @param event The name of the event to dispatch.
38+
* @param data Optional data to pass to the event handlers.
39+
*/
40+
public dispatch(event: string, data?: any): void {
41+
if (!this.events[event]) {
42+
return;
43+
}
44+
this.events[event].forEach((callback) => callback(data));
45+
}
46+
}
47+
48+
// A singleton instance of the EventBus for the entire application.
49+
export const appEventBus = new EventBus();
50+
51+
// A centralized object for defining event names to prevent typos.
52+
export const AppEvents = {
53+
API_UNAUTHORIZED: 'onApiError401',
54+
};

src/shared/lib/general.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Performs a deep equality check between two values of the same type.
3+
* Handles Primitives, Arrays, Objects, Maps, Sets, Dates, and RegExps.
4+
*/
5+
export function deepEqual<T>(a: T, b: T): boolean {
6+
if (a === b) return true;
7+
8+
if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') {
9+
return a !== a && b !== b; // Handle NaN
10+
}
11+
12+
if (a.constructor !== b.constructor) return false;
13+
14+
if (Array.isArray(a)) {
15+
const arrA = a as unknown[];
16+
const arrB = b as unknown[];
17+
18+
if (arrA.length !== arrB.length) return false;
19+
20+
for (let i = 0; i < arrA.length; i++) {
21+
if (!deepEqual(arrA[i], arrB[i])) return false;
22+
}
23+
24+
return true;
25+
}
26+
27+
if (a instanceof Map && b instanceof Map) {
28+
if (a.size !== b.size) return false;
29+
30+
for (const [key, val] of a) {
31+
if (!b.has(key)) return false;
32+
if (!deepEqual(val, b.get(key))) return false;
33+
}
34+
35+
return true;
36+
}
37+
38+
if (a instanceof Set && b instanceof Set) {
39+
if (a.size !== b.size) return false;
40+
41+
for (const val of a) {
42+
if (!b.has(val)) return false;
43+
}
44+
45+
return true;
46+
}
47+
48+
if (a instanceof Date && b instanceof Date) {
49+
return a.getTime() === b.getTime();
50+
}
51+
52+
if (a instanceof RegExp && b instanceof RegExp) {
53+
return a.source === b.source && a.flags === b.flags;
54+
}
55+
56+
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
57+
// Specific handling for typed arrays if needed, generally strictly referential or length check
58+
// For simple use cases, we treat them as objects below or add specific logic here.
59+
// Falling through to object check for now.
60+
}
61+
62+
const keysA = Object.keys(a as Record<string, unknown>);
63+
const keysB = Object.keys(b as Record<string, unknown>);
64+
65+
if (keysA.length !== keysB.length) return false;
66+
67+
for (const key of keysA) {
68+
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
69+
70+
const valA = (a as Record<string, unknown>)[key];
71+
const valB = (b as Record<string, unknown>)[key];
72+
73+
if (!deepEqual(valA, valB)) return false;
74+
}
75+
76+
return true;
77+
}
78+
79+
export const simpleIsEqual = deepEqual;

0 commit comments

Comments
 (0)