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.
@@ -6,9 +6,9 @@ var react = require('react');
6
6
  var zustand = require('zustand');
7
7
  var reactFontawesome = require('@fortawesome/react-fontawesome');
8
8
  var framerMotion = require('framer-motion');
9
+ var lucideReact = require('lucide-react');
9
10
  var clickSoundUrl = require('../click_sound-PNCRRTM4.mp3');
10
11
  var hoverSoundUrl = require('../hover_sound-NBUA222C.mp3');
11
- var lucideReact = require('lucide-react');
12
12
  var reactQuery = require('@tanstack/react-query');
13
13
 
14
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -1274,13 +1274,24 @@ async function fetchNui(eventName, data, mockData) {
1274
1274
  }
1275
1275
  const overrideResourceName = useSettings.getState().overideResourceName;
1276
1276
  const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName ? overrideResourceName : "dirk-cfx-react";
1277
- const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1278
- return await resp.json();
1277
+ try {
1278
+ const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1279
+ return await resp.json();
1280
+ } catch {
1281
+ return mockData ?? {};
1282
+ }
1279
1283
  }
1280
1284
  async function registerInitialFetch(eventName, data, mockData) {
1281
1285
  const fetcher = () => fetchNui(eventName, data, mockData);
1282
1286
  return fetcher();
1283
1287
  }
1288
+ var reportedMissing = /* @__PURE__ */ new Set();
1289
+ function reportMissingLocale(key) {
1290
+ if (!key || reportedMissing.has(key)) return;
1291
+ reportedMissing.add(key);
1292
+ fetchNui("REPORT_MISSING_LOCALE", { key }).catch(() => {
1293
+ });
1294
+ }
1284
1295
  var localeStore = zustand.create((set, get) => {
1285
1296
  return {
1286
1297
  locales: {
@@ -1288,6 +1299,7 @@ var localeStore = zustand.create((set, get) => {
1288
1299
  },
1289
1300
  locale: (key, ...args) => {
1290
1301
  const exists = get().locales[key];
1302
+ if (!exists) reportMissingLocale(key);
1291
1303
  let translation = exists || key;
1292
1304
  if (args.length) {
1293
1305
  translation = translation.replace(/%s/g, () => String(args.shift() || ""));
@@ -1299,7 +1311,16 @@ var localeStore = zustand.create((set, get) => {
1299
1311
  var locale = localeStore.getState().locale;
1300
1312
  registerInitialFetch("GET_LOCALES", void 0).then((data) => {
1301
1313
  localeStore.setState({ locales: data });
1314
+ }).catch(() => {
1302
1315
  });
1316
+ if (typeof window !== "undefined") {
1317
+ window.addEventListener("message", (event) => {
1318
+ const msg = event.data;
1319
+ if (!msg || msg.action !== "UPDATE_DIRK_LIB_LOCALES") return;
1320
+ if (!msg.data || typeof msg.data !== "object") return;
1321
+ localeStore.setState({ locales: msg.data });
1322
+ });
1323
+ }
1303
1324
  var useItems = zustand.create(() => ({}));
1304
1325
  var useItemsList = (excludeItemNames = []) => {
1305
1326
  const excludeSet = new Set(excludeItemNames);
@@ -1312,6 +1333,7 @@ registerInitialFetch("FETCH_ALL_ITEMS", null, {
1312
1333
  }).then((fetchedItems) => {
1313
1334
  if (!fetchedItems) return;
1314
1335
  useItems.setState(fetchedItems);
1336
+ }).catch(() => {
1315
1337
  });
1316
1338
 
1317
1339
  // src/utils/inputMapper.ts
@@ -1938,8 +1960,8 @@ function InputContainer(props) {
1938
1960
  (props.title || props.description) && /* @__PURE__ */ jsxRuntime.jsxs(
1939
1961
  core.Flex,
1940
1962
  {
1941
- direction: "column",
1942
- gap: "xxs",
1963
+ align: "center",
1964
+ flex: 1,
1943
1965
  p: props.p == "0" ? "sm" : 0,
1944
1966
  children: [
1945
1967
  props.title && /* @__PURE__ */ jsxRuntime.jsx(
@@ -1956,12 +1978,26 @@ function InputContainer(props) {
1956
1978
  }
1957
1979
  ),
1958
1980
  props.description && /* @__PURE__ */ jsxRuntime.jsx(
1959
- core.Text,
1981
+ core.Tooltip,
1960
1982
  {
1961
- size: "xs",
1962
- c: "rgba(255, 255, 255, 0.8)",
1963
- fw: 400,
1964
- children: props.description
1983
+ label: props.description,
1984
+ position: "top-end",
1985
+ withArrow: true,
1986
+ multiline: true,
1987
+ maw: "22vh",
1988
+ styles: {
1989
+ tooltip: {
1990
+ background: core.alpha(theme.colors.dark[7], 0.95),
1991
+ border: `0.1vh solid rgba(255,255,255,0.1)`,
1992
+ color: "rgba(255,255,255,0.75)",
1993
+ fontFamily: "Akrobat Bold",
1994
+ fontSize: "1.3vh",
1995
+ lineHeight: 1.3,
1996
+ padding: "0.6vh 0.8vh",
1997
+ letterSpacing: "0.03em"
1998
+ }
1999
+ },
2000
+ 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(theme.colors[theme.primaryColor][5], 0.45) }) })
1965
2001
  }
1966
2002
  )
1967
2003
  ]
@@ -1978,13 +2014,7 @@ function InputContainer(props) {
1978
2014
  children: props.error
1979
2015
  }
1980
2016
  ),
1981
- /* @__PURE__ */ jsxRuntime.jsx(
1982
- core.Flex,
1983
- {
1984
- ml: "auto",
1985
- children: props.rightSection
1986
- }
1987
- )
2017
+ props.rightSection && /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { children: props.rightSection })
1988
2018
  ]
1989
2019
  }
1990
2020
  ),
@@ -2675,7 +2705,7 @@ function Modal({
2675
2705
  children: description
2676
2706
  }
2677
2707
  ) }),
2678
- children
2708
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }, children })
2679
2709
  ]
2680
2710
  }
2681
2711
  )
@@ -3298,10 +3328,10 @@ function useFormActions() {
3298
3328
  }
3299
3329
  return store.getState();
3300
3330
  }
3301
- function getScriptSettingsInstance() {
3302
- throw new Error("[dirk-cfx-react] createScriptSettings must be called before using SettingsPanel");
3331
+ function getScriptConfigInstance() {
3332
+ throw new Error("[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel");
3303
3333
  }
3304
- var settingsPanelQueryClient = new reactQuery.QueryClient({
3334
+ var configPanelQueryClient = new reactQuery.QueryClient({
3305
3335
  defaultOptions: { queries: { staleTime: 3e4, gcTime: 5 * 6e4 } }
3306
3336
  });
3307
3337
  function NavItemButton({
@@ -3345,7 +3375,7 @@ function NavItemButton({
3345
3375
  }
3346
3376
  );
3347
3377
  }
3348
- function SettingsJsonModal({
3378
+ function ConfigJsonModal({
3349
3379
  onClose,
3350
3380
  schema
3351
3381
  }) {
@@ -3382,7 +3412,7 @@ function SettingsJsonModal({
3382
3412
  setError(e.message);
3383
3413
  }
3384
3414
  };
3385
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Settings JSON", icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
3415
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config JSON", icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
3386
3416
  /* @__PURE__ */ jsxRuntime.jsxs(core.Box, { flex: 1, p: "0.8vh", style: { overflowY: "auto" }, children: [
3387
3417
  /* @__PURE__ */ jsxRuntime.jsx(
3388
3418
  core.JsonInput,
@@ -3524,10 +3554,10 @@ function HistoryTableHeader() {
3524
3554
  /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.45)", children: "Version" })
3525
3555
  ] });
3526
3556
  }
3527
- function SettingsHistoryModal({
3557
+ function ConfigHistoryModal({
3528
3558
  onClose
3529
3559
  }) {
3530
- const { getHistory } = getScriptSettingsInstance();
3560
+ const { getHistory } = getScriptConfigInstance();
3531
3561
  const theme = core.useMantineTheme();
3532
3562
  const color = theme.colors[theme.primaryColor][5];
3533
3563
  const [queryInput, setQueryInput] = react.useState("");
@@ -3539,7 +3569,7 @@ function SettingsHistoryModal({
3539
3569
  const [expandedKey, setExpandedKey] = react.useState(null);
3540
3570
  const filters = react.useMemo(() => ({ query, path, admin }), [query, path, admin]);
3541
3571
  const historyQuery = reactQuery.useInfiniteQuery({
3542
- queryKey: ["scriptSettingsHistory", filters],
3572
+ queryKey: ["scriptConfigHistory", filters],
3543
3573
  initialPageParam: 0,
3544
3574
  queryFn: async ({ pageParam }) => {
3545
3575
  const response = await getHistory({
@@ -3550,7 +3580,7 @@ function SettingsHistoryModal({
3550
3580
  admin: filters.admin || void 0
3551
3581
  });
3552
3582
  if (!response?.success || !response.data) {
3553
- throw new Error(response?._error || "Failed to load settings history");
3583
+ throw new Error(response?._error || "Failed to load config history");
3554
3584
  }
3555
3585
  return response.data;
3556
3586
  },
@@ -3564,7 +3594,7 @@ function SettingsHistoryModal({
3564
3594
  historyQuery.fetchNextPage();
3565
3595
  }
3566
3596
  };
3567
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Settings History", icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
3597
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config History", icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
3568
3598
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minHeight: 0 }, children: [
3569
3599
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { gap: "xs", p: "sm", style: { borderBottom: `0.1vh solid ${core.alpha(theme.colors.dark[7], 0.8)}` }, children: [
3570
3600
  /* @__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 } }),
@@ -3613,7 +3643,7 @@ function SettingsHistoryModal({
3613
3643
  ) })
3614
3644
  ] });
3615
3645
  }
3616
- function SettingsPanelInner({
3646
+ function ConfigPanelInner({
3617
3647
  navItems,
3618
3648
  title,
3619
3649
  subtitle,
@@ -3622,28 +3652,37 @@ function SettingsPanelInner({
3622
3652
  onClose,
3623
3653
  schema,
3624
3654
  resetConfirmText,
3625
- defaultSettings,
3655
+ defaultConfig,
3626
3656
  width,
3627
3657
  height
3628
3658
  }) {
3629
- const { updateSettings, getHistory } = getScriptSettingsInstance();
3659
+ const { updateConfig, resetConfig, getHistory } = getScriptConfigInstance();
3630
3660
  const form = useForm();
3631
3661
  const theme = core.useMantineTheme();
3632
3662
  const color = theme.colors[theme.primaryColor][5];
3633
3663
  const version = useSettings((s) => s.resourceVersion);
3634
3664
  const [activeTab, setActiveTab] = react.useState(navItems[0]?.id ?? "");
3665
+ const firstMountRef = react.useRef(true);
3635
3666
  const [jsonOpen, setJsonOpen] = react.useState(false);
3636
3667
  const [historyOpen, setHistoryOpen] = react.useState(false);
3637
3668
  const [resetOpen, setResetOpen] = react.useState(false);
3638
- const [closeConfirmOpen, setCloseConfirmOpen] = react.useState(false);
3669
+ const [pendingAction, setPendingAction] = react.useState(null);
3639
3670
  const changedCount = form.changedCount ?? 0;
3640
3671
  const isDirty = changedCount > 0;
3672
+ const goBack = () => fetchNui("CONFIG_PANEL_BACK");
3673
+ const handleBack = () => {
3674
+ if (isDirty) {
3675
+ setPendingAction("back");
3676
+ return;
3677
+ }
3678
+ goBack();
3679
+ };
3641
3680
  react.useEffect(() => {
3642
3681
  function handleKeyDown(e) {
3643
3682
  if (e.key !== "Escape") return;
3644
3683
  if (isDirty) {
3645
3684
  e.preventDefault();
3646
- setCloseConfirmOpen(true);
3685
+ setPendingAction("close");
3647
3686
  return;
3648
3687
  }
3649
3688
  onClose();
@@ -3652,34 +3691,40 @@ function SettingsPanelInner({
3652
3691
  return () => window.removeEventListener("keydown", handleKeyDown);
3653
3692
  }, [isDirty, onClose]);
3654
3693
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3655
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: jsonOpen && /* @__PURE__ */ jsxRuntime.jsx(SettingsJsonModal, { onClose: () => setJsonOpen(false), schema }) }),
3656
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: historyOpen && /* @__PURE__ */ jsxRuntime.jsx(SettingsHistoryModal, { onClose: () => setHistoryOpen(false) }) }),
3694
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: jsonOpen && /* @__PURE__ */ jsxRuntime.jsx(ConfigJsonModal, { onClose: () => setJsonOpen(false), schema }) }),
3695
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: historyOpen && /* @__PURE__ */ jsxRuntime.jsx(ConfigHistoryModal, { onClose: () => setHistoryOpen(false) }) }),
3657
3696
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: resetOpen && /* @__PURE__ */ jsxRuntime.jsx(
3658
3697
  ConfirmModal,
3659
3698
  {
3660
3699
  title: "Reset to Defaults",
3661
- description: "This will permanently reset ALL settings back to their defaults. Every setting you have configured will be overwritten. This cannot be undone.",
3662
- confirmLabel: "Reset Settings",
3700
+ description: "This will permanently reset ALL config back to the defaults. Every value you have configured will be overwritten. This cannot be undone.",
3701
+ confirmLabel: "Reset Config",
3663
3702
  confirmText: resetConfirmText,
3664
- onConfirm: () => {
3665
- updateSettings(defaultSettings).then(() => form.reinitialize(cloneSettings(defaultSettings)));
3703
+ onConfirm: async () => {
3666
3704
  setResetOpen(false);
3705
+ const result = await resetConfig();
3706
+ if (result?.success) {
3707
+ const { store } = getScriptConfigInstance();
3708
+ form.reinitialize(cloneConfig(store.getState()));
3709
+ }
3667
3710
  },
3668
3711
  onClose: () => setResetOpen(false),
3669
3712
  zIndex: 300
3670
3713
  }
3671
3714
  ) }),
3672
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: closeConfirmOpen && /* @__PURE__ */ jsxRuntime.jsx(
3715
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: pendingAction !== null && /* @__PURE__ */ jsxRuntime.jsx(
3673
3716
  ConfirmModal,
3674
3717
  {
3675
3718
  title: "Discard Unsaved Changes?",
3676
- description: "You have unsaved changes. Closing now will discard them.",
3677
- confirmLabel: "Close Without Saving",
3719
+ description: pendingAction === "back" ? "You have unsaved changes. Going back now will discard them." : "You have unsaved changes. Closing now will discard them.",
3720
+ confirmLabel: pendingAction === "back" ? "Go Back Without Saving" : "Close Without Saving",
3678
3721
  onConfirm: () => {
3679
- setCloseConfirmOpen(false);
3680
- onClose();
3722
+ const action = pendingAction;
3723
+ setPendingAction(null);
3724
+ if (action === "back") goBack();
3725
+ else onClose();
3681
3726
  },
3682
- onClose: () => setCloseConfirmOpen(false),
3727
+ onClose: () => setPendingAction(null),
3683
3728
  zIndex: 300
3684
3729
  }
3685
3730
  ) }),
@@ -3706,9 +3751,33 @@ function SettingsPanelInner({
3706
3751
  exit: { scale: 0.3, opacity: 0, transform: "translate(-50%, -50%)" },
3707
3752
  children: [
3708
3753
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { width: "18vh", flexShrink: 0, borderRight: `0.1vh solid ${core.alpha(theme.colors.dark[6], 0.8)}`, background: core.alpha(theme.colors.dark[8], 0.6), overflow: "hidden" }, children: [
3709
- /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "baseline", gap: "0.3vh", px: "sm", py: "sm", style: { borderBottom: `0.1vh solid ${core.alpha(theme.colors.dark[6], 0.5)}`, flexShrink: 0 }, children: [
3710
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", children: title }),
3711
- subtitle && /* @__PURE__ */ jsxRuntime.jsx(core.Text, { tt: "uppercase", fw: 600, c: color, children: subtitle })
3754
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "0.6vh", px: "sm", py: "sm", style: { borderBottom: `0.1vh solid ${core.alpha(theme.colors.dark[6], 0.5)}`, flexShrink: 0 }, children: [
3755
+ /* @__PURE__ */ jsxRuntime.jsx(
3756
+ framerMotion.motion.button,
3757
+ {
3758
+ title: "Back to script list",
3759
+ onClick: handleBack,
3760
+ whileHover: { background: core.alpha(color, 0.16), borderColor: core.alpha(color, 0.45) },
3761
+ whileTap: { scale: 0.95 },
3762
+ style: {
3763
+ aspectRatio: "1 / 1",
3764
+ height: "2.4vh",
3765
+ background: core.alpha(color, 0.08),
3766
+ border: `0.1vh solid ${core.alpha(color, 0.3)}`,
3767
+ borderRadius: theme.radius.xs,
3768
+ cursor: "pointer",
3769
+ display: "flex",
3770
+ alignItems: "center",
3771
+ justifyContent: "center",
3772
+ flexShrink: 0
3773
+ },
3774
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { size: "1.4vh", color })
3775
+ }
3776
+ ),
3777
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { minWidth: 0, lineHeight: 1 }, children: [
3778
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
3779
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.08em", c: color, truncate: true, children: subtitle })
3780
+ ] })
3712
3781
  ] }),
3713
3782
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { gap: "xxs", px: "xs", py: "xs", style: { borderBottom: `0.1vh solid ${core.alpha(theme.colors.dark[6], 0.4)}`, flexShrink: 0 }, children: [
3714
3783
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -3811,7 +3880,7 @@ function SettingsPanelInner({
3811
3880
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
3812
3881
  framerMotion.motion.div,
3813
3882
  {
3814
- initial: { opacity: 0, y: 4 },
3883
+ initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
3815
3884
  animate: { opacity: 1, y: 0 },
3816
3885
  exit: { opacity: 0, y: -4 },
3817
3886
  transition: { duration: 0.15 },
@@ -3825,15 +3894,15 @@ function SettingsPanelInner({
3825
3894
  )
3826
3895
  ] });
3827
3896
  }
3828
- function cloneSettings(value) {
3897
+ function cloneConfig(value) {
3829
3898
  return JSON.parse(JSON.stringify(value));
3830
3899
  }
3831
3900
  function ServerOnlyFetcher() {
3832
- const { fetchSettings } = getScriptSettingsInstance();
3901
+ const { fetchConfig } = getScriptConfigInstance();
3833
3902
  const { reinitialize } = useFormActions();
3834
3903
  react.useEffect(() => {
3835
3904
  let cancelled = false;
3836
- fetchSettings().then((full) => {
3905
+ fetchConfig().then((full) => {
3837
3906
  if (!cancelled && full) reinitialize(full);
3838
3907
  }).catch(() => {
3839
3908
  });
@@ -3844,28 +3913,28 @@ function ServerOnlyFetcher() {
3844
3913
  return null;
3845
3914
  }
3846
3915
  var defaultOnClose = () => fetchNui("CLOSE_ADMIN_SECTION");
3847
- function SettingsPanel(props) {
3916
+ function ConfigPanel(props) {
3848
3917
  const { open, onClose = defaultOnClose } = props;
3849
- const { store, updateSettings, fetchSettings } = getScriptSettingsInstance();
3918
+ const { store, updateConfig } = getScriptConfigInstance();
3850
3919
  const [isSaving, setIsSaving] = react.useState(false);
3851
3920
  if (!open) return null;
3852
- return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: settingsPanelQueryClient, children: /* @__PURE__ */ jsxRuntime.jsxs(
3921
+ return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: configPanelQueryClient, children: /* @__PURE__ */ jsxRuntime.jsxs(
3853
3922
  FormProvider,
3854
3923
  {
3855
- initialValues: cloneSettings(store.getState()),
3924
+ initialValues: cloneConfig(store.getState()),
3856
3925
  onSubmit: async (form) => {
3857
3926
  if (isSaving) return;
3858
3927
  setIsSaving(true);
3859
3928
  try {
3860
- const result = await updateSettings(form.values);
3929
+ const result = await updateConfig(form.values);
3861
3930
  if (result?.success) {
3862
- form.reinitialize(cloneSettings(form.values));
3863
- settingsPanelQueryClient.invalidateQueries({ queryKey: ["scriptSettingsHistory"] });
3931
+ form.reinitialize(cloneConfig(form.values));
3932
+ configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
3864
3933
  return;
3865
3934
  }
3866
- form.reinitialize(cloneSettings(store.getState()));
3935
+ form.reinitialize(cloneConfig(store.getState()));
3867
3936
  if (result?._error) {
3868
- console.warn(`[SettingsPanel] settings save failed: ${result._error}`);
3937
+ console.warn(`[ConfigPanel] config save failed: ${result._error}`);
3869
3938
  }
3870
3939
  } finally {
3871
3940
  setIsSaving(false);
@@ -3874,7 +3943,7 @@ function SettingsPanel(props) {
3874
3943
  children: [
3875
3944
  /* @__PURE__ */ jsxRuntime.jsx(ServerOnlyFetcher, {}),
3876
3945
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: open && /* @__PURE__ */ jsxRuntime.jsx(
3877
- SettingsPanelInner,
3946
+ ConfigPanelInner,
3878
3947
  {
3879
3948
  ...props,
3880
3949
  onClose,
@@ -4082,12 +4151,167 @@ function AdminPageTitle(props) {
4082
4151
  /* @__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) })
4083
4152
  ] });
4084
4153
  }
4154
+ var loadPersistedState = (storageKey) => {
4155
+ try {
4156
+ const raw = localStorage.getItem(storageKey);
4157
+ return raw ? JSON.parse(raw) : {};
4158
+ } catch {
4159
+ return {};
4160
+ }
4161
+ };
4162
+ var savePersistedState = (storageKey, state) => {
4163
+ try {
4164
+ localStorage.setItem(storageKey, JSON.stringify(state));
4165
+ } catch {
4166
+ }
4167
+ };
4168
+ function TestBed({
4169
+ items,
4170
+ storageKey = "testbed:open-state",
4171
+ disablePersistence = false,
4172
+ title = "TestBed"
4173
+ }) {
4174
+ const [open, setOpen] = react.useState(false);
4175
+ const itemsRef = react.useRef(items);
4176
+ itemsRef.current = items;
4177
+ react.useEffect(() => {
4178
+ if (!isEnvBrowser() || disablePersistence) return;
4179
+ const persisted = loadPersistedState(storageKey);
4180
+ itemsRef.current.forEach((item) => {
4181
+ const persistedValue = persisted[item.key];
4182
+ if (typeof persistedValue === "boolean" && persistedValue !== item.active) {
4183
+ item.onToggle(persistedValue);
4184
+ }
4185
+ });
4186
+ }, []);
4187
+ if (!isEnvBrowser()) return null;
4188
+ const toggle = (item) => {
4189
+ const next = !item.active;
4190
+ item.onToggle(next);
4191
+ if (!disablePersistence) {
4192
+ const persisted = loadPersistedState(storageKey);
4193
+ persisted[item.key] = next;
4194
+ savePersistedState(storageKey, persisted);
4195
+ }
4196
+ };
4197
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4198
+ "div",
4199
+ {
4200
+ style: {
4201
+ position: "fixed",
4202
+ top: "1vh",
4203
+ left: "1vh",
4204
+ zIndex: 2147483647,
4205
+ pointerEvents: "auto",
4206
+ fontSize: "1.4vh"
4207
+ },
4208
+ children: [
4209
+ /* @__PURE__ */ jsxRuntime.jsxs(
4210
+ core.Flex,
4211
+ {
4212
+ align: "center",
4213
+ gap: "xs",
4214
+ px: "sm",
4215
+ py: "xs",
4216
+ onClick: () => setOpen((v) => !v),
4217
+ style: {
4218
+ cursor: "pointer",
4219
+ background: "rgba(0,0,0,0.55)",
4220
+ backdropFilter: "blur(0.6vh)",
4221
+ WebkitBackdropFilter: "blur(0.6vh)",
4222
+ border: "0.1vh solid rgba(255,255,255,0.1)",
4223
+ borderRadius: "var(--mantine-radius-sm)",
4224
+ userSelect: "none",
4225
+ minWidth: "16vh"
4226
+ },
4227
+ children: [
4228
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FlaskConical, { size: 14, color: "rgba(255,255,255,0.7)" }),
4229
+ /* @__PURE__ */ jsxRuntime.jsx(
4230
+ core.Text,
4231
+ {
4232
+ size: "xs",
4233
+ ff: "Akrobat Bold",
4234
+ tt: "uppercase",
4235
+ lts: "0.08em",
4236
+ c: "rgba(255,255,255,0.85)",
4237
+ style: { flex: 1 },
4238
+ children: title
4239
+ }
4240
+ ),
4241
+ /* @__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 }) })
4242
+ ]
4243
+ }
4244
+ ),
4245
+ open && /* @__PURE__ */ jsxRuntime.jsx(
4246
+ core.Stack,
4247
+ {
4248
+ gap: 4,
4249
+ mt: "xxs",
4250
+ p: "xs",
4251
+ style: {
4252
+ background: "rgba(0,0,0,0.55)",
4253
+ backdropFilter: "blur(0.6vh)",
4254
+ WebkitBackdropFilter: "blur(0.6vh)",
4255
+ border: "0.1vh solid rgba(255,255,255,0.1)",
4256
+ borderRadius: "var(--mantine-radius-sm)",
4257
+ minWidth: "16vh",
4258
+ maxHeight: "80vh",
4259
+ overflowY: "auto"
4260
+ },
4261
+ children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
4262
+ core.Flex,
4263
+ {
4264
+ align: "center",
4265
+ justify: "space-between",
4266
+ gap: "xs",
4267
+ px: "xs",
4268
+ py: "xxs",
4269
+ onClick: () => toggle(item),
4270
+ style: {
4271
+ cursor: "pointer",
4272
+ borderRadius: "var(--mantine-radius-xs)",
4273
+ background: item.active ? "rgba(245,158,11,0.15)" : "rgba(255,255,255,0.03)",
4274
+ border: `0.1vh solid ${item.active ? "rgba(245,158,11,0.35)" : "rgba(255,255,255,0.05)"}`,
4275
+ userSelect: "none"
4276
+ },
4277
+ children: [
4278
+ /* @__PURE__ */ jsxRuntime.jsx(
4279
+ core.Text,
4280
+ {
4281
+ size: "xs",
4282
+ ff: "Akrobat Bold",
4283
+ c: item.active ? "#f59e0b" : "rgba(255,255,255,0.75)",
4284
+ children: item.label
4285
+ }
4286
+ ),
4287
+ /* @__PURE__ */ jsxRuntime.jsx(
4288
+ core.Text,
4289
+ {
4290
+ size: "xxs",
4291
+ ff: "Akrobat Bold",
4292
+ tt: "uppercase",
4293
+ lts: "0.06em",
4294
+ c: item.active ? "#f59e0b" : "rgba(255,255,255,0.35)",
4295
+ children: item.active ? "On" : "Off"
4296
+ }
4297
+ )
4298
+ ]
4299
+ },
4300
+ item.key
4301
+ ))
4302
+ }
4303
+ )
4304
+ ]
4305
+ }
4306
+ );
4307
+ }
4085
4308
 
4086
4309
  exports.AdminPageTitle = AdminPageTitle;
4087
4310
  exports.AsyncSaveButton = AsyncSaveButton;
4088
4311
  exports.BlipColorSelect = BlipColorSelect;
4089
4312
  exports.BlipIconSelect = BlipIconSelect;
4090
4313
  exports.BorderedIcon = BorderedIcon;
4314
+ exports.ConfigPanel = ConfigPanel;
4091
4315
  exports.ConfirmModal = ConfirmModal;
4092
4316
  exports.Counter = Counter;
4093
4317
  exports.FiveMKeyBindInput = FiveMKeyBindInput;
@@ -4110,7 +4334,7 @@ exports.PromptModal = PromptModal;
4110
4334
  exports.SegmentedControl = SegmentedControl;
4111
4335
  exports.SegmentedProgress = SegmentedProgress;
4112
4336
  exports.SelectItem = SelectItem;
4113
- exports.SettingsPanel = SettingsPanel;
4337
+ exports.TestBed = TestBed;
4114
4338
  exports.Title = Title;
4115
4339
  exports.useModal = useModal;
4116
4340
  exports.useModalActions = useModalActions;