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.js CHANGED
@@ -1,13 +1,13 @@
1
- import { Flex, Text, Image as Image$1, createTheme, Box, Stack, Title as Title$1, Code, TextInput, Select, useMantineTheme, alpha, Progress, RingProgress, Portal, Button, Loader, MantineProvider, BackgroundImage, Group, JsonInput } from '@mantine/core';
1
+ import { Flex, Text, Image as Image$1, createTheme, Box, Stack, Title as Title$1, Code, TextInput, Select, useMantineTheme, Tooltip, alpha, Progress, RingProgress, Portal, Button, Loader, ActionIcon, MantineProvider, BackgroundImage, Group, JsonInput } from '@mantine/core';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import React5, { createContext, useContext, useEffect, useRef, useState, useMemo, useLayoutEffect } from 'react';
4
4
  import { create, useStore, createStore } from 'zustand';
5
5
  import axios from 'axios';
6
6
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7
7
  import { motion, AnimatePresence, useMotionValue } from 'framer-motion';
8
+ import { Info, X, AlertTriangle, Trash2, Check, FlaskConical, ChevronUp, ChevronDown, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, RotateCcw, Search, Filter, User } from 'lucide-react';
8
9
  import clickSoundUrl from './click_sound-PNCRRTM4.mp3';
9
10
  import hoverSoundUrl from './hover_sound-NBUA222C.mp3';
10
- import { X, AlertTriangle, Trash2, Check, Undo2, Redo2, Save, History, XCircle, Code2, RotateCcw, Search, Filter, User, ChevronDown } from 'lucide-react';
11
11
  import { QueryClient, QueryClientProvider, useInfiniteQuery } from '@tanstack/react-query';
12
12
  import '@mantine/core/styles.css';
13
13
  import '@mantine/notifications/styles.css';
@@ -1306,8 +1306,12 @@ async function fetchNui(eventName, data, mockData) {
1306
1306
  }
1307
1307
  const overrideResourceName = useSettings.getState().overideResourceName;
1308
1308
  const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName ? overrideResourceName : "dirk-cfx-react";
1309
- const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1310
- return await resp.json();
1309
+ try {
1310
+ const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1311
+ return await resp.json();
1312
+ } catch {
1313
+ return mockData ?? {};
1314
+ }
1311
1315
  }
1312
1316
  var initialFetches = {};
1313
1317
  async function registerInitialFetch(eventName, data, mockData) {
@@ -1326,10 +1330,8 @@ async function runFetches() {
1326
1330
  var useAutoFetcher = () => {
1327
1331
  useEffect(() => {
1328
1332
  if (isEnvBrowser()) return;
1329
- const run = async () => {
1330
- await runFetches();
1331
- };
1332
- run();
1333
+ runFetches().catch(() => {
1334
+ });
1333
1335
  }, []);
1334
1336
  };
1335
1337
  var fetchLuaTable = (tableName, mockData) => {
@@ -1356,6 +1358,13 @@ var internalEvent = (events, timer = 1e3) => {
1356
1358
  }
1357
1359
  }
1358
1360
  };
1361
+ var reportedMissing = /* @__PURE__ */ new Set();
1362
+ function reportMissingLocale(key) {
1363
+ if (!key || reportedMissing.has(key)) return;
1364
+ reportedMissing.add(key);
1365
+ fetchNui("REPORT_MISSING_LOCALE", { key }).catch(() => {
1366
+ });
1367
+ }
1359
1368
  var localeStore = create((set, get) => {
1360
1369
  return {
1361
1370
  locales: {
@@ -1363,6 +1372,7 @@ var localeStore = create((set, get) => {
1363
1372
  },
1364
1373
  locale: (key, ...args) => {
1365
1374
  const exists = get().locales[key];
1375
+ if (!exists) reportMissingLocale(key);
1366
1376
  let translation = exists || key;
1367
1377
  if (args.length) {
1368
1378
  translation = translation.replace(/%s/g, () => String(args.shift() || ""));
@@ -1374,7 +1384,16 @@ var localeStore = create((set, get) => {
1374
1384
  var locale = localeStore.getState().locale;
1375
1385
  registerInitialFetch("GET_LOCALES", void 0).then((data) => {
1376
1386
  localeStore.setState({ locales: data });
1387
+ }).catch(() => {
1377
1388
  });
1389
+ if (typeof window !== "undefined") {
1390
+ window.addEventListener("message", (event) => {
1391
+ const msg = event.data;
1392
+ if (!msg || msg.action !== "UPDATE_DIRK_LIB_LOCALES") return;
1393
+ if (!msg.data || typeof msg.data !== "object") return;
1394
+ localeStore.setState({ locales: msg.data });
1395
+ });
1396
+ }
1378
1397
 
1379
1398
  // src/utils/map.ts
1380
1399
  var mapCenter = [-119.43, 58.84];
@@ -1891,6 +1910,7 @@ registerInitialFetch("FETCH_ALL_ITEMS", null, {
1891
1910
  }).then((fetchedItems) => {
1892
1911
  if (!fetchedItems) return;
1893
1912
  useItems.setState(fetchedItems);
1913
+ }).catch(() => {
1894
1914
  });
1895
1915
 
1896
1916
  // src/utils/inputMapper.ts
@@ -2536,8 +2556,8 @@ function InputContainer(props) {
2536
2556
  (props.title || props.description) && /* @__PURE__ */ jsxs(
2537
2557
  Flex,
2538
2558
  {
2539
- direction: "column",
2540
- gap: "xxs",
2559
+ align: "center",
2560
+ flex: 1,
2541
2561
  p: props.p == "0" ? "sm" : 0,
2542
2562
  children: [
2543
2563
  props.title && /* @__PURE__ */ jsx(
@@ -2554,12 +2574,26 @@ function InputContainer(props) {
2554
2574
  }
2555
2575
  ),
2556
2576
  props.description && /* @__PURE__ */ jsx(
2557
- Text,
2577
+ Tooltip,
2558
2578
  {
2559
- size: "xs",
2560
- c: "rgba(255, 255, 255, 0.8)",
2561
- fw: 400,
2562
- children: props.description
2579
+ label: props.description,
2580
+ position: "top-end",
2581
+ withArrow: true,
2582
+ multiline: true,
2583
+ maw: "22vh",
2584
+ styles: {
2585
+ tooltip: {
2586
+ background: alpha(theme2.colors.dark[7], 0.95),
2587
+ border: `0.1vh solid rgba(255,255,255,0.1)`,
2588
+ color: "rgba(255,255,255,0.75)",
2589
+ fontFamily: "Akrobat Bold",
2590
+ fontSize: "1.3vh",
2591
+ lineHeight: 1.3,
2592
+ padding: "0.6vh 0.8vh",
2593
+ letterSpacing: "0.03em"
2594
+ }
2595
+ },
2596
+ children: /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", style: { marginLeft: "auto", cursor: "help" }, children: /* @__PURE__ */ jsx(Info, { size: "1.6vh", color: alpha(theme2.colors[theme2.primaryColor][5], 0.45) }) })
2563
2597
  }
2564
2598
  )
2565
2599
  ]
@@ -2576,13 +2610,7 @@ function InputContainer(props) {
2576
2610
  children: props.error
2577
2611
  }
2578
2612
  ),
2579
- /* @__PURE__ */ jsx(
2580
- Flex,
2581
- {
2582
- ml: "auto",
2583
- children: props.rightSection
2584
- }
2585
- )
2613
+ props.rightSection && /* @__PURE__ */ jsx(Flex, { children: props.rightSection })
2586
2614
  ]
2587
2615
  }
2588
2616
  ),
@@ -3273,7 +3301,7 @@ function Modal({
3273
3301
  children: description2
3274
3302
  }
3275
3303
  ) }),
3276
- children
3304
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }, children })
3277
3305
  ]
3278
3306
  }
3279
3307
  )
@@ -3955,42 +3983,42 @@ var useNuiEvent = (action, handler) => {
3955
3983
  }, [action]);
3956
3984
  };
3957
3985
  var _instance = null;
3958
- function getScriptSettingsInstance() {
3959
- if (!_instance) throw new Error("[dirk-cfx-react] createScriptSettings must be called before using SettingsPanel");
3986
+ function getScriptConfigInstance() {
3987
+ if (!_instance) throw new Error("[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel");
3960
3988
  return _instance;
3961
3989
  }
3962
- function createScriptSettings(defaultValue) {
3990
+ function createScriptConfig(defaultValue) {
3963
3991
  const store = create(() => defaultValue);
3964
3992
  let clientVersion = 0;
3965
- const useScriptSettingHooks = () => {
3966
- useNuiEvent("UPDATE_SCRIPT_SETTINGS", (data) => {
3993
+ const useScriptConfigHooks = () => {
3994
+ useNuiEvent("UPDATE_SCRIPT_CONFIG", (data) => {
3967
3995
  if (!data) return;
3968
3996
  if (typeof data.clientVersion === "number") {
3969
3997
  clientVersion = data.clientVersion;
3970
3998
  }
3971
- if (data.settings && typeof data.settings === "object") {
3972
- store.setState((prev) => ({ ...prev, ...data.settings }));
3999
+ if (data.config && typeof data.config === "object") {
4000
+ store.setState((prev) => ({ ...prev, ...data.config }));
3973
4001
  }
3974
4002
  });
3975
4003
  };
3976
- const fetchScriptSettings = async () => {
4004
+ const fetchScriptConfig = async () => {
3977
4005
  try {
3978
- const response = await fetchNui("GET_FULL_SCRIPT_SETTINGS");
3979
- if (response?.success && response.data?.settings) {
3980
- store.setState(() => response.data.settings);
4006
+ const response = await fetchNui("GET_FULL_SCRIPT_CONFIG");
4007
+ if (response?.success && response.data?.config) {
4008
+ store.setState(() => response.data.config);
3981
4009
  if (typeof response.data.clientVersion === "number") {
3982
4010
  clientVersion = response.data.clientVersion;
3983
4011
  }
3984
- return response.data.settings;
4012
+ return response.data.config;
3985
4013
  }
3986
4014
  } catch {
3987
4015
  }
3988
4016
  return null;
3989
4017
  };
3990
- const updateScriptSettings = async (newSettings) => {
3991
- store.setState((prev) => ({ ...prev, ...newSettings }));
3992
- const response = await fetchNui("UPDATE_SCRIPT_SETTINGS", {
3993
- data: newSettings,
4018
+ const updateScriptConfig = async (newConfig) => {
4019
+ store.setState((prev) => ({ ...prev, ...newConfig }));
4020
+ const response = await fetchNui("UPDATE_SCRIPT_CONFIG", {
4021
+ data: newConfig,
3994
4022
  expectedVersion: clientVersion
3995
4023
  });
3996
4024
  if (response?.meta?.client_version != null) {
@@ -4001,18 +4029,29 @@ function createScriptSettings(defaultValue) {
4001
4029
  }
4002
4030
  return response;
4003
4031
  };
4004
- const getScriptSettingsHistory = async (params = {}) => {
4005
- return fetchNui("GET_SCRIPT_SETTINGS_HISTORY", params);
4032
+ const getScriptConfigHistory = async (params = {}) => {
4033
+ return fetchNui("GET_SCRIPT_CONFIG_HISTORY", params);
4034
+ };
4035
+ const resetConfig = async () => {
4036
+ const response = await fetchNui("RESET_SCRIPT_CONFIG");
4037
+ if (response?.success) {
4038
+ const fresh = await fetchScriptConfig();
4039
+ if (fresh) {
4040
+ store.setState(() => fresh);
4041
+ }
4042
+ }
4043
+ return response;
4006
4044
  };
4007
4045
  _instance = {
4008
4046
  store,
4009
- updateSettings: updateScriptSettings,
4010
- getHistory: getScriptSettingsHistory,
4011
- fetchSettings: fetchScriptSettings
4047
+ updateConfig: updateScriptConfig,
4048
+ resetConfig,
4049
+ getHistory: getScriptConfigHistory,
4050
+ fetchConfig: fetchScriptConfig
4012
4051
  };
4013
- return { store, updateScriptSettings, getScriptSettingsHistory, useScriptSettingHooks, fetchScriptSettings };
4052
+ return { store, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig };
4014
4053
  }
4015
- var settingsPanelQueryClient = new QueryClient({
4054
+ var configPanelQueryClient = new QueryClient({
4016
4055
  defaultOptions: { queries: { staleTime: 3e4, gcTime: 5 * 6e4 } }
4017
4056
  });
4018
4057
  function NavItemButton({
@@ -4056,7 +4095,7 @@ function NavItemButton({
4056
4095
  }
4057
4096
  );
4058
4097
  }
4059
- function SettingsJsonModal({
4098
+ function ConfigJsonModal({
4060
4099
  onClose,
4061
4100
  schema
4062
4101
  }) {
@@ -4093,7 +4132,7 @@ function SettingsJsonModal({
4093
4132
  setError(e.message);
4094
4133
  }
4095
4134
  };
4096
- return /* @__PURE__ */ jsxs(Modal, { title: "Settings JSON", icon: Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
4135
+ return /* @__PURE__ */ jsxs(Modal, { title: "Config JSON", icon: Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
4097
4136
  /* @__PURE__ */ jsxs(Box, { flex: 1, p: "0.8vh", style: { overflowY: "auto" }, children: [
4098
4137
  /* @__PURE__ */ jsx(
4099
4138
  JsonInput,
@@ -4235,10 +4274,10 @@ function HistoryTableHeader() {
4235
4274
  /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.45)", children: "Version" })
4236
4275
  ] });
4237
4276
  }
4238
- function SettingsHistoryModal({
4277
+ function ConfigHistoryModal({
4239
4278
  onClose
4240
4279
  }) {
4241
- const { getHistory } = getScriptSettingsInstance();
4280
+ const { getHistory } = getScriptConfigInstance();
4242
4281
  const theme2 = useMantineTheme();
4243
4282
  const color = theme2.colors[theme2.primaryColor][5];
4244
4283
  const [queryInput, setQueryInput] = useState("");
@@ -4250,7 +4289,7 @@ function SettingsHistoryModal({
4250
4289
  const [expandedKey, setExpandedKey] = useState(null);
4251
4290
  const filters = useMemo(() => ({ query, path, admin }), [query, path, admin]);
4252
4291
  const historyQuery = useInfiniteQuery({
4253
- queryKey: ["scriptSettingsHistory", filters],
4292
+ queryKey: ["scriptConfigHistory", filters],
4254
4293
  initialPageParam: 0,
4255
4294
  queryFn: async ({ pageParam }) => {
4256
4295
  const response = await getHistory({
@@ -4261,7 +4300,7 @@ function SettingsHistoryModal({
4261
4300
  admin: filters.admin || void 0
4262
4301
  });
4263
4302
  if (!response?.success || !response.data) {
4264
- throw new Error(response?._error || "Failed to load settings history");
4303
+ throw new Error(response?._error || "Failed to load config history");
4265
4304
  }
4266
4305
  return response.data;
4267
4306
  },
@@ -4275,7 +4314,7 @@ function SettingsHistoryModal({
4275
4314
  historyQuery.fetchNextPage();
4276
4315
  }
4277
4316
  };
4278
- return /* @__PURE__ */ jsxs(Modal, { title: "Settings History", icon: History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
4317
+ return /* @__PURE__ */ jsxs(Modal, { title: "Config History", icon: History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
4279
4318
  /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minHeight: 0 }, children: [
4280
4319
  /* @__PURE__ */ jsxs(Flex, { gap: "xs", p: "sm", style: { borderBottom: `0.1vh solid ${alpha(theme2.colors.dark[7], 0.8)}` }, children: [
4281
4320
  /* @__PURE__ */ jsx(TextInput, { leftSection: /* @__PURE__ */ jsx(Search, { size: "1.4vh" }), placeholder: "Search path/admin/value", value: queryInput, onChange: (e) => setQueryInput(e.currentTarget.value), size: "xs", style: { flex: 1 } }),
@@ -4324,7 +4363,7 @@ function SettingsHistoryModal({
4324
4363
  ) })
4325
4364
  ] });
4326
4365
  }
4327
- function SettingsPanelInner({
4366
+ function ConfigPanelInner({
4328
4367
  navItems,
4329
4368
  title,
4330
4369
  subtitle,
@@ -4333,28 +4372,37 @@ function SettingsPanelInner({
4333
4372
  onClose,
4334
4373
  schema,
4335
4374
  resetConfirmText,
4336
- defaultSettings,
4375
+ defaultConfig,
4337
4376
  width,
4338
4377
  height
4339
4378
  }) {
4340
- const { updateSettings, getHistory } = getScriptSettingsInstance();
4379
+ const { updateConfig, resetConfig, getHistory } = getScriptConfigInstance();
4341
4380
  const form = useForm();
4342
4381
  const theme2 = useMantineTheme();
4343
4382
  const color = theme2.colors[theme2.primaryColor][5];
4344
4383
  const version = useSettings((s) => s.resourceVersion);
4345
4384
  const [activeTab, setActiveTab] = useState(navItems[0]?.id ?? "");
4385
+ const firstMountRef = useRef(true);
4346
4386
  const [jsonOpen, setJsonOpen] = useState(false);
4347
4387
  const [historyOpen, setHistoryOpen] = useState(false);
4348
4388
  const [resetOpen, setResetOpen] = useState(false);
4349
- const [closeConfirmOpen, setCloseConfirmOpen] = useState(false);
4389
+ const [pendingAction, setPendingAction] = useState(null);
4350
4390
  const changedCount = form.changedCount ?? 0;
4351
4391
  const isDirty = changedCount > 0;
4392
+ const goBack = () => fetchNui("CONFIG_PANEL_BACK");
4393
+ const handleBack = () => {
4394
+ if (isDirty) {
4395
+ setPendingAction("back");
4396
+ return;
4397
+ }
4398
+ goBack();
4399
+ };
4352
4400
  useEffect(() => {
4353
4401
  function handleKeyDown(e) {
4354
4402
  if (e.key !== "Escape") return;
4355
4403
  if (isDirty) {
4356
4404
  e.preventDefault();
4357
- setCloseConfirmOpen(true);
4405
+ setPendingAction("close");
4358
4406
  return;
4359
4407
  }
4360
4408
  onClose();
@@ -4363,34 +4411,40 @@ function SettingsPanelInner({
4363
4411
  return () => window.removeEventListener("keydown", handleKeyDown);
4364
4412
  }, [isDirty, onClose]);
4365
4413
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4366
- /* @__PURE__ */ jsx(AnimatePresence, { children: jsonOpen && /* @__PURE__ */ jsx(SettingsJsonModal, { onClose: () => setJsonOpen(false), schema }) }),
4367
- /* @__PURE__ */ jsx(AnimatePresence, { children: historyOpen && /* @__PURE__ */ jsx(SettingsHistoryModal, { onClose: () => setHistoryOpen(false) }) }),
4414
+ /* @__PURE__ */ jsx(AnimatePresence, { children: jsonOpen && /* @__PURE__ */ jsx(ConfigJsonModal, { onClose: () => setJsonOpen(false), schema }) }),
4415
+ /* @__PURE__ */ jsx(AnimatePresence, { children: historyOpen && /* @__PURE__ */ jsx(ConfigHistoryModal, { onClose: () => setHistoryOpen(false) }) }),
4368
4416
  /* @__PURE__ */ jsx(AnimatePresence, { children: resetOpen && /* @__PURE__ */ jsx(
4369
4417
  ConfirmModal,
4370
4418
  {
4371
4419
  title: "Reset to Defaults",
4372
- description: "This will permanently reset ALL settings back to their defaults. Every setting you have configured will be overwritten. This cannot be undone.",
4373
- confirmLabel: "Reset Settings",
4420
+ description: "This will permanently reset ALL config back to the defaults. Every value you have configured will be overwritten. This cannot be undone.",
4421
+ confirmLabel: "Reset Config",
4374
4422
  confirmText: resetConfirmText,
4375
- onConfirm: () => {
4376
- updateSettings(defaultSettings).then(() => form.reinitialize(cloneSettings(defaultSettings)));
4423
+ onConfirm: async () => {
4377
4424
  setResetOpen(false);
4425
+ const result = await resetConfig();
4426
+ if (result?.success) {
4427
+ const { store } = getScriptConfigInstance();
4428
+ form.reinitialize(cloneConfig(store.getState()));
4429
+ }
4378
4430
  },
4379
4431
  onClose: () => setResetOpen(false),
4380
4432
  zIndex: 300
4381
4433
  }
4382
4434
  ) }),
4383
- /* @__PURE__ */ jsx(AnimatePresence, { children: closeConfirmOpen && /* @__PURE__ */ jsx(
4435
+ /* @__PURE__ */ jsx(AnimatePresence, { children: pendingAction !== null && /* @__PURE__ */ jsx(
4384
4436
  ConfirmModal,
4385
4437
  {
4386
4438
  title: "Discard Unsaved Changes?",
4387
- description: "You have unsaved changes. Closing now will discard them.",
4388
- confirmLabel: "Close Without Saving",
4439
+ description: pendingAction === "back" ? "You have unsaved changes. Going back now will discard them." : "You have unsaved changes. Closing now will discard them.",
4440
+ confirmLabel: pendingAction === "back" ? "Go Back Without Saving" : "Close Without Saving",
4389
4441
  onConfirm: () => {
4390
- setCloseConfirmOpen(false);
4391
- onClose();
4442
+ const action = pendingAction;
4443
+ setPendingAction(null);
4444
+ if (action === "back") goBack();
4445
+ else onClose();
4392
4446
  },
4393
- onClose: () => setCloseConfirmOpen(false),
4447
+ onClose: () => setPendingAction(null),
4394
4448
  zIndex: 300
4395
4449
  }
4396
4450
  ) }),
@@ -4417,9 +4471,33 @@ function SettingsPanelInner({
4417
4471
  exit: { scale: 0.3, opacity: 0, transform: "translate(-50%, -50%)" },
4418
4472
  children: [
4419
4473
  /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { width: "18vh", flexShrink: 0, borderRight: `0.1vh solid ${alpha(theme2.colors.dark[6], 0.8)}`, background: alpha(theme2.colors.dark[8], 0.6), overflow: "hidden" }, children: [
4420
- /* @__PURE__ */ jsxs(Flex, { align: "baseline", gap: "0.3vh", px: "sm", py: "sm", style: { borderBottom: `0.1vh solid ${alpha(theme2.colors.dark[6], 0.5)}`, flexShrink: 0 }, children: [
4421
- /* @__PURE__ */ jsx(Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", children: title }),
4422
- subtitle && /* @__PURE__ */ jsx(Text, { tt: "uppercase", fw: 600, c: color, children: subtitle })
4474
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "0.6vh", px: "sm", py: "sm", style: { borderBottom: `0.1vh solid ${alpha(theme2.colors.dark[6], 0.5)}`, flexShrink: 0 }, children: [
4475
+ /* @__PURE__ */ jsx(
4476
+ motion.button,
4477
+ {
4478
+ title: "Back to script list",
4479
+ onClick: handleBack,
4480
+ whileHover: { background: alpha(color, 0.16), borderColor: alpha(color, 0.45) },
4481
+ whileTap: { scale: 0.95 },
4482
+ style: {
4483
+ aspectRatio: "1 / 1",
4484
+ height: "2.4vh",
4485
+ background: alpha(color, 0.08),
4486
+ border: `0.1vh solid ${alpha(color, 0.3)}`,
4487
+ borderRadius: theme2.radius.xs,
4488
+ cursor: "pointer",
4489
+ display: "flex",
4490
+ alignItems: "center",
4491
+ justifyContent: "center",
4492
+ flexShrink: 0
4493
+ },
4494
+ children: /* @__PURE__ */ jsx(ArrowLeft, { size: "1.4vh", color })
4495
+ }
4496
+ ),
4497
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { minWidth: 0, lineHeight: 1 }, children: [
4498
+ /* @__PURE__ */ jsx(Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4499
+ subtitle && /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.08em", c: color, truncate: true, children: subtitle })
4500
+ ] })
4423
4501
  ] }),
4424
4502
  /* @__PURE__ */ jsxs(Flex, { gap: "xxs", px: "xs", py: "xs", style: { borderBottom: `0.1vh solid ${alpha(theme2.colors.dark[6], 0.4)}`, flexShrink: 0 }, children: [
4425
4503
  /* @__PURE__ */ jsx(
@@ -4522,7 +4600,7 @@ function SettingsPanelInner({
4522
4600
  /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
4523
4601
  motion.div,
4524
4602
  {
4525
- initial: { opacity: 0, y: 4 },
4603
+ initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4526
4604
  animate: { opacity: 1, y: 0 },
4527
4605
  exit: { opacity: 0, y: -4 },
4528
4606
  transition: { duration: 0.15 },
@@ -4536,15 +4614,15 @@ function SettingsPanelInner({
4536
4614
  )
4537
4615
  ] });
4538
4616
  }
4539
- function cloneSettings(value) {
4617
+ function cloneConfig(value) {
4540
4618
  return JSON.parse(JSON.stringify(value));
4541
4619
  }
4542
4620
  function ServerOnlyFetcher() {
4543
- const { fetchSettings } = getScriptSettingsInstance();
4621
+ const { fetchConfig } = getScriptConfigInstance();
4544
4622
  const { reinitialize } = useFormActions();
4545
4623
  useEffect(() => {
4546
4624
  let cancelled = false;
4547
- fetchSettings().then((full) => {
4625
+ fetchConfig().then((full) => {
4548
4626
  if (!cancelled && full) reinitialize(full);
4549
4627
  }).catch(() => {
4550
4628
  });
@@ -4555,28 +4633,28 @@ function ServerOnlyFetcher() {
4555
4633
  return null;
4556
4634
  }
4557
4635
  var defaultOnClose = () => fetchNui("CLOSE_ADMIN_SECTION");
4558
- function SettingsPanel(props) {
4636
+ function ConfigPanel(props) {
4559
4637
  const { open, onClose = defaultOnClose } = props;
4560
- const { store, updateSettings, fetchSettings } = getScriptSettingsInstance();
4638
+ const { store, updateConfig } = getScriptConfigInstance();
4561
4639
  const [isSaving, setIsSaving] = useState(false);
4562
4640
  if (!open) return null;
4563
- return /* @__PURE__ */ jsx(QueryClientProvider, { client: settingsPanelQueryClient, children: /* @__PURE__ */ jsxs(
4641
+ return /* @__PURE__ */ jsx(QueryClientProvider, { client: configPanelQueryClient, children: /* @__PURE__ */ jsxs(
4564
4642
  FormProvider,
4565
4643
  {
4566
- initialValues: cloneSettings(store.getState()),
4644
+ initialValues: cloneConfig(store.getState()),
4567
4645
  onSubmit: async (form) => {
4568
4646
  if (isSaving) return;
4569
4647
  setIsSaving(true);
4570
4648
  try {
4571
- const result = await updateSettings(form.values);
4649
+ const result = await updateConfig(form.values);
4572
4650
  if (result?.success) {
4573
- form.reinitialize(cloneSettings(form.values));
4574
- settingsPanelQueryClient.invalidateQueries({ queryKey: ["scriptSettingsHistory"] });
4651
+ form.reinitialize(cloneConfig(form.values));
4652
+ configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
4575
4653
  return;
4576
4654
  }
4577
- form.reinitialize(cloneSettings(store.getState()));
4655
+ form.reinitialize(cloneConfig(store.getState()));
4578
4656
  if (result?._error) {
4579
- console.warn(`[SettingsPanel] settings save failed: ${result._error}`);
4657
+ console.warn(`[ConfigPanel] config save failed: ${result._error}`);
4580
4658
  }
4581
4659
  } finally {
4582
4660
  setIsSaving(false);
@@ -4585,7 +4663,7 @@ function SettingsPanel(props) {
4585
4663
  children: [
4586
4664
  /* @__PURE__ */ jsx(ServerOnlyFetcher, {}),
4587
4665
  /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(
4588
- SettingsPanelInner,
4666
+ ConfigPanelInner,
4589
4667
  {
4590
4668
  ...props,
4591
4669
  onClose,
@@ -4793,6 +4871,160 @@ function AdminPageTitle(props) {
4793
4871
  /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", tt: "uppercase", lts: "0.1em", size: "sm", c: "rgba(255,255,255,0.6)", children: locale(props.title) })
4794
4872
  ] });
4795
4873
  }
4874
+ var loadPersistedState = (storageKey) => {
4875
+ try {
4876
+ const raw = localStorage.getItem(storageKey);
4877
+ return raw ? JSON.parse(raw) : {};
4878
+ } catch {
4879
+ return {};
4880
+ }
4881
+ };
4882
+ var savePersistedState = (storageKey, state) => {
4883
+ try {
4884
+ localStorage.setItem(storageKey, JSON.stringify(state));
4885
+ } catch {
4886
+ }
4887
+ };
4888
+ function TestBed({
4889
+ items,
4890
+ storageKey = "testbed:open-state",
4891
+ disablePersistence = false,
4892
+ title = "TestBed"
4893
+ }) {
4894
+ const [open, setOpen] = useState(false);
4895
+ const itemsRef = useRef(items);
4896
+ itemsRef.current = items;
4897
+ useEffect(() => {
4898
+ if (!isEnvBrowser() || disablePersistence) return;
4899
+ const persisted = loadPersistedState(storageKey);
4900
+ itemsRef.current.forEach((item) => {
4901
+ const persistedValue = persisted[item.key];
4902
+ if (typeof persistedValue === "boolean" && persistedValue !== item.active) {
4903
+ item.onToggle(persistedValue);
4904
+ }
4905
+ });
4906
+ }, []);
4907
+ if (!isEnvBrowser()) return null;
4908
+ const toggle = (item) => {
4909
+ const next = !item.active;
4910
+ item.onToggle(next);
4911
+ if (!disablePersistence) {
4912
+ const persisted = loadPersistedState(storageKey);
4913
+ persisted[item.key] = next;
4914
+ savePersistedState(storageKey, persisted);
4915
+ }
4916
+ };
4917
+ return /* @__PURE__ */ jsxs(
4918
+ "div",
4919
+ {
4920
+ style: {
4921
+ position: "fixed",
4922
+ top: "1vh",
4923
+ left: "1vh",
4924
+ zIndex: 2147483647,
4925
+ pointerEvents: "auto",
4926
+ fontSize: "1.4vh"
4927
+ },
4928
+ children: [
4929
+ /* @__PURE__ */ jsxs(
4930
+ Flex,
4931
+ {
4932
+ align: "center",
4933
+ gap: "xs",
4934
+ px: "sm",
4935
+ py: "xs",
4936
+ onClick: () => setOpen((v) => !v),
4937
+ style: {
4938
+ cursor: "pointer",
4939
+ background: "rgba(0,0,0,0.55)",
4940
+ backdropFilter: "blur(0.6vh)",
4941
+ WebkitBackdropFilter: "blur(0.6vh)",
4942
+ border: "0.1vh solid rgba(255,255,255,0.1)",
4943
+ borderRadius: "var(--mantine-radius-sm)",
4944
+ userSelect: "none",
4945
+ minWidth: "16vh"
4946
+ },
4947
+ children: [
4948
+ /* @__PURE__ */ jsx(FlaskConical, { size: 14, color: "rgba(255,255,255,0.7)" }),
4949
+ /* @__PURE__ */ jsx(
4950
+ Text,
4951
+ {
4952
+ size: "xs",
4953
+ ff: "Akrobat Bold",
4954
+ tt: "uppercase",
4955
+ lts: "0.08em",
4956
+ c: "rgba(255,255,255,0.85)",
4957
+ style: { flex: 1 },
4958
+ children: title
4959
+ }
4960
+ ),
4961
+ /* @__PURE__ */ jsx(ActionIcon, { size: "xs", variant: "transparent", c: "rgba(255,255,255,0.6)", children: open ? /* @__PURE__ */ jsx(ChevronUp, { size: 14 }) : /* @__PURE__ */ jsx(ChevronDown, { size: 14 }) })
4962
+ ]
4963
+ }
4964
+ ),
4965
+ open && /* @__PURE__ */ jsx(
4966
+ Stack,
4967
+ {
4968
+ gap: 4,
4969
+ mt: "xxs",
4970
+ p: "xs",
4971
+ style: {
4972
+ background: "rgba(0,0,0,0.55)",
4973
+ backdropFilter: "blur(0.6vh)",
4974
+ WebkitBackdropFilter: "blur(0.6vh)",
4975
+ border: "0.1vh solid rgba(255,255,255,0.1)",
4976
+ borderRadius: "var(--mantine-radius-sm)",
4977
+ minWidth: "16vh",
4978
+ maxHeight: "80vh",
4979
+ overflowY: "auto"
4980
+ },
4981
+ children: items.map((item) => /* @__PURE__ */ jsxs(
4982
+ Flex,
4983
+ {
4984
+ align: "center",
4985
+ justify: "space-between",
4986
+ gap: "xs",
4987
+ px: "xs",
4988
+ py: "xxs",
4989
+ onClick: () => toggle(item),
4990
+ style: {
4991
+ cursor: "pointer",
4992
+ borderRadius: "var(--mantine-radius-xs)",
4993
+ background: item.active ? "rgba(245,158,11,0.15)" : "rgba(255,255,255,0.03)",
4994
+ border: `0.1vh solid ${item.active ? "rgba(245,158,11,0.35)" : "rgba(255,255,255,0.05)"}`,
4995
+ userSelect: "none"
4996
+ },
4997
+ children: [
4998
+ /* @__PURE__ */ jsx(
4999
+ Text,
5000
+ {
5001
+ size: "xs",
5002
+ ff: "Akrobat Bold",
5003
+ c: item.active ? "#f59e0b" : "rgba(255,255,255,0.75)",
5004
+ children: item.label
5005
+ }
5006
+ ),
5007
+ /* @__PURE__ */ jsx(
5008
+ Text,
5009
+ {
5010
+ size: "xxs",
5011
+ ff: "Akrobat Bold",
5012
+ tt: "uppercase",
5013
+ lts: "0.06em",
5014
+ c: item.active ? "#f59e0b" : "rgba(255,255,255,0.35)",
5015
+ children: item.active ? "On" : "Off"
5016
+ }
5017
+ )
5018
+ ]
5019
+ },
5020
+ item.key
5021
+ ))
5022
+ }
5023
+ )
5024
+ ]
5025
+ }
5026
+ );
5027
+ }
4796
5028
  function useTornEdges() {
4797
5029
  const game = useSettings((state) => state.game);
4798
5030
  return game === "rdr3" ? "torn-edge-wrapper" : "";
@@ -4973,6 +5205,11 @@ function mergeMantineThemeSafe(base, custom, override) {
4973
5205
  const colors = { ...base.colors };
4974
5206
  if (custom && isValidColorScale(custom)) {
4975
5207
  colors["custom"] = custom;
5208
+ } else if (!colors["custom"]) {
5209
+ const fallback = base.colors && base.colors.dirk;
5210
+ if (fallback && isValidColorScale(fallback)) {
5211
+ colors["custom"] = fallback;
5212
+ }
4976
5213
  }
4977
5214
  return {
4978
5215
  ...base,
@@ -5046,6 +5283,7 @@ function DirkProvider({ children, overideResourceName, themeOverride }) {
5046
5283
  customTheme,
5047
5284
  game
5048
5285
  } = useSettings();
5286
+ localeStore((s) => s.locales);
5049
5287
  useLayoutEffect(() => {
5050
5288
  useSettings.setState({
5051
5289
  overideResourceName
@@ -5066,6 +5304,10 @@ function DirkProvider({ children, overideResourceName, themeOverride }) {
5066
5304
  console.error("Failed to fetch initial settings within dirk-cfx-react:", err);
5067
5305
  });
5068
5306
  }, []);
5307
+ useNuiEvent("UPDATE_DIRK_LIB_SETTINGS", (data) => {
5308
+ if (!data || typeof data !== "object") return;
5309
+ useSettings.setState(data);
5310
+ });
5069
5311
  const mergedTheme = useMemo(
5070
5312
  () => mergeMantineThemeSafe(
5071
5313
  { ...theme_default, primaryColor, primaryShade },
@@ -5089,6 +5331,6 @@ function DirkProvider({ children, overideResourceName, themeOverride }) {
5089
5331
  return /* @__PURE__ */ jsx(MantineProvider, { theme: mergedTheme, defaultColorScheme: "dark", children: /* @__PURE__ */ jsx(DirkErrorBoundary, { children: content }) });
5090
5332
  }
5091
5333
 
5092
- export { AdminPageTitle, AsyncSaveButton, BlipColorSelect, BlipIconSelect, BorderedIcon, ConfirmModal, Counter, DirkProvider, FiveMKeyBindInput, FloatingParticles, FormProvider, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, InfoBox, InputContainer, LevelBanner, LevelPanel, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PromptModal, SegmentedControl, SegmentedProgress, SelectItem, SettingsPanel, Title, TornEdgeSVGFilter, colorWithAlpha, copyToClipboard, createFormStore, createScriptSettings, createSkill, extractDefaults, fetchLuaTable, fetchNui, gameToMap, getImageShape, getItemImageUrl, getScriptSettingsInstance, initialFetches, internalEvent, isEnvBrowser, isProfanity, latPr100, locale, localeStore, mapCenter, mapToGame, noop, numberToRoman, openLink, registerInitialFetch, registerInitialLuaTableFetch, runFetches, splitFAString, updatePresignedURL, uploadImage, useAudio, useAutoFetcher, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useItems, useItemsList, useModal, useModalActions, useNavigation, useNavigationStore, useNuiEvent, useProfanityStore, useSettings, useTornEdges };
5334
+ export { AdminPageTitle, AsyncSaveButton, BlipColorSelect, BlipIconSelect, BorderedIcon, ConfigPanel, ConfirmModal, Counter, DirkProvider, FiveMKeyBindInput, FloatingParticles, FormProvider, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, InfoBox, InputContainer, LevelBanner, LevelPanel, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PromptModal, SegmentedControl, SegmentedProgress, SelectItem, TestBed, Title, TornEdgeSVGFilter, colorWithAlpha, copyToClipboard, createFormStore, createScriptConfig, createSkill, extractDefaults, fetchLuaTable, fetchNui, gameToMap, getImageShape, getItemImageUrl, getScriptConfigInstance, initialFetches, internalEvent, isEnvBrowser, isProfanity, latPr100, locale, localeStore, mapCenter, mapToGame, noop, numberToRoman, openLink, registerInitialFetch, registerInitialLuaTableFetch, runFetches, splitFAString, updatePresignedURL, uploadImage, useAudio, useAutoFetcher, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useItems, useItemsList, useModal, useModalActions, useNavigation, useNavigationStore, useNuiEvent, useProfanityStore, useSettings, useTornEdges };
5093
5335
  //# sourceMappingURL=index.js.map
5094
5336
  //# sourceMappingURL=index.js.map