dirk-cfx-react 1.1.92 → 1.1.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/chunk-3T2CCW5E.cjs +150 -0
  2. package/dist/chunk-3T2CCW5E.cjs.map +1 -0
  3. package/dist/chunk-4DE2IREA.cjs +9 -0
  4. package/dist/chunk-4DE2IREA.cjs.map +1 -0
  5. package/dist/chunk-E2SFEBBB.cjs +449 -0
  6. package/dist/chunk-E2SFEBBB.cjs.map +1 -0
  7. package/dist/chunk-GKSP5RIA.js +3 -0
  8. package/dist/chunk-GKSP5RIA.js.map +1 -0
  9. package/dist/chunk-HAB6H274.js +6646 -0
  10. package/dist/chunk-HAB6H274.js.map +1 -0
  11. package/dist/chunk-K3GDU6EY.cjs +6717 -0
  12. package/dist/chunk-K3GDU6EY.cjs.map +1 -0
  13. package/dist/chunk-KGLO3ZAS.js +431 -0
  14. package/dist/chunk-KGLO3ZAS.js.map +1 -0
  15. package/dist/chunk-LRGCBNZ7.cjs +4 -0
  16. package/dist/chunk-LRGCBNZ7.cjs.map +1 -0
  17. package/dist/chunk-MYNNCLMA.js +1375 -0
  18. package/dist/chunk-MYNNCLMA.js.map +1 -0
  19. package/dist/chunk-NVMOGS7Y.js +437 -0
  20. package/dist/chunk-NVMOGS7Y.js.map +1 -0
  21. package/dist/chunk-QOQQ3ERZ.cjs +445 -0
  22. package/dist/chunk-QOQQ3ERZ.cjs.map +1 -0
  23. package/dist/chunk-V6TY7KAL.js +7 -0
  24. package/dist/chunk-V6TY7KAL.js.map +1 -0
  25. package/dist/chunk-VKYONY7E.cjs +112 -0
  26. package/dist/chunk-VKYONY7E.cjs.map +1 -0
  27. package/dist/chunk-YA2PXBP6.cjs +1430 -0
  28. package/dist/chunk-YA2PXBP6.cjs.map +1 -0
  29. package/dist/chunk-Z7N5AQJW.js +146 -0
  30. package/dist/chunk-Z7N5AQJW.js.map +1 -0
  31. package/dist/chunk-ZPFW7C2A.js +108 -0
  32. package/dist/chunk-ZPFW7C2A.js.map +1 -0
  33. package/dist/components/index.cjs +282 -7760
  34. package/dist/components/index.cjs.map +1 -1
  35. package/dist/components/index.d.cts +39 -1
  36. package/dist/components/index.d.ts +39 -1
  37. package/dist/components/index.js +6 -7699
  38. package/dist/components/index.js.map +1 -1
  39. package/dist/hooks/index.cjs +75 -718
  40. package/dist/hooks/index.cjs.map +1 -1
  41. package/dist/hooks/index.d.cts +11 -1
  42. package/dist/hooks/index.d.ts +11 -1
  43. package/dist/hooks/index.js +5 -701
  44. package/dist/hooks/index.js.map +1 -1
  45. package/dist/index.cjs +572 -9066
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.d.cts +3 -3
  48. package/dist/index.d.ts +3 -3
  49. package/dist/index.js +9 -8928
  50. package/dist/index.js.map +1 -1
  51. package/dist/modelNames-QIAIYORH.cjs +53910 -0
  52. package/dist/modelNames-QIAIYORH.cjs.map +1 -0
  53. package/dist/modelNames-WAUYJLL3.js +53908 -0
  54. package/dist/modelNames-WAUYJLL3.js.map +1 -0
  55. package/dist/providers/index.cjs +11 -596
  56. package/dist/providers/index.cjs.map +1 -1
  57. package/dist/providers/index.js +5 -596
  58. package/dist/providers/index.js.map +1 -1
  59. package/dist/utils/index.cjs +186 -1361
  60. package/dist/utils/index.cjs.map +1 -1
  61. package/dist/utils/index.d.cts +41 -1
  62. package/dist/utils/index.d.ts +41 -1
  63. package/dist/utils/index.js +2 -1324
  64. package/dist/utils/index.js.map +1 -1
  65. package/package.json +1 -1
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ var chunkYA2PXBP6_cjs = require('./chunk-YA2PXBP6.cjs');
4
+ var react = require('react');
5
+ var zustand = require('zustand');
6
+
7
+ var useNuiEvent = (action, handler) => {
8
+ const savedHandler = react.useRef(chunkYA2PXBP6_cjs.noop);
9
+ react.useEffect(() => {
10
+ savedHandler.current = handler;
11
+ }, [handler]);
12
+ react.useEffect(() => {
13
+ const eventListener = (event) => {
14
+ const { action: eventAction, data } = event.data;
15
+ if (savedHandler.current) {
16
+ if (eventAction === action) {
17
+ savedHandler.current(data);
18
+ }
19
+ }
20
+ };
21
+ window.addEventListener("message", eventListener);
22
+ return () => window.removeEventListener("message", eventListener);
23
+ }, [action]);
24
+ };
25
+ function isPlainObject(v) {
26
+ return typeof v === "object" && v !== null && !Array.isArray(v);
27
+ }
28
+ function deepMerge(base, patch) {
29
+ if (!isPlainObject(base) || !isPlainObject(patch)) {
30
+ return patch ?? base;
31
+ }
32
+ const out = { ...base };
33
+ for (const key of Object.keys(patch)) {
34
+ const patchVal = patch[key];
35
+ const baseVal = out[key];
36
+ out[key] = isPlainObject(baseVal) && isPlainObject(patchVal) ? deepMerge(baseVal, patchVal) : patchVal;
37
+ }
38
+ return out;
39
+ }
40
+ function changedTopLevelSections(changedFields) {
41
+ if (!changedFields || changedFields.length === 0) return [];
42
+ const sections = /* @__PURE__ */ new Set();
43
+ for (const path of changedFields) {
44
+ if (typeof path !== "string" || path.length === 0) continue;
45
+ const dot = path.indexOf(".");
46
+ sections.add(dot === -1 ? path : path.slice(0, dot));
47
+ }
48
+ return Array.from(sections);
49
+ }
50
+ var _instance = null;
51
+ function getScriptConfigInstance() {
52
+ if (!_instance) throw new Error("[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel");
53
+ return _instance;
54
+ }
55
+ function createScriptConfig(defaultValue) {
56
+ const store = zustand.create(() => defaultValue);
57
+ let clientVersion = 0;
58
+ const useScriptConfigHooks = () => {
59
+ useNuiEvent(
60
+ "UPDATE_SCRIPT_CONFIG",
61
+ (data) => {
62
+ if (!data) return;
63
+ if (typeof data.clientVersion === "number") {
64
+ clientVersion = data.clientVersion;
65
+ }
66
+ if (data.config && typeof data.config === "object") {
67
+ store.setState((prev) => ({ ...prev, ...data.config }));
68
+ }
69
+ }
70
+ );
71
+ };
72
+ const fetchScriptConfig = async () => {
73
+ try {
74
+ const response = await chunkYA2PXBP6_cjs.fetchNui("GET_FULL_SCRIPT_CONFIG");
75
+ if (response?.success && response.data?.config) {
76
+ store.setState(() => response.data.config);
77
+ if (typeof response.data.clientVersion === "number") {
78
+ clientVersion = response.data.clientVersion;
79
+ }
80
+ return response.data.config;
81
+ }
82
+ } catch {
83
+ }
84
+ return null;
85
+ };
86
+ const fetchServerOnlyScriptConfig = async () => {
87
+ try {
88
+ const response = await chunkYA2PXBP6_cjs.fetchNui(
89
+ "GET_SERVER_ONLY_SCRIPT_CONFIG"
90
+ );
91
+ if (response?.success && response.data) {
92
+ if (typeof response.data.clientVersion === "number") {
93
+ clientVersion = response.data.clientVersion;
94
+ }
95
+ const sliver = response.data.serverOnly;
96
+ const merged = deepMerge(store.getState(), sliver ?? {});
97
+ store.setState(() => merged);
98
+ return merged;
99
+ }
100
+ } catch {
101
+ }
102
+ return null;
103
+ };
104
+ const updateScriptConfig = async (newConfig, changedFields) => {
105
+ store.setState((prev) => ({ ...prev, ...newConfig }));
106
+ const sections = changedTopLevelSections(changedFields);
107
+ let payload;
108
+ if (sections.length > 0) {
109
+ const current = store.getState();
110
+ const delta = {};
111
+ for (const key of sections) delta[key] = current[key];
112
+ payload = { data: delta, expectedVersion: clientVersion, sectionReplace: true };
113
+ } else {
114
+ payload = { data: newConfig, expectedVersion: clientVersion };
115
+ }
116
+ const response = await chunkYA2PXBP6_cjs.fetchNui("UPDATE_SCRIPT_CONFIG", payload);
117
+ if (response?.meta?.client_version != null) {
118
+ clientVersion = response.meta.client_version;
119
+ }
120
+ if (response?.success === false && response?.meta?.latestData) {
121
+ store.setState((prev) => ({ ...prev, ...response.meta.latestData }));
122
+ }
123
+ return response;
124
+ };
125
+ const getScriptConfigHistory = async (params = {}) => {
126
+ return chunkYA2PXBP6_cjs.fetchNui("GET_SCRIPT_CONFIG_HISTORY", params);
127
+ };
128
+ const resetConfig = async () => {
129
+ const response = await chunkYA2PXBP6_cjs.fetchNui("RESET_SCRIPT_CONFIG");
130
+ if (response?.success) {
131
+ await fetchServerOnlyScriptConfig();
132
+ }
133
+ return response;
134
+ };
135
+ _instance = {
136
+ store,
137
+ updateConfig: updateScriptConfig,
138
+ resetConfig,
139
+ getHistory: getScriptConfigHistory,
140
+ fetchConfig: fetchScriptConfig,
141
+ fetchServerOnly: fetchServerOnlyScriptConfig
142
+ };
143
+ return { store, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig, fetchServerOnlyScriptConfig };
144
+ }
145
+
146
+ exports.createScriptConfig = createScriptConfig;
147
+ exports.getScriptConfigInstance = getScriptConfigInstance;
148
+ exports.useNuiEvent = useNuiEvent;
149
+ //# sourceMappingURL=chunk-3T2CCW5E.cjs.map
150
+ //# sourceMappingURL=chunk-3T2CCW5E.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useNuiEvent.ts","../src/hooks/useScriptConfig.ts"],"names":["useRef","noop","useEffect","create","fetchNui"],"mappings":"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","file":"chunk-3T2CCW5E.cjs","sourcesContent":["import { MutableRefObject, useEffect, useRef } from \"react\";\r\nimport { noop } from \"../utils/misc\";\r\n\r\nexport interface NuiMessageData<T = unknown> {\r\n action: string;\r\n data: T;\r\n}\r\n\r\nexport type NuiHandlerSignature<T> = ( data: T) => void;\r\n\r\n/**\r\n * A hook that manage events listeners for receiving data from the client scripts\r\n * @param action The specific `action` that should be listened for.\r\n * @param handler The callback function that will handle data relayed by this hook\r\n *\r\n * @example\r\n * useNuiEvent<{visibility: true, wasVisible: 'something'}>('setVisible', (data) => {\r\n * // whatever logic you want\r\n * })\r\n *\r\n **/\r\n\r\nexport const useNuiEvent = <T = unknown>(\r\n action: string,\r\n handler: ( data: T) => void,\r\n) => {\r\n const savedHandler: MutableRefObject<NuiHandlerSignature<T>> = useRef(noop);\r\n\r\n // Make sure we handle for a reactive handler\r\n useEffect(() => {\r\n savedHandler.current = handler;\r\n }, [handler]);\r\n\r\n useEffect(() => {\r\n const eventListener = (event: MessageEvent<NuiMessageData<T>>) => {\r\n const { action: eventAction, data } = event.data;\r\n\r\n if (savedHandler.current) {\r\n if (eventAction === action) {\r\n savedHandler.current( data);\r\n }\r\n }\r\n };\r\n\r\n window.addEventListener(\"message\", eventListener);\r\n // Remove Event Listener on component dirkup\r\n return () => window.removeEventListener(\"message\", eventListener);\r\n }, [action]);\r\n};\r\n","import { fetchNui } from \"@/utils\";\r\nimport { useNuiEvent } from \"@/hooks/useNuiEvent\";\r\nimport { create } from \"zustand\";\r\n\r\ntype ScriptConfigUpdateMeta<T> = {\r\n client_version?: number;\r\n latestVersion?: number;\r\n latestData?: Partial<T>;\r\n changed_paths?: Array<{ path: string; old: unknown; new: unknown }>;\r\n lastEditor?: { source?: number; name?: string; identifier?: string };\r\n};\r\n\r\nexport type ScriptConfigHistoryChange = {\r\n path: string;\r\n old: unknown;\r\n new: unknown;\r\n};\r\n\r\nexport type ScriptConfigHistoryEntry = {\r\n at_unix: number;\r\n at_utc: string;\r\n script: string;\r\n admin?: { source?: number; name?: string; identifier?: string };\r\n expected_version?: number;\r\n applied_version?: number;\r\n changes: ScriptConfigHistoryChange[];\r\n};\r\n\r\nexport type ScriptConfigHistoryRequest = {\r\n offset?: number;\r\n limit?: number;\r\n query?: string;\r\n path?: string;\r\n admin?: string;\r\n fromUnix?: number;\r\n toUnix?: number;\r\n};\r\n\r\nexport type ScriptConfigHistoryResponse = {\r\n success: boolean;\r\n _error?: string;\r\n data?: {\r\n items: ScriptConfigHistoryEntry[];\r\n total: number;\r\n limit: number;\r\n offset: number;\r\n nextOffset?: number;\r\n };\r\n};\r\n\r\ntype NuiResponse<T> = {\r\n success: boolean;\r\n message?: string;\r\n _error?: string;\r\n meta?: ScriptConfigUpdateMeta<T>;\r\n};\r\n\r\n// Server-only \"sliver\" response (GET_SERVER_ONLY_SCRIPT_CONFIG). Identical\r\n// envelope to GET_FULL_SCRIPT_CONFIG — only the inner key differs (`serverOnly`\r\n// instead of `config`). `serverOnly` is the x-serverOnly subtree in the SAME\r\n// nested shape/paths as `config` (a deep object, NOT flat dot-paths); `{}` when\r\n// the schema declares no server-only fields.\r\nexport type ServerOnlyScriptConfigResponse<T> = {\r\n success: boolean;\r\n _error?: string;\r\n data?: { serverOnly: Partial<T>; clientVersion: number };\r\n};\r\n\r\n// Recursive deep-merge: returns a new object where `patch`'s plain-object\r\n// branches are merged INTO `base`'s, and non-object values (scalars, arrays)\r\n// from `patch` replace `base`. Used to top up the server-only sliver onto the\r\n// already-cached client-visible config WITHOUT the top-level spread-replace\r\n// `fetchScriptConfig` uses — a shallow spread would clobber sibling\r\n// client-visible keys that live under a shared parent section. By construction\r\n// the two views are disjoint (serverOnly is the set-complement of the\r\n// client-visible view), but we still deep-merge so a shared parent section keeps\r\n// both its client-visible and server-only children.\r\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\r\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\r\n}\r\n\r\nfunction deepMerge<T>(base: T, patch: Partial<T>): T {\r\n if (!isPlainObject(base) || !isPlainObject(patch)) {\r\n // patch wins for non-object values (arrays/scalars are replaced wholesale).\r\n return (patch as unknown as T) ?? base;\r\n }\r\n const out: Record<string, unknown> = { ...(base as Record<string, unknown>) };\r\n for (const key of Object.keys(patch as Record<string, unknown>)) {\r\n const patchVal = (patch as Record<string, unknown>)[key];\r\n const baseVal = out[key];\r\n out[key] = isPlainObject(baseVal) && isPlainObject(patchVal)\r\n ? deepMerge(baseVal, patchVal as Record<string, unknown>)\r\n : patchVal;\r\n }\r\n return out as T;\r\n}\r\n\r\n// Derive the set of changed TOP-LEVEL section keys from a useForm changed-set\r\n// of dotted paths (e.g. [\"stores.0.name\", \"basic.weightUnit\"] → {\"stores\",\"basic\"}).\r\n// CONSERVATIVE by design: any changed path under section X marks the WHOLE\r\n// section X as changed, so the caller sends its full current value. This is what\r\n// lets deletions inside a section (a removed array element) propagate, because\r\n// the server/client overwrite the section wholesale rather than deep-merging.\r\nfunction changedTopLevelSections(changedFields?: readonly string[]): string[] {\r\n if (!changedFields || changedFields.length === 0) return [];\r\n const sections = new Set<string>();\r\n for (const path of changedFields) {\r\n if (typeof path !== \"string\" || path.length === 0) continue;\r\n const dot = path.indexOf(\".\");\r\n sections.add(dot === -1 ? path : path.slice(0, dot));\r\n }\r\n return Array.from(sections);\r\n}\r\n\r\n// ── Singleton registry ────────────────────────────────────────────────────────\r\n\r\nexport interface ScriptConfigInstance<T = any> {\r\n store: { getState: () => T; setState: (partial: Partial<T> | ((prev: T) => T)) => void };\r\n updateConfig: (newConfig: Partial<T>, changedFields?: readonly string[]) => Promise<NuiResponse<T>>;\r\n resetConfig: () => Promise<{ success: boolean; _error?: string }>;\r\n getHistory: (params?: ScriptConfigHistoryRequest) => Promise<ScriptConfigHistoryResponse>;\r\n fetchConfig: () => Promise<T | null>;\r\n // Admin-only top-up: fetches the server-only sliver and DEEP-MERGES it onto\r\n // the already-cached client-visible store state. Returns the merged full\r\n // config, or null on failure / no-permission. The sliver is in-memory only —\r\n // it is NEVER persisted to KVP or any cache (KVP holds client-visible only).\r\n fetchServerOnly: () => Promise<T | null>;\r\n}\r\n\r\nlet _instance: ScriptConfigInstance | null = null;\r\n\r\nexport function getScriptConfigInstance<T = any>(): ScriptConfigInstance<T> {\r\n if (!_instance) throw new Error(\"[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel\");\r\n return _instance as ScriptConfigInstance<T>;\r\n}\r\n\r\nexport function createScriptConfig<T>(defaultValue: T) {\r\n const store = create<T>(() => defaultValue);\r\n let clientVersion = 0;\r\n\r\n const useScriptConfigHooks = () => {\r\n useNuiEvent<{ config?: Partial<T>; clientVersion?: number; sectionReplace?: boolean }>(\r\n \"UPDATE_SCRIPT_CONFIG\",\r\n (data) => {\r\n if (!data) return;\r\n\r\n if (typeof data.clientVersion === \"number\") {\r\n clientVersion = data.clientVersion as number;\r\n }\r\n\r\n if (data.config && typeof data.config === \"object\") {\r\n // Both apply modes use the same top-level merge-by-replace:\r\n // - sectionReplace: `config` holds only the changed sections; spreading\r\n // them over `prev` wholesale-overwrites those keys and leaves every\r\n // other (unsent) section exactly as-is. We MUST NOT full-replace here\r\n // or the unsent sections would vanish from the UI.\r\n // - fullReplace / merge / initial: `config` holds the FULL config, so\r\n // the same spread effectively replaces every key.\r\n store.setState((prev) => ({ ...prev, ...(data.config as Partial<T>) }));\r\n }\r\n }\r\n );\r\n };\r\n\r\n const fetchScriptConfig = async (): Promise<T | null> => {\r\n try {\r\n const response = await fetchNui<{\r\n success: boolean;\r\n data?: { config: T; clientVersion: number };\r\n }>(\"GET_FULL_SCRIPT_CONFIG\");\r\n\r\n if (response?.success && response.data?.config) {\r\n store.setState(() => response.data!.config as T);\r\n if (typeof response.data.clientVersion === \"number\") {\r\n clientVersion = response.data.clientVersion;\r\n }\r\n return response.data.config;\r\n }\r\n } catch { /* fallback to current store state */ }\r\n return null;\r\n };\r\n\r\n // Option B top-up. Called ONLY by the admin editor on panel open. The\r\n // baseline (client-visible config) is already in the store — pushed by Lua\r\n // via UPDATE_SCRIPT_CONFIG (handled in useScriptConfigHooks above) and cached\r\n // in KVP on the Lua side. Here we fetch ONLY the server-only sliver\r\n // (permission-gated server-side on canEditScript) and DEEP-MERGE it onto that\r\n // baseline to form the full editor view. We deliberately do NOT spread-replace\r\n // (as fetchScriptConfig does) — that would clobber sibling client-visible keys\r\n // under a shared parent section.\r\n //\r\n // SECURITY: this sliver is admin-only and in-memory only. It is NEVER written\r\n // to KVP or any cache; it is re-fetched fresh on every panel open.\r\n const fetchServerOnlyScriptConfig = async (): Promise<T | null> => {\r\n try {\r\n const response = await fetchNui<ServerOnlyScriptConfigResponse<T>>(\r\n \"GET_SERVER_ONLY_SCRIPT_CONFIG\"\r\n );\r\n\r\n if (response?.success && response.data) {\r\n // clientVersion here matches the value already in the store from the\r\n // Lua push; keep it in sync (safe no-op if identical).\r\n if (typeof response.data.clientVersion === \"number\") {\r\n clientVersion = response.data.clientVersion;\r\n }\r\n const sliver = response.data.serverOnly;\r\n // An empty sliver ({} — schema declares no server-only paths) is a valid\r\n // no-op merge: deepMerge returns the baseline unchanged.\r\n const merged = deepMerge(store.getState() as T, (sliver ?? {}) as Partial<T>);\r\n store.setState(() => merged as T);\r\n return merged;\r\n }\r\n } catch { /* fall back to the client-visible baseline already in the store */ }\r\n return null;\r\n };\r\n\r\n // `changedFields` is the useForm tracked changed-set (dotted paths). When\r\n // supplied AND it resolves to ≥1 top-level section, we send a SECTION-DELTA:\r\n // only the changed sections, each as its WHOLE current value, with\r\n // sectionReplace:true. The server/client then overwrite those keys wholesale\r\n // (which is what lets deletions inside a section propagate). When omitted or\r\n // empty, we fall back to the legacy full-send (server defaults to\r\n // fullReplace=true when no `sectionReplace` flag is present).\r\n const updateScriptConfig = async (\r\n newConfig: Partial<T>,\r\n changedFields?: readonly string[]\r\n ): Promise<NuiResponse<T>> => {\r\n store.setState((prev) => ({ ...prev, ...newConfig }));\r\n\r\n const sections = changedTopLevelSections(changedFields);\r\n\r\n let payload: { data: Partial<T>; expectedVersion: number; sectionReplace?: boolean };\r\n if (sections.length > 0) {\r\n // Section-delta: flat object keyed by top-level section name → whole\r\n // current section value (pulled from the just-applied store state so it\r\n // reflects the user's edits, including deletions).\r\n const current = store.getState() as Record<string, unknown>;\r\n const delta: Record<string, unknown> = {};\r\n for (const key of sections) delta[key] = current[key];\r\n payload = { data: delta as Partial<T>, expectedVersion: clientVersion, sectionReplace: true };\r\n } else {\r\n // No changed-set provided (or nothing resolved) but the user explicitly\r\n // saved → legacy full-send. No sectionReplace flag ⇒ server fullReplace.\r\n payload = { data: newConfig, expectedVersion: clientVersion };\r\n }\r\n\r\n const response = await fetchNui<NuiResponse<T>>(\"UPDATE_SCRIPT_CONFIG\", payload);\r\n\r\n if (response?.meta?.client_version != null) {\r\n clientVersion = response.meta.client_version as number;\r\n }\r\n\r\n if (response?.success === false && response?.meta?.latestData) {\r\n store.setState((prev) => ({ ...prev, ...(response.meta!.latestData as Partial<T>) }));\r\n }\r\n\r\n return response;\r\n };\r\n\r\n const getScriptConfigHistory = async (\r\n params: ScriptConfigHistoryRequest = {}\r\n ): Promise<ScriptConfigHistoryResponse> => {\r\n return fetchNui<ScriptConfigHistoryResponse>('GET_SCRIPT_CONFIG_HISTORY', params);\r\n };\r\n\r\n const resetConfig = async (): Promise<{ success: boolean; _error?: string }> => {\r\n const response = await fetchNui<{ success: boolean; _error?: string }>('RESET_SCRIPT_CONFIG');\r\n if (response?.success) {\r\n // Option B: after a reset the server restores defaults and broadcasts a\r\n // fresh client-visible UPDATE_SCRIPT_CONFIG push (handled in\r\n // useScriptConfigHooks → store baseline). We only need to re-merge the\r\n // server-only sliver onto that refreshed baseline to rebuild the full\r\n // editor view — no GET_FULL_SCRIPT_CONFIG round-trip. fetchServerOnly\r\n // merges onto whatever client-visible baseline is currently in the store,\r\n // so it's safe regardless of broadcast/callback ordering.\r\n await fetchServerOnlyScriptConfig();\r\n }\r\n return response;\r\n };\r\n\r\n _instance = {\r\n store,\r\n updateConfig: updateScriptConfig,\r\n resetConfig,\r\n getHistory: getScriptConfigHistory,\r\n fetchConfig: fetchScriptConfig,\r\n fetchServerOnly: fetchServerOnlyScriptConfig,\r\n };\r\n\r\n return {store, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig, fetchServerOnlyScriptConfig}\r\n}\r\n"]}
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+
7
+ exports.__publicField = __publicField;
8
+ //# sourceMappingURL=chunk-4DE2IREA.cjs.map
9
+ //# sourceMappingURL=chunk-4DE2IREA.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-4DE2IREA.cjs"}
@@ -0,0 +1,449 @@
1
+ 'use strict';
2
+
3
+ var chunkYA2PXBP6_cjs = require('./chunk-YA2PXBP6.cjs');
4
+ var zustand = require('zustand');
5
+ var clickSoundUrl = require('./click_sound-PNCRRTM4.mp3');
6
+ var hoverSoundUrl = require('./hover_sound-NBUA222C.mp3');
7
+ var react = require('react');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+ var reactQuery = require('@tanstack/react-query');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var clickSoundUrl__default = /*#__PURE__*/_interopDefault(clickSoundUrl);
14
+ var hoverSoundUrl__default = /*#__PURE__*/_interopDefault(hoverSoundUrl);
15
+
16
+ var useAudio = zustand.create(() => {
17
+ const audioRefs = {};
18
+ const sounds = {
19
+ click: clickSoundUrl__default.default,
20
+ hover: hoverSoundUrl__default.default
21
+ };
22
+ for (const [key, src] of Object.entries(sounds)) {
23
+ audioRefs[key] = new Audio(src);
24
+ }
25
+ return {
26
+ play: (sound) => {
27
+ const audio = audioRefs[sound];
28
+ if (!audio) return console.warn(`Sound '${sound}' not found.`);
29
+ audio.currentTime = 0;
30
+ audio.volume = 0.1;
31
+ audio.play();
32
+ },
33
+ stop: (sound) => {
34
+ const audio = audioRefs[sound];
35
+ if (!audio) return console.warn(`Sound '${sound}' not found.`);
36
+ audio.pause();
37
+ audio.currentTime = 0;
38
+ }
39
+ };
40
+ });
41
+ function getNested(obj, path) {
42
+ return path.split(".").reduce((acc, key) => acc ? acc[key] : void 0, obj);
43
+ }
44
+ function setNested(obj, path, value) {
45
+ const keys = path.split(".");
46
+ const root = Array.isArray(obj) ? [...obj] : { ...obj };
47
+ let current = root;
48
+ for (let i = 0; i < keys.length - 1; i++) {
49
+ const key = keys[i];
50
+ const nextKey = keys[i + 1];
51
+ const isIndex = !isNaN(Number(nextKey));
52
+ const existing = current[key];
53
+ current[key] = existing != null ? Array.isArray(existing) ? [...existing] : { ...existing } : isIndex ? [] : {};
54
+ current = current[key];
55
+ }
56
+ current[keys[keys.length - 1]] = value;
57
+ return root;
58
+ }
59
+ function deleteNested(obj, path) {
60
+ const keys = path.split(".");
61
+ const newObj = { ...obj };
62
+ let current = newObj;
63
+ for (let i = 0; i < keys.length - 1; i++) {
64
+ const key = keys[i];
65
+ if (!current[key]) return obj;
66
+ current[key] = { ...current[key] };
67
+ current = current[key];
68
+ }
69
+ delete current[keys[keys.length - 1]];
70
+ return newObj;
71
+ }
72
+ function isPlainObject(value) {
73
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
74
+ }
75
+ function collectChangedPaths(values, initial, prefix = "") {
76
+ if (Object.is(values, initial)) return [];
77
+ const valuesIsObj = isPlainObject(values);
78
+ const initialIsObj = isPlainObject(initial);
79
+ const valuesIsArr = Array.isArray(values);
80
+ const initialIsArr = Array.isArray(initial);
81
+ if (valuesIsArr || initialIsArr) {
82
+ const maxLen = Math.max(values?.length ?? 0, initial?.length ?? 0);
83
+ const fields = [];
84
+ for (let i = 0; i < maxLen; i++) {
85
+ const nextPrefix = prefix ? `${prefix}.${i}` : `${i}`;
86
+ fields.push(...collectChangedPaths(values?.[i], initial?.[i], nextPrefix));
87
+ }
88
+ return fields;
89
+ }
90
+ if (valuesIsObj || initialIsObj) {
91
+ const keys = /* @__PURE__ */ new Set([
92
+ ...Object.keys(values ?? {}),
93
+ ...Object.keys(initial ?? {})
94
+ ]);
95
+ const fields = [];
96
+ for (const key of keys) {
97
+ const nextPrefix = prefix ? `${prefix}.${key}` : key;
98
+ fields.push(...collectChangedPaths(values?.[key], initial?.[key], nextPrefix));
99
+ }
100
+ return fields;
101
+ }
102
+ return prefix ? [prefix] : [];
103
+ }
104
+ function computeChangedState(values, initialVals) {
105
+ const fields = collectChangedPaths(values, initialVals);
106
+ let partial = {};
107
+ for (const path of fields) {
108
+ partial = setNested(partial, path, getNested(values, path));
109
+ }
110
+ return { fields, partial };
111
+ }
112
+ function flattenRules(rules, prefix = "") {
113
+ const result = {};
114
+ for (const key in rules) {
115
+ const fullPath = prefix ? `${prefix}.${key}` : key;
116
+ const val = rules[key];
117
+ if (typeof val === "function") result[fullPath] = val;
118
+ else if (typeof val === "object")
119
+ Object.assign(result, flattenRules(val, fullPath));
120
+ }
121
+ return result;
122
+ }
123
+ async function runRule(rule, value, values) {
124
+ const result = rule(value, values);
125
+ return result instanceof Promise ? await result : result;
126
+ }
127
+ function createFormStore(initialValues, validationRules, onSubmit) {
128
+ const flatRules = validationRules ? flattenRules(validationRules) : {};
129
+ const history = [];
130
+ const future = [];
131
+ const changed = /* @__PURE__ */ new Set();
132
+ return zustand.createStore((set, get) => ({
133
+ initialValues,
134
+ values: initialValues,
135
+ errors: {},
136
+ partialChanged: {},
137
+ canBack: false,
138
+ canForward: false,
139
+ changedFields: [],
140
+ changedCount: 0,
141
+ onSubmit,
142
+ submit: async () => {
143
+ const state = get();
144
+ const isValid = await state.validate();
145
+ if (isValid && state.onSubmit) {
146
+ state.onSubmit(get());
147
+ }
148
+ },
149
+ getInputProps: (path) => {
150
+ return {
151
+ value: getNested(get().values, path) ?? "",
152
+ error: get().errors[path],
153
+ onChange: (e) => {
154
+ get().setValue(path, e.target.value, { validate: true });
155
+ }
156
+ };
157
+ },
158
+ resetChangeCount: () => {
159
+ changed.clear();
160
+ set({ changedFields: [], changedCount: 0, partialChanged: {} });
161
+ },
162
+ setInitialValues: (newInitialValues) => set({ initialValues: newInitialValues }),
163
+ setValue: (path, value, options) => {
164
+ const state = get();
165
+ const currentValues = state.values;
166
+ const newValues = setNested(currentValues, path, value);
167
+ const oldValue = getNested(state.initialValues, path);
168
+ const hasChanged = value !== oldValue;
169
+ history.push(currentValues);
170
+ future.length = 0;
171
+ let newPartial = state.partialChanged;
172
+ if (hasChanged) {
173
+ changed.add(path);
174
+ newPartial = setNested(newPartial, path, value);
175
+ } else {
176
+ changed.delete(path);
177
+ newPartial = deleteNested(newPartial, path);
178
+ }
179
+ set({
180
+ values: newValues,
181
+ partialChanged: newPartial,
182
+ canBack: history.length > 0,
183
+ canForward: false,
184
+ changedFields: Array.from(changed),
185
+ changedCount: changed.size
186
+ });
187
+ if (!options?.validate) return;
188
+ const rule = flatRules[path];
189
+ if (!rule) return;
190
+ Promise.resolve(runRule(rule, value, newValues)).then((error) => {
191
+ if (error)
192
+ set((s) => ({ errors: setNested(s.errors, path, error) }));
193
+ else
194
+ set((s) => ({ errors: deleteNested(s.errors, path) }));
195
+ });
196
+ },
197
+ setError: (path, message) => set((s) => ({ errors: setNested(s.errors, path, message) })),
198
+ clearError: (path) => set((s) => ({ errors: deleteNested(s.errors, path) })),
199
+ validateField: async (path) => {
200
+ const state = get();
201
+ const rule = flatRules[path];
202
+ if (!rule) return true;
203
+ const value = getNested(state.values, path);
204
+ const error = await runRule(rule, value, state.values);
205
+ if (error) {
206
+ set((s) => ({ errors: setNested(s.errors, path, error) }));
207
+ return false;
208
+ }
209
+ set((s) => ({ errors: deleteNested(s.errors, path) }));
210
+ return true;
211
+ },
212
+ validate: async () => {
213
+ const state = get();
214
+ let isValid = true;
215
+ let newErrors = {};
216
+ for (const path in flatRules) {
217
+ const rule = flatRules[path];
218
+ const value = getNested(state.values, path);
219
+ const error = await runRule(rule, value, state.values);
220
+ if (error) {
221
+ isValid = false;
222
+ newErrors = setNested(newErrors, path, error);
223
+ }
224
+ }
225
+ set({ errors: newErrors });
226
+ return isValid;
227
+ },
228
+ reset: () => {
229
+ history.length = 0;
230
+ future.length = 0;
231
+ changed.clear();
232
+ set({
233
+ values: get().initialValues,
234
+ errors: {},
235
+ partialChanged: {},
236
+ canBack: false,
237
+ canForward: false,
238
+ changedFields: [],
239
+ changedCount: 0
240
+ });
241
+ },
242
+ reinitialize: (newValues) => {
243
+ history.length = 0;
244
+ future.length = 0;
245
+ changed.clear();
246
+ set({
247
+ values: newValues,
248
+ initialValues: newValues,
249
+ errors: {},
250
+ partialChanged: {},
251
+ canBack: false,
252
+ canForward: false,
253
+ changedFields: [],
254
+ changedCount: 0
255
+ });
256
+ },
257
+ back: () => {
258
+ if (!history.length) return;
259
+ const prev = history.pop();
260
+ future.push(get().values);
261
+ const { fields, partial } = computeChangedState(prev, get().initialValues);
262
+ set({
263
+ values: prev,
264
+ partialChanged: partial,
265
+ canBack: history.length > 0,
266
+ canForward: true,
267
+ changedFields: fields,
268
+ changedCount: fields.length
269
+ });
270
+ },
271
+ forward: () => {
272
+ if (!future.length) return;
273
+ const next = future.pop();
274
+ history.push(get().values);
275
+ const { fields, partial } = computeChangedState(next, get().initialValues);
276
+ set({
277
+ values: next,
278
+ partialChanged: partial,
279
+ canBack: true,
280
+ canForward: future.length > 0,
281
+ changedFields: fields,
282
+ changedCount: fields.length
283
+ });
284
+ }
285
+ }));
286
+ }
287
+ var FormContext = react.createContext(null);
288
+ function FormProvider({
289
+ initialValues,
290
+ validate,
291
+ onSubmit,
292
+ children
293
+ }) {
294
+ const storeRef = react.useRef(
295
+ createFormStore(initialValues, validate, onSubmit)
296
+ );
297
+ return /* @__PURE__ */ jsxRuntime.jsx(FormContext.Provider, { value: storeRef.current, children });
298
+ }
299
+ function useForm() {
300
+ const store2 = react.useContext(FormContext);
301
+ if (!store2) {
302
+ throw new Error("useForm must be used inside <FormProvider>");
303
+ }
304
+ const state = zustand.useStore(store2);
305
+ const changedFields = react.useMemo(() => {
306
+ return collectChangedPaths(state.values, state.initialValues);
307
+ }, [state.values, state.initialValues]);
308
+ return { ...state, changedFields, changedCount: changedFields.length };
309
+ }
310
+ function useFormField(path) {
311
+ const store2 = react.useContext(FormContext);
312
+ if (!store2) {
313
+ throw new Error("useFormField must be used inside <FormProvider>");
314
+ }
315
+ return zustand.useStore(store2, (s) => getNested(s.values, path));
316
+ }
317
+ function useFormFields(...paths) {
318
+ const store2 = react.useContext(FormContext);
319
+ if (!store2) {
320
+ throw new Error("useFormFields must be used inside <FormProvider>");
321
+ }
322
+ return zustand.useStore(store2, (s) => {
323
+ const result = {};
324
+ for (const path of paths) {
325
+ result[path] = getNested(s.values, path);
326
+ }
327
+ return result;
328
+ });
329
+ }
330
+ function useFormError(path) {
331
+ const store2 = react.useContext(FormContext);
332
+ if (!store2) {
333
+ throw new Error("useFormError must be used inside <FormProvider>");
334
+ }
335
+ return zustand.useStore(store2, (s) => s.errors[path]);
336
+ }
337
+ function useFormErrors(...paths) {
338
+ const store2 = react.useContext(FormContext);
339
+ if (!store2) {
340
+ throw new Error("useFormErrors must be used inside <FormProvider>");
341
+ }
342
+ return zustand.useStore(store2, (s) => {
343
+ const result = {};
344
+ for (const path of paths) {
345
+ result[path] = s.errors[path];
346
+ }
347
+ return result;
348
+ });
349
+ }
350
+ function useFormActions() {
351
+ const store2 = react.useContext(FormContext);
352
+ if (!store2) {
353
+ throw new Error("useFormActions must be used inside <FormProvider>");
354
+ }
355
+ return store2.getState();
356
+ }
357
+ var store = /* @__PURE__ */ new Map();
358
+ var listeners = /* @__PURE__ */ new Map();
359
+ function notify(key) {
360
+ const ls = listeners.get(key);
361
+ if (!ls) return;
362
+ ls.forEach((fn) => fn());
363
+ }
364
+ function useAdminState(key, initial) {
365
+ const [, setTick] = react.useState(0);
366
+ react.useEffect(() => {
367
+ const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
368
+ listeners.set(key, set);
369
+ const handler = () => setTick((t) => t + 1);
370
+ set.add(handler);
371
+ return () => {
372
+ set.delete(handler);
373
+ if (set.size === 0) listeners.delete(key);
374
+ };
375
+ }, [key]);
376
+ const value = store.has(key) ? store.get(key) : initial;
377
+ const setValue = (v) => {
378
+ const next = typeof v === "function" ? v(value) : v;
379
+ store.set(key, next);
380
+ notify(key);
381
+ };
382
+ return [value, setValue];
383
+ }
384
+ function clearAdminState(key) {
385
+ if (key) {
386
+ store.delete(key);
387
+ notify(key);
388
+ } else {
389
+ const keys = Array.from(store.keys());
390
+ store.clear();
391
+ keys.forEach(notify);
392
+ }
393
+ }
394
+ function usePlayers(opts = {}) {
395
+ const {
396
+ includeOffline = false,
397
+ search = "",
398
+ limit = 50,
399
+ staleTimeMs,
400
+ refetchIntervalMs
401
+ } = opts;
402
+ const query = reactQuery.useQuery({
403
+ queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
404
+ queryFn: async () => {
405
+ const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
406
+ const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
407
+ const result = await chunkYA2PXBP6_cjs.fetchNui(
408
+ "ADMIN_TOOL_QUERY",
409
+ payload,
410
+ // Browser-dev fallback. Returns a couple of mock players so the
411
+ // dev shell isn't blank.
412
+ includeOffline ? [
413
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
414
+ { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
415
+ ] : [
416
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
417
+ ]
418
+ );
419
+ return Array.isArray(result) ? result : [];
420
+ },
421
+ staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
422
+ gcTime: 5 * 6e4,
423
+ refetchInterval: refetchIntervalMs ?? false,
424
+ refetchOnWindowFocus: false,
425
+ placeholderData: includeOffline ? reactQuery.keepPreviousData : void 0
426
+ });
427
+ return {
428
+ players: query.data ?? [],
429
+ isLoading: query.isLoading,
430
+ isFetching: query.isFetching,
431
+ error: query.error ?? null,
432
+ refresh: () => query.refetch()
433
+ };
434
+ }
435
+
436
+ exports.FormProvider = FormProvider;
437
+ exports.clearAdminState = clearAdminState;
438
+ exports.createFormStore = createFormStore;
439
+ exports.useAdminState = useAdminState;
440
+ exports.useAudio = useAudio;
441
+ exports.useForm = useForm;
442
+ exports.useFormActions = useFormActions;
443
+ exports.useFormError = useFormError;
444
+ exports.useFormErrors = useFormErrors;
445
+ exports.useFormField = useFormField;
446
+ exports.useFormFields = useFormFields;
447
+ exports.usePlayers = usePlayers;
448
+ //# sourceMappingURL=chunk-E2SFEBBB.cjs.map
449
+ //# sourceMappingURL=chunk-E2SFEBBB.cjs.map