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.
- package/dist/{chunk-E2SFEBBB.cjs → chunk-7EICODPM.cjs} +4 -4
- package/dist/{chunk-E2SFEBBB.cjs.map → chunk-7EICODPM.cjs.map} +1 -1
- package/dist/{chunk-MYNNCLMA.js → chunk-BXP4HEYF.js} +334 -43
- package/dist/chunk-BXP4HEYF.js.map +1 -0
- package/dist/{chunk-KGLO3ZAS.js → chunk-F6EV4VBH.js} +3 -3
- package/dist/{chunk-KGLO3ZAS.js.map → chunk-F6EV4VBH.js.map} +1 -1
- package/dist/{chunk-K3GDU6EY.cjs → chunk-I4YJBITG.cjs} +298 -256
- package/dist/chunk-I4YJBITG.cjs.map +1 -0
- package/dist/{chunk-VKYONY7E.cjs → chunk-J5VNINGK.cjs} +5 -5
- package/dist/{chunk-VKYONY7E.cjs.map → chunk-J5VNINGK.cjs.map} +1 -1
- package/dist/{chunk-YA2PXBP6.cjs → chunk-JIBWKY7H.cjs} +334 -43
- package/dist/chunk-JIBWKY7H.cjs.map +1 -0
- package/dist/{chunk-ZPFW7C2A.js → chunk-JQHQYPEL.js} +3 -3
- package/dist/{chunk-ZPFW7C2A.js.map → chunk-JQHQYPEL.js.map} +1 -1
- package/dist/{chunk-3T2CCW5E.cjs → chunk-K64EX3U2.cjs} +9 -9
- package/dist/{chunk-3T2CCW5E.cjs.map → chunk-K64EX3U2.cjs.map} +1 -1
- package/dist/{chunk-HAB6H274.js → chunk-M5NZAOYQ.js} +145 -103
- package/dist/chunk-M5NZAOYQ.js.map +1 -0
- package/dist/{chunk-Z7N5AQJW.js → chunk-M7H4JKEL.js} +3 -3
- package/dist/{chunk-Z7N5AQJW.js.map → chunk-M7H4JKEL.js.map} +1 -1
- package/dist/{chunk-QOQQ3ERZ.cjs → chunk-NBLBFWZQ.cjs} +18 -17
- package/dist/chunk-NBLBFWZQ.cjs.map +1 -0
- package/dist/{chunk-NVMOGS7Y.js → chunk-Y25C6YMN.js} +5 -5
- package/dist/chunk-Y25C6YMN.js.map +1 -0
- package/dist/components/index.cjs +76 -76
- package/dist/components/index.d.cts +1 -5
- package/dist/components/index.d.ts +1 -5
- package/dist/components/index.js +5 -5
- package/dist/hooks/index.cjs +22 -22
- package/dist/hooks/index.js +4 -4
- package/dist/index.cjs +147 -147
- package/dist/index.js +6 -6
- package/dist/providers/index.cjs +5 -5
- package/dist/providers/index.js +3 -3
- package/dist/utils/index.cjs +48 -48
- package/dist/utils/index.js +1 -1
- package/package.json +123 -123
- package/dist/chunk-HAB6H274.js.map +0 -1
- package/dist/chunk-K3GDU6EY.cjs.map +0 -1
- package/dist/chunk-MYNNCLMA.js.map +0 -1
- package/dist/chunk-NVMOGS7Y.js.map +0 -1
- package/dist/chunk-QOQQ3ERZ.cjs.map +0 -1
- package/dist/chunk-YA2PXBP6.cjs.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
126
|
+
return chunkJIBWKY7H_cjs.fetchNui("GET_SCRIPT_CONFIG_HISTORY", params);
|
|
127
127
|
};
|
|
128
128
|
const resetConfig = async () => {
|
|
129
|
-
const response = await
|
|
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-
|
|
150
|
-
//# sourceMappingURL=chunk-
|
|
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-
|
|
2
|
-
import { dirkQueryClient, useAdminToolStore } from './chunk-
|
|
3
|
-
import { getScriptConfigInstance } from './chunk-
|
|
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-
|
|
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,
|
|
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
|
|
2513
|
-
{
|
|
2514
|
-
{
|
|
2515
|
-
{
|
|
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: {
|
|
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 [
|
|
2549
|
-
const [
|
|
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
|
|
2555
|
-
|
|
2556
|
-
|
|
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(
|
|
2559
|
-
setTimeout(() => setCopied(
|
|
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__ */
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
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
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
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
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
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[
|
|
2672
|
-
copied
|
|
2679
|
+
code: active && audit.snippets[active] || "",
|
|
2680
|
+
copied,
|
|
2673
2681
|
onCopy: (e) => {
|
|
2674
2682
|
e.stopPropagation();
|
|
2675
|
-
handleCopy(
|
|
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
|
|
3357
|
-
FormProvider
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
if (
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
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: "
|
|
3371
|
-
title: locale(
|
|
3372
|
-
message: locale(
|
|
3373
|
-
autoClose:
|
|
3424
|
+
color: "red",
|
|
3425
|
+
title: locale(titleKey),
|
|
3426
|
+
message: locale(bodyKey, err),
|
|
3427
|
+
autoClose: 6e3
|
|
3374
3428
|
});
|
|
3375
|
-
|
|
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-
|
|
6646
|
-
//# sourceMappingURL=chunk-
|
|
6687
|
+
//# sourceMappingURL=chunk-M5NZAOYQ.js.map
|
|
6688
|
+
//# sourceMappingURL=chunk-M5NZAOYQ.js.map
|