dirk-cfx-react 1.1.58 → 1.1.64

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/index.cjs CHANGED
@@ -7,9 +7,9 @@ var zustand = require('zustand');
7
7
  var axios = require('axios');
8
8
  var reactFontawesome = require('@fortawesome/react-fontawesome');
9
9
  var framerMotion = require('framer-motion');
10
+ var lucideReact = require('lucide-react');
10
11
  var clickSoundUrl = require('./click_sound-PNCRRTM4.mp3');
11
12
  var hoverSoundUrl = require('./hover_sound-NBUA222C.mp3');
12
- var lucideReact = require('lucide-react');
13
13
  var reactQuery = require('@tanstack/react-query');
14
14
  require('@mantine/core/styles.css');
15
15
  require('@mantine/notifications/styles.css');
@@ -1315,8 +1315,12 @@ async function fetchNui(eventName, data, mockData) {
1315
1315
  }
1316
1316
  const overrideResourceName = useSettings.getState().overideResourceName;
1317
1317
  const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName ? overrideResourceName : "dirk-cfx-react";
1318
- const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1319
- return await resp.json();
1318
+ try {
1319
+ const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1320
+ return await resp.json();
1321
+ } catch {
1322
+ return mockData ?? {};
1323
+ }
1320
1324
  }
1321
1325
  var initialFetches = {};
1322
1326
  async function registerInitialFetch(eventName, data, mockData) {
@@ -1335,10 +1339,8 @@ async function runFetches() {
1335
1339
  var useAutoFetcher = () => {
1336
1340
  React5.useEffect(() => {
1337
1341
  if (isEnvBrowser()) return;
1338
- const run = async () => {
1339
- await runFetches();
1340
- };
1341
- run();
1342
+ runFetches().catch(() => {
1343
+ });
1342
1344
  }, []);
1343
1345
  };
1344
1346
  var fetchLuaTable = (tableName, mockData) => {
@@ -1365,6 +1367,13 @@ var internalEvent = (events, timer = 1e3) => {
1365
1367
  }
1366
1368
  }
1367
1369
  };
1370
+ var reportedMissing = /* @__PURE__ */ new Set();
1371
+ function reportMissingLocale(key) {
1372
+ if (!key || reportedMissing.has(key)) return;
1373
+ reportedMissing.add(key);
1374
+ fetchNui("REPORT_MISSING_LOCALE", { key }).catch(() => {
1375
+ });
1376
+ }
1368
1377
  var localeStore = zustand.create((set, get) => {
1369
1378
  return {
1370
1379
  locales: {
@@ -1372,6 +1381,7 @@ var localeStore = zustand.create((set, get) => {
1372
1381
  },
1373
1382
  locale: (key, ...args) => {
1374
1383
  const exists = get().locales[key];
1384
+ if (!exists) reportMissingLocale(key);
1375
1385
  let translation = exists || key;
1376
1386
  if (args.length) {
1377
1387
  translation = translation.replace(/%s/g, () => String(args.shift() || ""));
@@ -1383,7 +1393,16 @@ var localeStore = zustand.create((set, get) => {
1383
1393
  var locale = localeStore.getState().locale;
1384
1394
  registerInitialFetch("GET_LOCALES", void 0).then((data) => {
1385
1395
  localeStore.setState({ locales: data });
1396
+ }).catch(() => {
1386
1397
  });
1398
+ if (typeof window !== "undefined") {
1399
+ window.addEventListener("message", (event) => {
1400
+ const msg = event.data;
1401
+ if (!msg || msg.action !== "UPDATE_DIRK_LIB_LOCALES") return;
1402
+ if (!msg.data || typeof msg.data !== "object") return;
1403
+ localeStore.setState({ locales: msg.data });
1404
+ });
1405
+ }
1387
1406
 
1388
1407
  // src/utils/map.ts
1389
1408
  var mapCenter = [-119.43, 58.84];
@@ -1900,6 +1919,7 @@ registerInitialFetch("FETCH_ALL_ITEMS", null, {
1900
1919
  }).then((fetchedItems) => {
1901
1920
  if (!fetchedItems) return;
1902
1921
  useItems.setState(fetchedItems);
1922
+ }).catch(() => {
1903
1923
  });
1904
1924
 
1905
1925
  // src/utils/inputMapper.ts
@@ -2545,8 +2565,8 @@ function InputContainer(props) {
2545
2565
  (props.title || props.description) && /* @__PURE__ */ jsxRuntime.jsxs(
2546
2566
  core.Flex,
2547
2567
  {
2548
- direction: "column",
2549
- gap: "xxs",
2568
+ align: "center",
2569
+ flex: 1,
2550
2570
  p: props.p == "0" ? "sm" : 0,
2551
2571
  children: [
2552
2572
  props.title && /* @__PURE__ */ jsxRuntime.jsx(
@@ -2563,12 +2583,26 @@ function InputContainer(props) {
2563
2583
  }
2564
2584
  ),
2565
2585
  props.description && /* @__PURE__ */ jsxRuntime.jsx(
2566
- core.Text,
2586
+ core.Tooltip,
2567
2587
  {
2568
- size: "xs",
2569
- c: "rgba(255, 255, 255, 0.8)",
2570
- fw: 400,
2571
- children: props.description
2588
+ label: props.description,
2589
+ position: "top-end",
2590
+ withArrow: true,
2591
+ multiline: true,
2592
+ maw: "22vh",
2593
+ styles: {
2594
+ tooltip: {
2595
+ background: core.alpha(theme2.colors.dark[7], 0.95),
2596
+ border: `0.1vh solid rgba(255,255,255,0.1)`,
2597
+ color: "rgba(255,255,255,0.75)",
2598
+ fontFamily: "Akrobat Bold",
2599
+ fontSize: "1.3vh",
2600
+ lineHeight: 1.3,
2601
+ padding: "0.6vh 0.8vh",
2602
+ letterSpacing: "0.03em"
2603
+ }
2604
+ },
2605
+ children: /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { align: "center", justify: "center", style: { marginLeft: "auto", cursor: "help" }, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { size: "1.6vh", color: core.alpha(theme2.colors[theme2.primaryColor][5], 0.45) }) })
2572
2606
  }
2573
2607
  )
2574
2608
  ]
@@ -2585,13 +2619,7 @@ function InputContainer(props) {
2585
2619
  children: props.error
2586
2620
  }
2587
2621
  ),
2588
- /* @__PURE__ */ jsxRuntime.jsx(
2589
- core.Flex,
2590
- {
2591
- ml: "auto",
2592
- children: props.rightSection
2593
- }
2594
- )
2622
+ props.rightSection && /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { children: props.rightSection })
2595
2623
  ]
2596
2624
  }
2597
2625
  ),
@@ -3282,7 +3310,7 @@ function Modal({
3282
3310
  children: description2
3283
3311
  }
3284
3312
  ) }),
3285
- children
3313
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }, children })
3286
3314
  ]
3287
3315
  }
3288
3316
  )
@@ -3964,42 +3992,42 @@ var useNuiEvent = (action, handler) => {
3964
3992
  }, [action]);
3965
3993
  };
3966
3994
  var _instance = null;
3967
- function getScriptSettingsInstance() {
3968
- if (!_instance) throw new Error("[dirk-cfx-react] createScriptSettings must be called before using SettingsPanel");
3995
+ function getScriptConfigInstance() {
3996
+ if (!_instance) throw new Error("[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel");
3969
3997
  return _instance;
3970
3998
  }
3971
- function createScriptSettings(defaultValue) {
3999
+ function createScriptConfig(defaultValue) {
3972
4000
  const store = zustand.create(() => defaultValue);
3973
4001
  let clientVersion = 0;
3974
- const useScriptSettingHooks = () => {
3975
- useNuiEvent("UPDATE_SCRIPT_SETTINGS", (data) => {
4002
+ const useScriptConfigHooks = () => {
4003
+ useNuiEvent("UPDATE_SCRIPT_CONFIG", (data) => {
3976
4004
  if (!data) return;
3977
4005
  if (typeof data.clientVersion === "number") {
3978
4006
  clientVersion = data.clientVersion;
3979
4007
  }
3980
- if (data.settings && typeof data.settings === "object") {
3981
- store.setState((prev) => ({ ...prev, ...data.settings }));
4008
+ if (data.config && typeof data.config === "object") {
4009
+ store.setState((prev) => ({ ...prev, ...data.config }));
3982
4010
  }
3983
4011
  });
3984
4012
  };
3985
- const fetchScriptSettings = async () => {
4013
+ const fetchScriptConfig = async () => {
3986
4014
  try {
3987
- const response = await fetchNui("GET_FULL_SCRIPT_SETTINGS");
3988
- if (response?.success && response.data?.settings) {
3989
- store.setState(() => response.data.settings);
4015
+ const response = await fetchNui("GET_FULL_SCRIPT_CONFIG");
4016
+ if (response?.success && response.data?.config) {
4017
+ store.setState(() => response.data.config);
3990
4018
  if (typeof response.data.clientVersion === "number") {
3991
4019
  clientVersion = response.data.clientVersion;
3992
4020
  }
3993
- return response.data.settings;
4021
+ return response.data.config;
3994
4022
  }
3995
4023
  } catch {
3996
4024
  }
3997
4025
  return null;
3998
4026
  };
3999
- const updateScriptSettings = async (newSettings) => {
4000
- store.setState((prev) => ({ ...prev, ...newSettings }));
4001
- const response = await fetchNui("UPDATE_SCRIPT_SETTINGS", {
4002
- data: newSettings,
4027
+ const updateScriptConfig = async (newConfig) => {
4028
+ store.setState((prev) => ({ ...prev, ...newConfig }));
4029
+ const response = await fetchNui("UPDATE_SCRIPT_CONFIG", {
4030
+ data: newConfig,
4003
4031
  expectedVersion: clientVersion
4004
4032
  });
4005
4033
  if (response?.meta?.client_version != null) {
@@ -4010,18 +4038,29 @@ function createScriptSettings(defaultValue) {
4010
4038
  }
4011
4039
  return response;
4012
4040
  };
4013
- const getScriptSettingsHistory = async (params = {}) => {
4014
- return fetchNui("GET_SCRIPT_SETTINGS_HISTORY", params);
4041
+ const getScriptConfigHistory = async (params = {}) => {
4042
+ return fetchNui("GET_SCRIPT_CONFIG_HISTORY", params);
4043
+ };
4044
+ const resetConfig = async () => {
4045
+ const response = await fetchNui("RESET_SCRIPT_CONFIG");
4046
+ if (response?.success) {
4047
+ const fresh = await fetchScriptConfig();
4048
+ if (fresh) {
4049
+ store.setState(() => fresh);
4050
+ }
4051
+ }
4052
+ return response;
4015
4053
  };
4016
4054
  _instance = {
4017
4055
  store,
4018
- updateSettings: updateScriptSettings,
4019
- getHistory: getScriptSettingsHistory,
4020
- fetchSettings: fetchScriptSettings
4056
+ updateConfig: updateScriptConfig,
4057
+ resetConfig,
4058
+ getHistory: getScriptConfigHistory,
4059
+ fetchConfig: fetchScriptConfig
4021
4060
  };
4022
- return { store, updateScriptSettings, getScriptSettingsHistory, useScriptSettingHooks, fetchScriptSettings };
4061
+ return { store, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig };
4023
4062
  }
4024
- var settingsPanelQueryClient = new reactQuery.QueryClient({
4063
+ var configPanelQueryClient = new reactQuery.QueryClient({
4025
4064
  defaultOptions: { queries: { staleTime: 3e4, gcTime: 5 * 6e4 } }
4026
4065
  });
4027
4066
  function NavItemButton({
@@ -4065,7 +4104,7 @@ function NavItemButton({
4065
4104
  }
4066
4105
  );
4067
4106
  }
4068
- function SettingsJsonModal({
4107
+ function ConfigJsonModal({
4069
4108
  onClose,
4070
4109
  schema
4071
4110
  }) {
@@ -4102,7 +4141,7 @@ function SettingsJsonModal({
4102
4141
  setError(e.message);
4103
4142
  }
4104
4143
  };
4105
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Settings JSON", icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
4144
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config JSON", icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
4106
4145
  /* @__PURE__ */ jsxRuntime.jsxs(core.Box, { flex: 1, p: "0.8vh", style: { overflowY: "auto" }, children: [
4107
4146
  /* @__PURE__ */ jsxRuntime.jsx(
4108
4147
  core.JsonInput,
@@ -4244,10 +4283,10 @@ function HistoryTableHeader() {
4244
4283
  /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.45)", children: "Version" })
4245
4284
  ] });
4246
4285
  }
4247
- function SettingsHistoryModal({
4286
+ function ConfigHistoryModal({
4248
4287
  onClose
4249
4288
  }) {
4250
- const { getHistory } = getScriptSettingsInstance();
4289
+ const { getHistory } = getScriptConfigInstance();
4251
4290
  const theme2 = core.useMantineTheme();
4252
4291
  const color = theme2.colors[theme2.primaryColor][5];
4253
4292
  const [queryInput, setQueryInput] = React5.useState("");
@@ -4259,7 +4298,7 @@ function SettingsHistoryModal({
4259
4298
  const [expandedKey, setExpandedKey] = React5.useState(null);
4260
4299
  const filters = React5.useMemo(() => ({ query, path, admin }), [query, path, admin]);
4261
4300
  const historyQuery = reactQuery.useInfiniteQuery({
4262
- queryKey: ["scriptSettingsHistory", filters],
4301
+ queryKey: ["scriptConfigHistory", filters],
4263
4302
  initialPageParam: 0,
4264
4303
  queryFn: async ({ pageParam }) => {
4265
4304
  const response = await getHistory({
@@ -4270,7 +4309,7 @@ function SettingsHistoryModal({
4270
4309
  admin: filters.admin || void 0
4271
4310
  });
4272
4311
  if (!response?.success || !response.data) {
4273
- throw new Error(response?._error || "Failed to load settings history");
4312
+ throw new Error(response?._error || "Failed to load config history");
4274
4313
  }
4275
4314
  return response.data;
4276
4315
  },
@@ -4284,7 +4323,7 @@ function SettingsHistoryModal({
4284
4323
  historyQuery.fetchNextPage();
4285
4324
  }
4286
4325
  };
4287
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Settings History", icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
4326
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config History", icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
4288
4327
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minHeight: 0 }, children: [
4289
4328
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { gap: "xs", p: "sm", style: { borderBottom: `0.1vh solid ${core.alpha(theme2.colors.dark[7], 0.8)}` }, children: [
4290
4329
  /* @__PURE__ */ jsxRuntime.jsx(core.TextInput, { leftSection: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { size: "1.4vh" }), placeholder: "Search path/admin/value", value: queryInput, onChange: (e) => setQueryInput(e.currentTarget.value), size: "xs", style: { flex: 1 } }),
@@ -4333,7 +4372,7 @@ function SettingsHistoryModal({
4333
4372
  ) })
4334
4373
  ] });
4335
4374
  }
4336
- function SettingsPanelInner({
4375
+ function ConfigPanelInner({
4337
4376
  navItems,
4338
4377
  title,
4339
4378
  subtitle,
@@ -4342,28 +4381,37 @@ function SettingsPanelInner({
4342
4381
  onClose,
4343
4382
  schema,
4344
4383
  resetConfirmText,
4345
- defaultSettings,
4384
+ defaultConfig,
4346
4385
  width,
4347
4386
  height
4348
4387
  }) {
4349
- const { updateSettings, getHistory } = getScriptSettingsInstance();
4388
+ const { updateConfig, resetConfig, getHistory } = getScriptConfigInstance();
4350
4389
  const form = useForm();
4351
4390
  const theme2 = core.useMantineTheme();
4352
4391
  const color = theme2.colors[theme2.primaryColor][5];
4353
4392
  const version = useSettings((s) => s.resourceVersion);
4354
4393
  const [activeTab, setActiveTab] = React5.useState(navItems[0]?.id ?? "");
4394
+ const firstMountRef = React5.useRef(true);
4355
4395
  const [jsonOpen, setJsonOpen] = React5.useState(false);
4356
4396
  const [historyOpen, setHistoryOpen] = React5.useState(false);
4357
4397
  const [resetOpen, setResetOpen] = React5.useState(false);
4358
- const [closeConfirmOpen, setCloseConfirmOpen] = React5.useState(false);
4398
+ const [pendingAction, setPendingAction] = React5.useState(null);
4359
4399
  const changedCount = form.changedCount ?? 0;
4360
4400
  const isDirty = changedCount > 0;
4401
+ const goBack = () => fetchNui("CONFIG_PANEL_BACK");
4402
+ const handleBack = () => {
4403
+ if (isDirty) {
4404
+ setPendingAction("back");
4405
+ return;
4406
+ }
4407
+ goBack();
4408
+ };
4361
4409
  React5.useEffect(() => {
4362
4410
  function handleKeyDown(e) {
4363
4411
  if (e.key !== "Escape") return;
4364
4412
  if (isDirty) {
4365
4413
  e.preventDefault();
4366
- setCloseConfirmOpen(true);
4414
+ setPendingAction("close");
4367
4415
  return;
4368
4416
  }
4369
4417
  onClose();
@@ -4372,34 +4420,40 @@ function SettingsPanelInner({
4372
4420
  return () => window.removeEventListener("keydown", handleKeyDown);
4373
4421
  }, [isDirty, onClose]);
4374
4422
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4375
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: jsonOpen && /* @__PURE__ */ jsxRuntime.jsx(SettingsJsonModal, { onClose: () => setJsonOpen(false), schema }) }),
4376
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: historyOpen && /* @__PURE__ */ jsxRuntime.jsx(SettingsHistoryModal, { onClose: () => setHistoryOpen(false) }) }),
4423
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: jsonOpen && /* @__PURE__ */ jsxRuntime.jsx(ConfigJsonModal, { onClose: () => setJsonOpen(false), schema }) }),
4424
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: historyOpen && /* @__PURE__ */ jsxRuntime.jsx(ConfigHistoryModal, { onClose: () => setHistoryOpen(false) }) }),
4377
4425
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: resetOpen && /* @__PURE__ */ jsxRuntime.jsx(
4378
4426
  ConfirmModal,
4379
4427
  {
4380
4428
  title: "Reset to Defaults",
4381
- description: "This will permanently reset ALL settings back to their defaults. Every setting you have configured will be overwritten. This cannot be undone.",
4382
- confirmLabel: "Reset Settings",
4429
+ description: "This will permanently reset ALL config back to the defaults. Every value you have configured will be overwritten. This cannot be undone.",
4430
+ confirmLabel: "Reset Config",
4383
4431
  confirmText: resetConfirmText,
4384
- onConfirm: () => {
4385
- updateSettings(defaultSettings).then(() => form.reinitialize(cloneSettings(defaultSettings)));
4432
+ onConfirm: async () => {
4386
4433
  setResetOpen(false);
4434
+ const result = await resetConfig();
4435
+ if (result?.success) {
4436
+ const { store } = getScriptConfigInstance();
4437
+ form.reinitialize(cloneConfig(store.getState()));
4438
+ }
4387
4439
  },
4388
4440
  onClose: () => setResetOpen(false),
4389
4441
  zIndex: 300
4390
4442
  }
4391
4443
  ) }),
4392
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: closeConfirmOpen && /* @__PURE__ */ jsxRuntime.jsx(
4444
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: pendingAction !== null && /* @__PURE__ */ jsxRuntime.jsx(
4393
4445
  ConfirmModal,
4394
4446
  {
4395
4447
  title: "Discard Unsaved Changes?",
4396
- description: "You have unsaved changes. Closing now will discard them.",
4397
- confirmLabel: "Close Without Saving",
4448
+ description: pendingAction === "back" ? "You have unsaved changes. Going back now will discard them." : "You have unsaved changes. Closing now will discard them.",
4449
+ confirmLabel: pendingAction === "back" ? "Go Back Without Saving" : "Close Without Saving",
4398
4450
  onConfirm: () => {
4399
- setCloseConfirmOpen(false);
4400
- onClose();
4451
+ const action = pendingAction;
4452
+ setPendingAction(null);
4453
+ if (action === "back") goBack();
4454
+ else onClose();
4401
4455
  },
4402
- onClose: () => setCloseConfirmOpen(false),
4456
+ onClose: () => setPendingAction(null),
4403
4457
  zIndex: 300
4404
4458
  }
4405
4459
  ) }),
@@ -4426,9 +4480,33 @@ function SettingsPanelInner({
4426
4480
  exit: { scale: 0.3, opacity: 0, transform: "translate(-50%, -50%)" },
4427
4481
  children: [
4428
4482
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { width: "18vh", flexShrink: 0, borderRight: `0.1vh solid ${core.alpha(theme2.colors.dark[6], 0.8)}`, background: core.alpha(theme2.colors.dark[8], 0.6), overflow: "hidden" }, children: [
4429
- /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "baseline", gap: "0.3vh", px: "sm", py: "sm", style: { borderBottom: `0.1vh solid ${core.alpha(theme2.colors.dark[6], 0.5)}`, flexShrink: 0 }, children: [
4430
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", children: title }),
4431
- subtitle && /* @__PURE__ */ jsxRuntime.jsx(core.Text, { tt: "uppercase", fw: 600, c: color, children: subtitle })
4483
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "0.6vh", px: "sm", py: "sm", style: { borderBottom: `0.1vh solid ${core.alpha(theme2.colors.dark[6], 0.5)}`, flexShrink: 0 }, children: [
4484
+ /* @__PURE__ */ jsxRuntime.jsx(
4485
+ framerMotion.motion.button,
4486
+ {
4487
+ title: "Back to script list",
4488
+ onClick: handleBack,
4489
+ whileHover: { background: core.alpha(color, 0.16), borderColor: core.alpha(color, 0.45) },
4490
+ whileTap: { scale: 0.95 },
4491
+ style: {
4492
+ aspectRatio: "1 / 1",
4493
+ height: "2.4vh",
4494
+ background: core.alpha(color, 0.08),
4495
+ border: `0.1vh solid ${core.alpha(color, 0.3)}`,
4496
+ borderRadius: theme2.radius.xs,
4497
+ cursor: "pointer",
4498
+ display: "flex",
4499
+ alignItems: "center",
4500
+ justifyContent: "center",
4501
+ flexShrink: 0
4502
+ },
4503
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { size: "1.4vh", color })
4504
+ }
4505
+ ),
4506
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { minWidth: 0, lineHeight: 1 }, children: [
4507
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4508
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.08em", c: color, truncate: true, children: subtitle })
4509
+ ] })
4432
4510
  ] }),
4433
4511
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { gap: "xxs", px: "xs", py: "xs", style: { borderBottom: `0.1vh solid ${core.alpha(theme2.colors.dark[6], 0.4)}`, flexShrink: 0 }, children: [
4434
4512
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4531,7 +4609,7 @@ function SettingsPanelInner({
4531
4609
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
4532
4610
  framerMotion.motion.div,
4533
4611
  {
4534
- initial: { opacity: 0, y: 4 },
4612
+ initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4535
4613
  animate: { opacity: 1, y: 0 },
4536
4614
  exit: { opacity: 0, y: -4 },
4537
4615
  transition: { duration: 0.15 },
@@ -4545,15 +4623,15 @@ function SettingsPanelInner({
4545
4623
  )
4546
4624
  ] });
4547
4625
  }
4548
- function cloneSettings(value) {
4626
+ function cloneConfig(value) {
4549
4627
  return JSON.parse(JSON.stringify(value));
4550
4628
  }
4551
4629
  function ServerOnlyFetcher() {
4552
- const { fetchSettings } = getScriptSettingsInstance();
4630
+ const { fetchConfig } = getScriptConfigInstance();
4553
4631
  const { reinitialize } = useFormActions();
4554
4632
  React5.useEffect(() => {
4555
4633
  let cancelled = false;
4556
- fetchSettings().then((full) => {
4634
+ fetchConfig().then((full) => {
4557
4635
  if (!cancelled && full) reinitialize(full);
4558
4636
  }).catch(() => {
4559
4637
  });
@@ -4564,28 +4642,28 @@ function ServerOnlyFetcher() {
4564
4642
  return null;
4565
4643
  }
4566
4644
  var defaultOnClose = () => fetchNui("CLOSE_ADMIN_SECTION");
4567
- function SettingsPanel(props) {
4645
+ function ConfigPanel(props) {
4568
4646
  const { open, onClose = defaultOnClose } = props;
4569
- const { store, updateSettings, fetchSettings } = getScriptSettingsInstance();
4647
+ const { store, updateConfig } = getScriptConfigInstance();
4570
4648
  const [isSaving, setIsSaving] = React5.useState(false);
4571
4649
  if (!open) return null;
4572
- return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: settingsPanelQueryClient, children: /* @__PURE__ */ jsxRuntime.jsxs(
4650
+ return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: configPanelQueryClient, children: /* @__PURE__ */ jsxRuntime.jsxs(
4573
4651
  FormProvider,
4574
4652
  {
4575
- initialValues: cloneSettings(store.getState()),
4653
+ initialValues: cloneConfig(store.getState()),
4576
4654
  onSubmit: async (form) => {
4577
4655
  if (isSaving) return;
4578
4656
  setIsSaving(true);
4579
4657
  try {
4580
- const result = await updateSettings(form.values);
4658
+ const result = await updateConfig(form.values);
4581
4659
  if (result?.success) {
4582
- form.reinitialize(cloneSettings(form.values));
4583
- settingsPanelQueryClient.invalidateQueries({ queryKey: ["scriptSettingsHistory"] });
4660
+ form.reinitialize(cloneConfig(form.values));
4661
+ configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
4584
4662
  return;
4585
4663
  }
4586
- form.reinitialize(cloneSettings(store.getState()));
4664
+ form.reinitialize(cloneConfig(store.getState()));
4587
4665
  if (result?._error) {
4588
- console.warn(`[SettingsPanel] settings save failed: ${result._error}`);
4666
+ console.warn(`[ConfigPanel] config save failed: ${result._error}`);
4589
4667
  }
4590
4668
  } finally {
4591
4669
  setIsSaving(false);
@@ -4594,7 +4672,7 @@ function SettingsPanel(props) {
4594
4672
  children: [
4595
4673
  /* @__PURE__ */ jsxRuntime.jsx(ServerOnlyFetcher, {}),
4596
4674
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: open && /* @__PURE__ */ jsxRuntime.jsx(
4597
- SettingsPanelInner,
4675
+ ConfigPanelInner,
4598
4676
  {
4599
4677
  ...props,
4600
4678
  onClose,
@@ -4802,6 +4880,160 @@ function AdminPageTitle(props) {
4802
4880
  /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", tt: "uppercase", lts: "0.1em", size: "sm", c: "rgba(255,255,255,0.6)", children: locale(props.title) })
4803
4881
  ] });
4804
4882
  }
4883
+ var loadPersistedState = (storageKey) => {
4884
+ try {
4885
+ const raw = localStorage.getItem(storageKey);
4886
+ return raw ? JSON.parse(raw) : {};
4887
+ } catch {
4888
+ return {};
4889
+ }
4890
+ };
4891
+ var savePersistedState = (storageKey, state) => {
4892
+ try {
4893
+ localStorage.setItem(storageKey, JSON.stringify(state));
4894
+ } catch {
4895
+ }
4896
+ };
4897
+ function TestBed({
4898
+ items,
4899
+ storageKey = "testbed:open-state",
4900
+ disablePersistence = false,
4901
+ title = "TestBed"
4902
+ }) {
4903
+ const [open, setOpen] = React5.useState(false);
4904
+ const itemsRef = React5.useRef(items);
4905
+ itemsRef.current = items;
4906
+ React5.useEffect(() => {
4907
+ if (!isEnvBrowser() || disablePersistence) return;
4908
+ const persisted = loadPersistedState(storageKey);
4909
+ itemsRef.current.forEach((item) => {
4910
+ const persistedValue = persisted[item.key];
4911
+ if (typeof persistedValue === "boolean" && persistedValue !== item.active) {
4912
+ item.onToggle(persistedValue);
4913
+ }
4914
+ });
4915
+ }, []);
4916
+ if (!isEnvBrowser()) return null;
4917
+ const toggle = (item) => {
4918
+ const next = !item.active;
4919
+ item.onToggle(next);
4920
+ if (!disablePersistence) {
4921
+ const persisted = loadPersistedState(storageKey);
4922
+ persisted[item.key] = next;
4923
+ savePersistedState(storageKey, persisted);
4924
+ }
4925
+ };
4926
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4927
+ "div",
4928
+ {
4929
+ style: {
4930
+ position: "fixed",
4931
+ top: "1vh",
4932
+ left: "1vh",
4933
+ zIndex: 2147483647,
4934
+ pointerEvents: "auto",
4935
+ fontSize: "1.4vh"
4936
+ },
4937
+ children: [
4938
+ /* @__PURE__ */ jsxRuntime.jsxs(
4939
+ core.Flex,
4940
+ {
4941
+ align: "center",
4942
+ gap: "xs",
4943
+ px: "sm",
4944
+ py: "xs",
4945
+ onClick: () => setOpen((v) => !v),
4946
+ style: {
4947
+ cursor: "pointer",
4948
+ background: "rgba(0,0,0,0.55)",
4949
+ backdropFilter: "blur(0.6vh)",
4950
+ WebkitBackdropFilter: "blur(0.6vh)",
4951
+ border: "0.1vh solid rgba(255,255,255,0.1)",
4952
+ borderRadius: "var(--mantine-radius-sm)",
4953
+ userSelect: "none",
4954
+ minWidth: "16vh"
4955
+ },
4956
+ children: [
4957
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FlaskConical, { size: 14, color: "rgba(255,255,255,0.7)" }),
4958
+ /* @__PURE__ */ jsxRuntime.jsx(
4959
+ core.Text,
4960
+ {
4961
+ size: "xs",
4962
+ ff: "Akrobat Bold",
4963
+ tt: "uppercase",
4964
+ lts: "0.08em",
4965
+ c: "rgba(255,255,255,0.85)",
4966
+ style: { flex: 1 },
4967
+ children: title
4968
+ }
4969
+ ),
4970
+ /* @__PURE__ */ jsxRuntime.jsx(core.ActionIcon, { size: "xs", variant: "transparent", c: "rgba(255,255,255,0.6)", children: open ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { size: 14 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 14 }) })
4971
+ ]
4972
+ }
4973
+ ),
4974
+ open && /* @__PURE__ */ jsxRuntime.jsx(
4975
+ core.Stack,
4976
+ {
4977
+ gap: 4,
4978
+ mt: "xxs",
4979
+ p: "xs",
4980
+ style: {
4981
+ background: "rgba(0,0,0,0.55)",
4982
+ backdropFilter: "blur(0.6vh)",
4983
+ WebkitBackdropFilter: "blur(0.6vh)",
4984
+ border: "0.1vh solid rgba(255,255,255,0.1)",
4985
+ borderRadius: "var(--mantine-radius-sm)",
4986
+ minWidth: "16vh",
4987
+ maxHeight: "80vh",
4988
+ overflowY: "auto"
4989
+ },
4990
+ children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
4991
+ core.Flex,
4992
+ {
4993
+ align: "center",
4994
+ justify: "space-between",
4995
+ gap: "xs",
4996
+ px: "xs",
4997
+ py: "xxs",
4998
+ onClick: () => toggle(item),
4999
+ style: {
5000
+ cursor: "pointer",
5001
+ borderRadius: "var(--mantine-radius-xs)",
5002
+ background: item.active ? "rgba(245,158,11,0.15)" : "rgba(255,255,255,0.03)",
5003
+ border: `0.1vh solid ${item.active ? "rgba(245,158,11,0.35)" : "rgba(255,255,255,0.05)"}`,
5004
+ userSelect: "none"
5005
+ },
5006
+ children: [
5007
+ /* @__PURE__ */ jsxRuntime.jsx(
5008
+ core.Text,
5009
+ {
5010
+ size: "xs",
5011
+ ff: "Akrobat Bold",
5012
+ c: item.active ? "#f59e0b" : "rgba(255,255,255,0.75)",
5013
+ children: item.label
5014
+ }
5015
+ ),
5016
+ /* @__PURE__ */ jsxRuntime.jsx(
5017
+ core.Text,
5018
+ {
5019
+ size: "xxs",
5020
+ ff: "Akrobat Bold",
5021
+ tt: "uppercase",
5022
+ lts: "0.06em",
5023
+ c: item.active ? "#f59e0b" : "rgba(255,255,255,0.35)",
5024
+ children: item.active ? "On" : "Off"
5025
+ }
5026
+ )
5027
+ ]
5028
+ },
5029
+ item.key
5030
+ ))
5031
+ }
5032
+ )
5033
+ ]
5034
+ }
5035
+ );
5036
+ }
4805
5037
  function useTornEdges() {
4806
5038
  const game = useSettings((state) => state.game);
4807
5039
  return game === "rdr3" ? "torn-edge-wrapper" : "";
@@ -4982,6 +5214,11 @@ function mergeMantineThemeSafe(base, custom, override) {
4982
5214
  const colors = { ...base.colors };
4983
5215
  if (custom && isValidColorScale(custom)) {
4984
5216
  colors["custom"] = custom;
5217
+ } else if (!colors["custom"]) {
5218
+ const fallback = base.colors && base.colors.dirk;
5219
+ if (fallback && isValidColorScale(fallback)) {
5220
+ colors["custom"] = fallback;
5221
+ }
4985
5222
  }
4986
5223
  return {
4987
5224
  ...base,
@@ -5055,6 +5292,7 @@ function DirkProvider({ children, overideResourceName, themeOverride }) {
5055
5292
  customTheme,
5056
5293
  game
5057
5294
  } = useSettings();
5295
+ localeStore((s) => s.locales);
5058
5296
  React5.useLayoutEffect(() => {
5059
5297
  useSettings.setState({
5060
5298
  overideResourceName
@@ -5075,6 +5313,10 @@ function DirkProvider({ children, overideResourceName, themeOverride }) {
5075
5313
  console.error("Failed to fetch initial settings within dirk-cfx-react:", err);
5076
5314
  });
5077
5315
  }, []);
5316
+ useNuiEvent("UPDATE_DIRK_LIB_SETTINGS", (data) => {
5317
+ if (!data || typeof data !== "object") return;
5318
+ useSettings.setState(data);
5319
+ });
5078
5320
  const mergedTheme = React5.useMemo(
5079
5321
  () => mergeMantineThemeSafe(
5080
5322
  { ...theme_default, primaryColor, primaryShade },
@@ -5103,6 +5345,7 @@ exports.AsyncSaveButton = AsyncSaveButton;
5103
5345
  exports.BlipColorSelect = BlipColorSelect;
5104
5346
  exports.BlipIconSelect = BlipIconSelect;
5105
5347
  exports.BorderedIcon = BorderedIcon;
5348
+ exports.ConfigPanel = ConfigPanel;
5106
5349
  exports.ConfirmModal = ConfirmModal;
5107
5350
  exports.Counter = Counter;
5108
5351
  exports.DirkProvider = DirkProvider;
@@ -5129,13 +5372,13 @@ exports.PromptModal = PromptModal;
5129
5372
  exports.SegmentedControl = SegmentedControl;
5130
5373
  exports.SegmentedProgress = SegmentedProgress;
5131
5374
  exports.SelectItem = SelectItem;
5132
- exports.SettingsPanel = SettingsPanel;
5375
+ exports.TestBed = TestBed;
5133
5376
  exports.Title = Title;
5134
5377
  exports.TornEdgeSVGFilter = TornEdgeSVGFilter;
5135
5378
  exports.colorWithAlpha = colorWithAlpha;
5136
5379
  exports.copyToClipboard = copyToClipboard;
5137
5380
  exports.createFormStore = createFormStore;
5138
- exports.createScriptSettings = createScriptSettings;
5381
+ exports.createScriptConfig = createScriptConfig;
5139
5382
  exports.createSkill = createSkill;
5140
5383
  exports.extractDefaults = extractDefaults;
5141
5384
  exports.fetchLuaTable = fetchLuaTable;
@@ -5143,7 +5386,7 @@ exports.fetchNui = fetchNui;
5143
5386
  exports.gameToMap = gameToMap;
5144
5387
  exports.getImageShape = getImageShape;
5145
5388
  exports.getItemImageUrl = getItemImageUrl;
5146
- exports.getScriptSettingsInstance = getScriptSettingsInstance;
5389
+ exports.getScriptConfigInstance = getScriptConfigInstance;
5147
5390
  exports.initialFetches = initialFetches;
5148
5391
  exports.internalEvent = internalEvent;
5149
5392
  exports.isEnvBrowser = isEnvBrowser;