dirk-cfx-react 1.1.95 → 1.1.97

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 (43) hide show
  1. package/dist/{chunk-E2SFEBBB.cjs → chunk-7EICODPM.cjs} +4 -4
  2. package/dist/{chunk-E2SFEBBB.cjs.map → chunk-7EICODPM.cjs.map} +1 -1
  3. package/dist/{chunk-MYNNCLMA.js → chunk-BXP4HEYF.js} +334 -43
  4. package/dist/chunk-BXP4HEYF.js.map +1 -0
  5. package/dist/{chunk-KGLO3ZAS.js → chunk-F6EV4VBH.js} +3 -3
  6. package/dist/{chunk-KGLO3ZAS.js.map → chunk-F6EV4VBH.js.map} +1 -1
  7. package/dist/{chunk-K3GDU6EY.cjs → chunk-I4YJBITG.cjs} +298 -256
  8. package/dist/chunk-I4YJBITG.cjs.map +1 -0
  9. package/dist/{chunk-VKYONY7E.cjs → chunk-J5VNINGK.cjs} +5 -5
  10. package/dist/{chunk-VKYONY7E.cjs.map → chunk-J5VNINGK.cjs.map} +1 -1
  11. package/dist/{chunk-YA2PXBP6.cjs → chunk-JIBWKY7H.cjs} +334 -43
  12. package/dist/chunk-JIBWKY7H.cjs.map +1 -0
  13. package/dist/{chunk-ZPFW7C2A.js → chunk-JQHQYPEL.js} +3 -3
  14. package/dist/{chunk-ZPFW7C2A.js.map → chunk-JQHQYPEL.js.map} +1 -1
  15. package/dist/{chunk-3T2CCW5E.cjs → chunk-K64EX3U2.cjs} +9 -9
  16. package/dist/{chunk-3T2CCW5E.cjs.map → chunk-K64EX3U2.cjs.map} +1 -1
  17. package/dist/{chunk-HAB6H274.js → chunk-M5NZAOYQ.js} +145 -103
  18. package/dist/chunk-M5NZAOYQ.js.map +1 -0
  19. package/dist/{chunk-Z7N5AQJW.js → chunk-M7H4JKEL.js} +3 -3
  20. package/dist/{chunk-Z7N5AQJW.js.map → chunk-M7H4JKEL.js.map} +1 -1
  21. package/dist/{chunk-QOQQ3ERZ.cjs → chunk-NBLBFWZQ.cjs} +18 -17
  22. package/dist/chunk-NBLBFWZQ.cjs.map +1 -0
  23. package/dist/{chunk-NVMOGS7Y.js → chunk-Y25C6YMN.js} +5 -5
  24. package/dist/chunk-Y25C6YMN.js.map +1 -0
  25. package/dist/components/index.cjs +76 -76
  26. package/dist/components/index.d.cts +1 -5
  27. package/dist/components/index.d.ts +1 -5
  28. package/dist/components/index.js +5 -5
  29. package/dist/hooks/index.cjs +22 -22
  30. package/dist/hooks/index.js +4 -4
  31. package/dist/index.cjs +147 -147
  32. package/dist/index.js +6 -6
  33. package/dist/providers/index.cjs +5 -5
  34. package/dist/providers/index.js +3 -3
  35. package/dist/utils/index.cjs +48 -48
  36. package/dist/utils/index.js +1 -1
  37. package/package.json +123 -123
  38. package/dist/chunk-HAB6H274.js.map +0 -1
  39. package/dist/chunk-K3GDU6EY.cjs.map +0 -1
  40. package/dist/chunk-MYNNCLMA.js.map +0 -1
  41. package/dist/chunk-NVMOGS7Y.js.map +0 -1
  42. package/dist/chunk-QOQQ3ERZ.cjs.map +0 -1
  43. package/dist/chunk-YA2PXBP6.cjs.map +0 -1
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var chunkYA2PXBP6_cjs = require('./chunk-YA2PXBP6.cjs');
3
+ var chunkJIBWKY7H_cjs = require('./chunk-JIBWKY7H.cjs');
4
4
  var react = require('react');
5
5
  var zustand = require('zustand');
6
6
 
7
7
  var useNuiEvent = (action, handler) => {
8
- const savedHandler = react.useRef(chunkYA2PXBP6_cjs.noop);
8
+ const savedHandler = react.useRef(chunkJIBWKY7H_cjs.noop);
9
9
  react.useEffect(() => {
10
10
  savedHandler.current = handler;
11
11
  }, [handler]);
@@ -71,7 +71,7 @@ function createScriptConfig(defaultValue) {
71
71
  };
72
72
  const fetchScriptConfig = async () => {
73
73
  try {
74
- const response = await chunkYA2PXBP6_cjs.fetchNui("GET_FULL_SCRIPT_CONFIG");
74
+ const response = await chunkJIBWKY7H_cjs.fetchNui("GET_FULL_SCRIPT_CONFIG");
75
75
  if (response?.success && response.data?.config) {
76
76
  store.setState(() => response.data.config);
77
77
  if (typeof response.data.clientVersion === "number") {
@@ -85,7 +85,7 @@ function createScriptConfig(defaultValue) {
85
85
  };
86
86
  const fetchServerOnlyScriptConfig = async () => {
87
87
  try {
88
- const response = await chunkYA2PXBP6_cjs.fetchNui(
88
+ const response = await chunkJIBWKY7H_cjs.fetchNui(
89
89
  "GET_SERVER_ONLY_SCRIPT_CONFIG"
90
90
  );
91
91
  if (response?.success && response.data) {
@@ -113,7 +113,7 @@ function createScriptConfig(defaultValue) {
113
113
  } else {
114
114
  payload = { data: newConfig, expectedVersion: clientVersion };
115
115
  }
116
- const response = await chunkYA2PXBP6_cjs.fetchNui("UPDATE_SCRIPT_CONFIG", payload);
116
+ const response = await chunkJIBWKY7H_cjs.fetchNui("UPDATE_SCRIPT_CONFIG", payload);
117
117
  if (response?.meta?.client_version != null) {
118
118
  clientVersion = response.meta.client_version;
119
119
  }
@@ -123,10 +123,10 @@ function createScriptConfig(defaultValue) {
123
123
  return response;
124
124
  };
125
125
  const getScriptConfigHistory = async (params = {}) => {
126
- return chunkYA2PXBP6_cjs.fetchNui("GET_SCRIPT_CONFIG_HISTORY", params);
126
+ return chunkJIBWKY7H_cjs.fetchNui("GET_SCRIPT_CONFIG_HISTORY", params);
127
127
  };
128
128
  const resetConfig = async () => {
129
- const response = await chunkYA2PXBP6_cjs.fetchNui("RESET_SCRIPT_CONFIG");
129
+ const response = await chunkJIBWKY7H_cjs.fetchNui("RESET_SCRIPT_CONFIG");
130
130
  if (response?.success) {
131
131
  await fetchServerOnlyScriptConfig();
132
132
  }
@@ -146,5 +146,5 @@ function createScriptConfig(defaultValue) {
146
146
  exports.createScriptConfig = createScriptConfig;
147
147
  exports.getScriptConfigInstance = getScriptConfigInstance;
148
148
  exports.useNuiEvent = useNuiEvent;
149
- //# sourceMappingURL=chunk-3T2CCW5E.cjs.map
150
- //# sourceMappingURL=chunk-3T2CCW5E.cjs.map
149
+ //# sourceMappingURL=chunk-K64EX3U2.cjs.map
150
+ //# sourceMappingURL=chunk-K64EX3U2.cjs.map
@@ -1 +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"]}
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-K64EX3U2.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"]}
@@ -1,12 +1,12 @@
1
- import { useAudio, FormProvider, useFormField, useFormActions, usePlayers, useForm, useAdminState } from './chunk-KGLO3ZAS.js';
2
- import { dirkQueryClient, useAdminToolStore } from './chunk-NVMOGS7Y.js';
3
- import { getScriptConfigInstance } from './chunk-Z7N5AQJW.js';
4
- import { fetchNui, gameToMap, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, colorWithAlpha, useSettings, locale, useItems, useItemsList, useFrameworkGroups, ensureFrameworkGroups, isEnvBrowser, defaultThemeOverride, useModels, loadModels, useVehicles, ensureVehicles, DEFAULT_PALETTE, GTA_CONTROLS, GTA_CONTROL_GROUP_ORDER, formatGtaControl } from './chunk-MYNNCLMA.js';
5
- import { Flex, Text, Image, TextInput, Select, Box, useMantineTheme, Tooltip, alpha, Progress, RingProgress, Portal, Button, NumberInput, MultiSelect, Loader, Switch, ActionIcon, Stack, ColorInput, Popover, Autocomplete, Group, JsonInput } from '@mantine/core';
1
+ import { useAudio, FormProvider, useFormField, useFormActions, usePlayers, useForm, useAdminState } from './chunk-F6EV4VBH.js';
2
+ import { theme_default, dirkQueryClient, useAdminToolStore } from './chunk-Y25C6YMN.js';
3
+ import { getScriptConfigInstance } from './chunk-M7H4JKEL.js';
4
+ import { fetchNui, gameToMap, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, colorWithAlpha, useSettings, locale, useItems, useItemsList, useFrameworkGroups, ensureFrameworkGroups, isEnvBrowser, defaultThemeOverride, useModels, loadModels, useVehicles, ensureVehicles, DEFAULT_PALETTE, GTA_CONTROLS, GTA_CONTROL_GROUP_ORDER, formatGtaControl } from './chunk-BXP4HEYF.js';
5
+ import { Flex, Text, Image, DEFAULT_THEME, TextInput, Select, Box, useMantineTheme, Tooltip, alpha, Progress, RingProgress, Portal, Button, MantineThemeProvider, NumberInput, MultiSelect, Loader, Switch, ActionIcon, Stack, ColorInput, Popover, Autocomplete, Group, JsonInput } from '@mantine/core';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8
8
  import { motion, AnimatePresence, useMotionValue } from 'framer-motion';
9
- import { createContext, memo, useRef, useEffect, useContext, useState, useCallback, useMemo } from 'react';
9
+ import { createContext, memo, useRef, useEffect, useContext, useState, useMemo, useCallback } from 'react';
10
10
  import { Info, X, AlertTriangle, Trash2, RefreshCw, ChevronDown, Check, Copy, MapPin, Crosshair, EyeOff, Eye, RotateCcw, FlaskConical, ChevronUp, Palette, ShieldCheck, Users, Plus, UserRound, DoorOpen, Minus, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
11
11
  import { create, useStore } from 'zustand';
12
12
  import { notifications } from '@mantine/notifications';
@@ -2509,10 +2509,17 @@ function ConfirmModal({
2509
2509
  }
2510
2510
  ) });
2511
2511
  }
2512
- var TABS = [
2513
- { id: "ox", label: "ox_inventory" },
2514
- { id: "qb", label: "qb-inventory" },
2515
- { id: "esx", label: "ESX legacy SQL" }
2512
+ var INVENTORY_META = [
2513
+ { key: "ox_inventory", label: "ox_inventory" },
2514
+ { key: "qb-inventory", label: "qb-inventory" },
2515
+ { key: "qs-inventory", label: "qs-inventory" },
2516
+ { key: "codem-inventory", label: "codem-inventory" },
2517
+ { key: "tgiann-inventory", label: "tgiann-inventory" },
2518
+ { key: "one_inventory", label: "one_inventory" },
2519
+ { key: "ak47_inventory", label: "ak47_inventory" },
2520
+ { key: "core_inventory", label: "core_inventory" },
2521
+ { key: "devix-inventory", label: "devix-inventory" },
2522
+ { key: "esx", label: "ESX (legacy SQL)" }
2516
2523
  ];
2517
2524
  var useMissingItemsAudit = create((set, get) => ({
2518
2525
  data: null,
@@ -2524,7 +2531,7 @@ var useMissingItemsAudit = create((set, get) => ({
2524
2531
  try {
2525
2532
  const res = await fetchNui("GET_MISSING_ITEMS", void 0, {
2526
2533
  success: true,
2527
- data: { missing: [], snippets: { ox: "", qb: "", esx: "" } }
2534
+ data: { missing: [], snippets: {} }
2528
2535
  });
2529
2536
  if (res?.success && res.data) {
2530
2537
  set({ data: res.data, loaded: true });
@@ -2544,22 +2551,31 @@ function MissingItemsBanner() {
2544
2551
  const loaded = useMissingItemsAudit((s) => s.loaded);
2545
2552
  const inFlight = useMissingItemsAudit((s) => s.inFlight);
2546
2553
  const refresh = useMissingItemsAudit((s) => s.refresh);
2554
+ const serverInventory = useSettings((s) => s.inventory);
2547
2555
  const [expanded, setExpanded] = useState(false);
2548
- const [activeTab, setActiveTab] = useState("ox");
2549
- const [hoveredTab, setHoveredTab] = useState(null);
2550
- const [copied, setCopied] = useState(null);
2556
+ const [selected, setSelected] = useState(null);
2557
+ const [copied, setCopied] = useState(false);
2551
2558
  useEffect(() => {
2552
2559
  if (!loaded) refresh();
2553
2560
  }, [loaded, refresh]);
2554
- const handleCopy = useCallback((tab) => {
2555
- if (!audit) return;
2556
- const text = audit.snippets[tab] ?? "";
2561
+ const options = useMemo(() => {
2562
+ const keys = audit ? Object.keys(audit.snippets) : [];
2563
+ return INVENTORY_META.filter((m) => keys.includes(m.key)).map((m) => ({ value: m.key, label: m.label }));
2564
+ }, [audit]);
2565
+ const defaultKey = useMemo(() => {
2566
+ if (serverInventory && options.some((o) => o.value === serverInventory)) return serverInventory;
2567
+ return options[0]?.value ?? null;
2568
+ }, [serverInventory, options]);
2569
+ const active = selected ?? defaultKey;
2570
+ const handleCopy = useCallback(() => {
2571
+ if (!audit || !active) return;
2572
+ const text = audit.snippets[active] ?? "";
2557
2573
  navigator.clipboard.writeText(text).then(() => {
2558
- setCopied(tab);
2559
- setTimeout(() => setCopied((c) => c === tab ? null : c), 1500);
2574
+ setCopied(true);
2575
+ setTimeout(() => setCopied(false), 1500);
2560
2576
  }).catch(() => {
2561
2577
  });
2562
- }, [audit]);
2578
+ }, [audit, active]);
2563
2579
  const handleRefresh = useCallback((e) => {
2564
2580
  e.stopPropagation();
2565
2581
  refresh();
@@ -2631,48 +2647,40 @@ function MissingItemsBanner() {
2631
2647
  transition: { duration: 0.18, ease: "easeOut" },
2632
2648
  style: { overflow: "hidden", borderTop: `0.1vh solid ${alpha(warnColor, 0.18)}` },
2633
2649
  children: [
2634
- /* @__PURE__ */ jsx(Flex, { gap: "0", style: { borderBottom: `0.1vh solid ${alpha(warnColor, 0.18)}` }, children: TABS.map((tab) => {
2635
- const active = tab.id === activeTab;
2636
- const hovered = hoveredTab === tab.id;
2637
- let bg = "transparent";
2638
- if (active) bg = alpha(warnColor, 0.12);
2639
- else if (hovered) bg = alpha(warnColor, 0.08);
2640
- return /* @__PURE__ */ jsx(
2641
- "button",
2650
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "0.8vh", p: "0.8vh 1vh", onClick: (e) => e.stopPropagation(), children: [
2651
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.5)", style: { whiteSpace: "nowrap" }, children: "Your inventory" }),
2652
+ /* @__PURE__ */ jsx(
2653
+ Select,
2642
2654
  {
2643
- onClick: (e) => {
2644
- e.stopPropagation();
2645
- setActiveTab(tab.id);
2646
- },
2647
- onMouseEnter: () => setHoveredTab(tab.id),
2648
- onMouseLeave: () => setHoveredTab((h) => h === tab.id ? null : h),
2649
- style: {
2650
- flex: 1,
2651
- background: bg,
2652
- border: "none",
2653
- borderBottom: active ? `0.2vh solid ${warnColor}` : "0.2vh solid transparent",
2654
- padding: "0.3vh 1vh",
2655
- cursor: active ? "default" : "pointer",
2656
- color: active ? warnColor : "rgba(255,255,255,0.5)",
2657
- fontFamily: "Akrobat Bold",
2658
- fontSize: "var(--mantine-font-size-xxs)",
2659
- letterSpacing: "0.07em",
2660
- textTransform: "uppercase",
2661
- transition: "background 0.12s"
2655
+ size: "xs",
2656
+ data: options,
2657
+ value: active,
2658
+ onChange: (v) => {
2659
+ setSelected(v);
2660
+ setCopied(false);
2662
2661
  },
2663
- children: tab.label
2664
- },
2665
- tab.id
2666
- );
2667
- }) }),
2662
+ allowDeselect: false,
2663
+ comboboxProps: { withinPortal: false },
2664
+ style: { flex: 1, maxWidth: "28vh" },
2665
+ styles: {
2666
+ input: {
2667
+ background: alpha(warnColor, 0.06),
2668
+ borderColor: alpha(warnColor, 0.35),
2669
+ color: warnColor,
2670
+ fontFamily: "Akrobat Bold"
2671
+ }
2672
+ }
2673
+ }
2674
+ )
2675
+ ] }),
2668
2676
  /* @__PURE__ */ jsx(
2669
2677
  CodeView,
2670
2678
  {
2671
- code: audit.snippets[activeTab] ?? "",
2672
- copied: copied === activeTab,
2679
+ code: active && audit.snippets[active] || "",
2680
+ copied,
2673
2681
  onCopy: (e) => {
2674
2682
  e.stopPropagation();
2675
- handleCopy(activeTab);
2683
+ handleCopy();
2676
2684
  },
2677
2685
  warnColor
2678
2686
  }
@@ -2749,6 +2757,31 @@ function CodeView({
2749
2757
  ] }, i)) }) }) })
2750
2758
  ] });
2751
2759
  }
2760
+ var panelTheme = {
2761
+ // Sections read theme.colors.dark[5/6/7/8/9] for row fills, dividers and
2762
+ // modal backdrops (the markup dirk_fishing established). Mantine's own dark
2763
+ // ramp is what that markup was designed against.
2764
+ colors: {
2765
+ dark: DEFAULT_THEME.colors.dark
2766
+ },
2767
+ // Type. The panel is sized in `vh` throughout, so it wants the library's vh
2768
+ // line-heights. A consumer whose own UI is sized in container units will
2769
+ // reasonably have swapped these for unitless ratios — correct for them,
2770
+ // wrong here.
2771
+ fontSizes: theme_default.fontSizes,
2772
+ lineHeights: theme_default.lineHeights
2773
+ };
2774
+ var panelCssVars = {
2775
+ ...Object.fromEntries(
2776
+ Object.entries(theme_default.fontSizes ?? {}).map(([k, v]) => [`--mantine-font-size-${k}`, v])
2777
+ ),
2778
+ ...Object.fromEntries(
2779
+ Object.entries(theme_default.lineHeights ?? {}).map(([k, v]) => [`--mantine-line-height-${k}`, v])
2780
+ ),
2781
+ ...Object.fromEntries(
2782
+ DEFAULT_THEME.colors.dark.map((c, i) => [`--mantine-color-dark-${i}`, c])
2783
+ )
2784
+ };
2752
2785
  function NavItemButton({
2753
2786
  icon: Icon,
2754
2787
  label,
@@ -3170,6 +3203,10 @@ function ConfigPanelInner({
3170
3203
  h: height ?? "80vh",
3171
3204
  bdrs: "sm",
3172
3205
  style: {
3206
+ // Re-declare the library's own type scale and greys for this subtree.
3207
+ // Custom properties inherit, so every Mantine `size=` inside the
3208
+ // panel resolves against these instead of the host resource's.
3209
+ ...panelCssVars,
3173
3210
  userSelect: "none",
3174
3211
  overflow: "hidden",
3175
3212
  transform: "translate(-50%, -50%)",
@@ -3353,55 +3390,60 @@ function ConfigPanel(props) {
3353
3390
  const { store, updateConfig } = getScriptConfigInstance();
3354
3391
  const [isSaving, setIsSaving] = useState(false);
3355
3392
  if (!open) return null;
3356
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(
3357
- FormProvider,
3358
- {
3359
- initialValues: cloneConfig(store.getState()),
3360
- onSubmit: async (form) => {
3361
- if (isSaving) return;
3362
- setIsSaving(true);
3363
- try {
3364
- const result = await updateConfig(form.values, form.changedFields);
3365
- if (result?.success) {
3366
- form.reinitialize(cloneConfig(form.values));
3367
- dirkQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
3368
- useMissingItemsAudit.getState().refresh();
3393
+ return (
3394
+ // Outside FormProvider and ConfigPanelInner on purpose: the panel's own
3395
+ // chrome calls useMantineTheme(), so it has to be inside this scope too,
3396
+ // not just the consumer's sections.
3397
+ /* @__PURE__ */ jsx(MantineThemeProvider, { theme: panelTheme, children: /* @__PURE__ */ jsxs(
3398
+ FormProvider,
3399
+ {
3400
+ initialValues: cloneConfig(store.getState()),
3401
+ onSubmit: async (form) => {
3402
+ if (isSaving) return;
3403
+ setIsSaving(true);
3404
+ try {
3405
+ const result = await updateConfig(form.values, form.changedFields);
3406
+ if (result?.success) {
3407
+ form.reinitialize(cloneConfig(form.values));
3408
+ dirkQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
3409
+ useMissingItemsAudit.getState().refresh();
3410
+ notifications.show({
3411
+ color: "green",
3412
+ title: locale("ConfigSaveSuccessTitle"),
3413
+ message: locale("ConfigSaveSuccessBody"),
3414
+ autoClose: 3e3
3415
+ });
3416
+ return;
3417
+ }
3418
+ form.reinitialize(cloneConfig(store.getState()));
3419
+ const err = result?._error || "Unknown";
3420
+ console.warn(`[ConfigPanel] config save failed: ${err}`);
3421
+ const titleKey = err === "NoPermission" ? "ConfigSaveNoPermissionTitle" : err === "VersionConflict" ? "ConfigSaveVersionConflictTitle" : err === "NotReady" ? "ConfigSaveNotReadyTitle" : "ConfigSaveFailedTitle";
3422
+ const bodyKey = err === "NoPermission" ? "ConfigSaveNoPermissionBody" : err === "VersionConflict" ? "ConfigSaveVersionConflictBody" : err === "NotReady" ? "ConfigSaveNotReadyBody" : "ConfigSaveFailedBody";
3369
3423
  notifications.show({
3370
- color: "green",
3371
- title: locale("ConfigSaveSuccessTitle"),
3372
- message: locale("ConfigSaveSuccessBody"),
3373
- autoClose: 3e3
3424
+ color: "red",
3425
+ title: locale(titleKey),
3426
+ message: locale(bodyKey, err),
3427
+ autoClose: 6e3
3374
3428
  });
3375
- return;
3376
- }
3377
- form.reinitialize(cloneConfig(store.getState()));
3378
- const err = result?._error || "Unknown";
3379
- console.warn(`[ConfigPanel] config save failed: ${err}`);
3380
- const titleKey = err === "NoPermission" ? "ConfigSaveNoPermissionTitle" : err === "VersionConflict" ? "ConfigSaveVersionConflictTitle" : err === "NotReady" ? "ConfigSaveNotReadyTitle" : "ConfigSaveFailedTitle";
3381
- const bodyKey = err === "NoPermission" ? "ConfigSaveNoPermissionBody" : err === "VersionConflict" ? "ConfigSaveVersionConflictBody" : err === "NotReady" ? "ConfigSaveNotReadyBody" : "ConfigSaveFailedBody";
3382
- notifications.show({
3383
- color: "red",
3384
- title: locale(titleKey),
3385
- message: locale(bodyKey, err),
3386
- autoClose: 6e3
3387
- });
3388
- } finally {
3389
- setIsSaving(false);
3390
- }
3391
- },
3392
- children: [
3393
- /* @__PURE__ */ jsx(ServerOnlyFetcher, {}),
3394
- /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(
3395
- ConfigPanelInner,
3396
- {
3397
- ...props,
3398
- onClose,
3399
- isSaving
3429
+ } finally {
3430
+ setIsSaving(false);
3400
3431
  }
3401
- ) })
3402
- ]
3403
- }
3404
- ) });
3432
+ },
3433
+ children: [
3434
+ /* @__PURE__ */ jsx(ServerOnlyFetcher, {}),
3435
+ /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(
3436
+ ConfigPanelInner,
3437
+ {
3438
+ ...props,
3439
+ onClose,
3440
+ isSaving
3441
+ }
3442
+ ) })
3443
+ ]
3444
+ }
3445
+ ) })
3446
+ );
3405
3447
  }
3406
3448
  var MISSING_COLOR = "#f59e0b";
3407
3449
  function LazyImage({ src, style }) {
@@ -6642,5 +6684,5 @@ function CategorySelect({
6642
6684
  }
6643
6685
 
6644
6686
  export { AccessOverrideSection, AccountSelect, AdminPageTitle, AnimPostFxSelect, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BlipMarker, BorderedIcon, CategorySelect, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, DiscordRoleSelect, DoorPickerButton, FiveMKeyBindInput, FloatingParticles, GroupName, GroupRank, GroupSelect, InfoBox, InputContainer, InstructionPanel, LevelBanner, LevelPanel, Map2 as Map, MapLayer, MissingItemsBanner, Modal, ModalContext, ModalProvider, ModelSelect, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PlayerSelect, PositionPicker, PromptModal, ScenarioSelect, SegmentedControl, SegmentedProgress, SelectItem, SwitchPanel, TestBed, ThemeOverrideSection, Title, Vector4DeleteButton, Vector4Display, VehicleSelect, WorldPositionGotoButton, WorldPositionPicker, WorldPositionSetButton, ZoomControls, blipUrl, blipUrlForSprite, getBlipColor, getBlipEntry, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore, usePickDoor };
6645
- //# sourceMappingURL=chunk-HAB6H274.js.map
6646
- //# sourceMappingURL=chunk-HAB6H274.js.map
6687
+ //# sourceMappingURL=chunk-M5NZAOYQ.js.map
6688
+ //# sourceMappingURL=chunk-M5NZAOYQ.js.map