dirk-cfx-react 1.1.89 → 1.1.91

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
@@ -4,6 +4,7 @@ var core = require('@mantine/core');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var React4 = require('react');
6
6
  var zustand = require('zustand');
7
+ var zod = require('zod');
7
8
  var axios = require('axios');
8
9
  var reactFontawesome = require('@fortawesome/react-fontawesome');
9
10
  var framerMotion = require('framer-motion');
@@ -28,7 +29,6 @@ var leaflet = require('leaflet');
28
29
  require('leaflet/dist/leaflet.css');
29
30
  var reactLeaflet = require('react-leaflet');
30
31
  var reactLeafletComponentMarker = require('@adamscybot/react-leaflet-component-marker');
31
- var zod = require('zod');
32
32
 
33
33
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
34
34
 
@@ -1321,6 +1321,32 @@ var openLink = (url) => {
1321
1321
  window.invokeNative("openLink", url);
1322
1322
  }
1323
1323
  };
1324
+ var DEFAULT_PALETTE = [
1325
+ "#f0f4ff",
1326
+ "#d9e3ff",
1327
+ "#bfcfff",
1328
+ "#a6bbff",
1329
+ "#8ca7ff",
1330
+ "#7393ff",
1331
+ "#5a7fff",
1332
+ "#406bff",
1333
+ "#2547ff",
1334
+ "#0b33ff"
1335
+ ];
1336
+ var ThemeOverrideSchema = zod.z.object({
1337
+ useOverride: zod.z.boolean(),
1338
+ primaryColor: zod.z.string(),
1339
+ primaryShade: zod.z.number(),
1340
+ customTheme: zod.z.array(zod.z.string())
1341
+ });
1342
+ var defaultThemeOverride = {
1343
+ useOverride: false,
1344
+ primaryColor: "dirk",
1345
+ primaryShade: 5,
1346
+ customTheme: DEFAULT_PALETTE
1347
+ };
1348
+
1349
+ // src/utils/useSettings.ts
1324
1350
  var useSettings = zustand.create(() => ({
1325
1351
  currency: "$",
1326
1352
  game: "fivem",
@@ -1328,18 +1354,7 @@ var useSettings = zustand.create(() => ({
1328
1354
  primaryShade: 9,
1329
1355
  itemImgPath: "",
1330
1356
  resourceVersion: "dev",
1331
- customTheme: [
1332
- "#f0f4ff",
1333
- "#d9e3ff",
1334
- "#bfcfff",
1335
- "#a6bbff",
1336
- "#8ca7ff",
1337
- "#7393ff",
1338
- "#5a7fff",
1339
- "#406bff",
1340
- "#2547ff",
1341
- "#0b33ff"
1342
- ]
1357
+ customTheme: DEFAULT_PALETTE
1343
1358
  }));
1344
1359
 
1345
1360
  // src/utils/fetchNui.ts
@@ -1423,11 +1438,35 @@ function reportMissingLocale(key) {
1423
1438
  fetchNui("REPORT_MISSING_LOCALE", { key }).catch(() => {
1424
1439
  });
1425
1440
  }
1441
+ var PACKAGE_DEFAULTS = {
1442
+ "OccupantsDesc": "Here you can view and manage the occupants of your traphouse. These occupants can be used mainly for selling drugs to the NPCs surrounding your traphouse. However they have other uses to so be careful who you add as an occupant.",
1443
+ "cfgpanel_discard": "Discard",
1444
+ "cfgpanel_manual_edit": "Manual Edit",
1445
+ "cfgpanel_reset_defaults": "Reset Defaults",
1446
+ "cfgpanel_version": "Version",
1447
+ "cfgpanel_back_title": "Back to script list",
1448
+ "cfgpanel_undo": "Undo",
1449
+ "cfgpanel_redo": "Redo",
1450
+ "cfgpanel_save": "Save",
1451
+ "cfgpanel_saving": "Saving...",
1452
+ "cfgpanel_history": "History",
1453
+ "cfgpanel_reset_title": "Reset to Defaults",
1454
+ "cfgpanel_reset_desc": "This will permanently reset ALL config back to the defaults. Every value you have configured will be overwritten. This cannot be undone.",
1455
+ "cfgpanel_reset_confirm": "Reset Config",
1456
+ "cfgpanel_discard_title": "Discard Unsaved Changes?",
1457
+ "cfgpanel_discard_desc_back": "You have unsaved changes. Going back now will discard them.",
1458
+ "cfgpanel_discard_desc_close": "You have unsaved changes. Closing now will discard them.",
1459
+ "cfgpanel_discard_confirm_back": "Go Back Without Saving",
1460
+ "cfgpanel_discard_confirm_close": "Close Without Saving",
1461
+ "cfgpanel_json_title": "Config JSON",
1462
+ "cfgpanel_cancel": "Cancel",
1463
+ "cfgpanel_apply": "Apply",
1464
+ "cfgpanel_history_title": "Config History",
1465
+ "cfgpanel_close": "Close"
1466
+ };
1426
1467
  var localeStore = zustand.create((set, get) => {
1427
1468
  return {
1428
- locales: {
1429
- "OccupantsDesc": "Here you can view and manage the occupants of your traphouse. These occupants can be used mainly for selling drugs to the NPCs surrounding your traphouse. However they have other uses to so be careful who you add as an occupant."
1430
- },
1469
+ locales: { ...PACKAGE_DEFAULTS },
1431
1470
  locale: (key, ...args) => {
1432
1471
  const exists = get().locales[key];
1433
1472
  if (!exists) reportMissingLocale(key);
@@ -1441,7 +1480,7 @@ var localeStore = zustand.create((set, get) => {
1441
1480
  });
1442
1481
  var locale = localeStore.getState().locale;
1443
1482
  registerInitialFetch("GET_LOCALES", void 0).then((data) => {
1444
- localeStore.setState({ locales: data });
1483
+ localeStore.setState({ locales: { ...PACKAGE_DEFAULTS, ...data } });
1445
1484
  }).catch(() => {
1446
1485
  });
1447
1486
  if (typeof window !== "undefined") {
@@ -1450,7 +1489,7 @@ if (typeof window !== "undefined") {
1450
1489
  if (!msg || msg.action !== "UPDATE_DIRK_LIB_LOCALES") return;
1451
1490
  if (!msg.data || typeof msg.data !== "object") return;
1452
1491
  if (Object.keys(msg.data).length === 0) return;
1453
- localeStore.setState({ locales: msg.data });
1492
+ localeStore.setState({ locales: { ...PACKAGE_DEFAULTS, ...msg.data } });
1454
1493
  });
1455
1494
  }
1456
1495
 
@@ -3727,11 +3766,11 @@ function ConfirmModal({
3727
3766
  placeholder: confirmText,
3728
3767
  value: typed,
3729
3768
  onChange: (e) => setTyped(e.currentTarget.value),
3730
- styles: (t3) => ({
3769
+ styles: (t4) => ({
3731
3770
  input: {
3732
- backgroundColor: core.alpha(t3.colors.dark[7], 0.5),
3771
+ backgroundColor: core.alpha(t4.colors.dark[7], 0.5),
3733
3772
  border: `0.1vh solid ${core.alpha(
3734
- typed === confirmText ? "#ef4444" : t3.colors.dark[5],
3773
+ typed === confirmText ? "#ef4444" : t4.colors.dark[5],
3735
3774
  0.5
3736
3775
  )}`,
3737
3776
  color: "rgba(255,255,255,0.85)",
@@ -4409,7 +4448,7 @@ function useAdminState(key, initial) {
4409
4448
  React4.useEffect(() => {
4410
4449
  const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
4411
4450
  listeners.set(key, set);
4412
- const handler = () => setTick((t3) => t3 + 1);
4451
+ const handler = () => setTick((t4) => t4 + 1);
4413
4452
  set.add(handler);
4414
4453
  return () => {
4415
4454
  set.delete(handler);
@@ -5021,7 +5060,7 @@ function ConfigJsonModal({
5021
5060
  setError(e.message);
5022
5061
  }
5023
5062
  };
5024
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config JSON", icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
5063
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: locale("cfgpanel_json_title"), icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
5025
5064
  /* @__PURE__ */ jsxRuntime.jsxs(core.Box, { flex: 1, p: "0.8vh", style: { overflowY: "auto" }, children: [
5026
5065
  /* @__PURE__ */ jsxRuntime.jsx(
5027
5066
  core.JsonInput,
@@ -5049,7 +5088,7 @@ function ConfigJsonModal({
5049
5088
  style: { background: "transparent", border: `0.1vh solid ${theme2.colors.dark[6]}`, borderRadius: theme2.radius.xs, padding: `${theme2.spacing.xxs} ${theme2.spacing.xs}`, cursor: "pointer", display: "flex", alignItems: "center", gap: theme2.spacing.xxs },
5050
5089
  children: [
5051
5090
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { color: "rgba(255,255,255,0.4)" }),
5052
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: "Cancel" })
5091
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: locale("cfgpanel_cancel") })
5053
5092
  ]
5054
5093
  }
5055
5094
  ),
@@ -5062,7 +5101,7 @@ function ConfigJsonModal({
5062
5101
  style: { background: core.alpha(color, 0.14), border: `0.1vh solid ${core.alpha(color, 0.4)}`, borderRadius: theme2.radius.xs, padding: `${theme2.spacing.xxs} ${theme2.spacing.xs}`, cursor: "pointer", display: "flex", alignItems: "center", gap: theme2.spacing.xxs },
5063
5102
  children: [
5064
5103
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { color }),
5065
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: color, children: "Apply" })
5104
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: color, children: locale("cfgpanel_apply") })
5066
5105
  ]
5067
5106
  }
5068
5107
  )
@@ -5203,7 +5242,7 @@ function ConfigHistoryModal({
5203
5242
  historyQuery.fetchNextPage();
5204
5243
  }
5205
5244
  };
5206
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config History", icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
5245
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: locale("cfgpanel_history_title"), icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
5207
5246
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minHeight: 0 }, children: [
5208
5247
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { gap: "xs", p: "sm", style: { borderBottom: `0.1vh solid ${core.alpha(theme2.colors.dark[7], 0.8)}` }, children: [
5209
5248
  /* @__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 } }),
@@ -5246,7 +5285,7 @@ function ConfigHistoryModal({
5246
5285
  style: { background: "transparent", border: `0.1vh solid ${theme2.colors.dark[6]}`, borderRadius: theme2.radius.xs, padding: `${theme2.spacing.xxs} ${theme2.spacing.xs}`, cursor: "pointer", display: "flex", alignItems: "center", gap: theme2.spacing.xxs },
5247
5286
  children: [
5248
5287
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { color: "rgba(255,255,255,0.4)", size: "1.5vh" }),
5249
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: "Close" })
5288
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: locale("cfgpanel_close") })
5250
5289
  ]
5251
5290
  }
5252
5291
  ) })
@@ -5306,9 +5345,9 @@ function ConfigPanelInner({
5306
5345
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: resetOpen && /* @__PURE__ */ jsxRuntime.jsx(
5307
5346
  ConfirmModal,
5308
5347
  {
5309
- title: "Reset to Defaults",
5310
- description: "This will permanently reset ALL config back to the defaults. Every value you have configured will be overwritten. This cannot be undone.",
5311
- confirmLabel: "Reset Config",
5348
+ title: locale("cfgpanel_reset_title"),
5349
+ description: locale("cfgpanel_reset_desc"),
5350
+ confirmLabel: locale("cfgpanel_reset_confirm"),
5312
5351
  confirmText: resetConfirmText,
5313
5352
  onConfirm: async () => {
5314
5353
  setResetOpen(false);
@@ -5340,9 +5379,9 @@ function ConfigPanelInner({
5340
5379
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: pendingAction !== null && /* @__PURE__ */ jsxRuntime.jsx(
5341
5380
  ConfirmModal,
5342
5381
  {
5343
- title: "Discard Unsaved Changes?",
5344
- description: pendingAction === "back" ? "You have unsaved changes. Going back now will discard them." : "You have unsaved changes. Closing now will discard them.",
5345
- confirmLabel: pendingAction === "back" ? "Go Back Without Saving" : "Close Without Saving",
5382
+ title: locale("cfgpanel_discard_title"),
5383
+ description: pendingAction === "back" ? locale("cfgpanel_discard_desc_back") : locale("cfgpanel_discard_desc_close"),
5384
+ confirmLabel: pendingAction === "back" ? locale("cfgpanel_discard_confirm_back") : locale("cfgpanel_discard_confirm_close"),
5346
5385
  onConfirm: () => {
5347
5386
  const action = pendingAction;
5348
5387
  setPendingAction(null);
@@ -5380,7 +5419,7 @@ function ConfigPanelInner({
5380
5419
  /* @__PURE__ */ jsxRuntime.jsx(
5381
5420
  framerMotion.motion.button,
5382
5421
  {
5383
- title: "Back to script list",
5422
+ title: locale("cfgpanel_back_title"),
5384
5423
  onClick: handleBack,
5385
5424
  whileHover: { background: core.alpha(color, 0.16), borderColor: core.alpha(color, 0.45) },
5386
5425
  whileTap: { scale: 0.95 },
@@ -5408,7 +5447,7 @@ function ConfigPanelInner({
5408
5447
  /* @__PURE__ */ jsxRuntime.jsx(
5409
5448
  framerMotion.motion.button,
5410
5449
  {
5411
- title: "Undo",
5450
+ title: locale("cfgpanel_undo"),
5412
5451
  onClick: () => form.canBack && form.back(),
5413
5452
  disabled: !form.canBack,
5414
5453
  whileHover: form.canBack ? { background: "rgba(255,255,255,0.07)" } : void 0,
@@ -5420,7 +5459,7 @@ function ConfigPanelInner({
5420
5459
  /* @__PURE__ */ jsxRuntime.jsx(
5421
5460
  framerMotion.motion.button,
5422
5461
  {
5423
- title: "Redo",
5462
+ title: locale("cfgpanel_redo"),
5424
5463
  onClick: () => form.canForward && form.forward(),
5425
5464
  disabled: !form.canForward,
5426
5465
  whileHover: form.canForward ? { background: "rgba(255,255,255,0.07)" } : void 0,
@@ -5432,7 +5471,7 @@ function ConfigPanelInner({
5432
5471
  /* @__PURE__ */ jsxRuntime.jsx(
5433
5472
  framerMotion.motion.button,
5434
5473
  {
5435
- title: isSaving ? "Saving..." : isDirty ? `Save (${changedCount})` : "Save",
5474
+ title: isSaving ? locale("cfgpanel_saving") : isDirty ? `${locale("cfgpanel_save")} (${changedCount})` : locale("cfgpanel_save"),
5436
5475
  onClick: () => isDirty && !isSaving && form.submit(),
5437
5476
  disabled: !isDirty || isSaving,
5438
5477
  whileHover: isDirty && !isSaving ? { background: core.alpha(color, 0.25), borderColor: core.alpha(color, 0.5) } : void 0,
@@ -5444,7 +5483,7 @@ function ConfigPanelInner({
5444
5483
  /* @__PURE__ */ jsxRuntime.jsx(
5445
5484
  framerMotion.motion.button,
5446
5485
  {
5447
- title: "History",
5486
+ title: locale("cfgpanel_history"),
5448
5487
  onClick: () => setHistoryOpen(true),
5449
5488
  whileHover: { background: core.alpha("#22d3ee", 0.16), borderColor: core.alpha("#22d3ee", 0.5) },
5450
5489
  whileTap: { scale: 0.97 },
@@ -5465,7 +5504,7 @@ function ConfigPanelInner({
5465
5504
  style: { width: "100%", background: "transparent", border: `0.1vh solid ${isDirty ? core.alpha("#f97316", 0.35) : core.alpha(theme2.colors.dark[5], 0.3)}`, borderRadius: theme2.radius.xs, padding: "0.65vh 0.8vh", cursor: isDirty ? "pointer" : "default", display: "flex", alignItems: "center", gap: "0.55vh", opacity: isDirty ? 1 : 0.35, transition: "opacity 0.2s" },
5466
5505
  children: [
5467
5506
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { color: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", size: "1.6vh" }),
5468
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", children: "Discard" })
5507
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", children: locale("cfgpanel_discard") })
5469
5508
  ]
5470
5509
  }
5471
5510
  ),
@@ -5479,7 +5518,7 @@ function ConfigPanelInner({
5479
5518
  style: { width: "100%", background: core.alpha(color, 0.07), border: `0.1vh solid ${core.alpha(color, 0.28)}`, borderRadius: theme2.radius.xs, padding: "0.65vh 0.8vh", cursor: "pointer", display: "flex", alignItems: "center", gap: "0.55vh" },
5480
5519
  children: [
5481
5520
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code2, { color, size: "1.6vh" }),
5482
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: "Manual Edit" })
5521
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: locale("cfgpanel_manual_edit") })
5483
5522
  ]
5484
5523
  }
5485
5524
  ),
@@ -5492,12 +5531,13 @@ function ConfigPanelInner({
5492
5531
  style: { width: "100%", background: core.alpha("#ef4444", 0.07), border: `0.1vh solid ${core.alpha("#ef4444", 0.25)}`, borderRadius: theme2.radius.xs, padding: "0.65vh 0.8vh", cursor: "pointer", display: "flex", alignItems: "center", gap: "0.55vh" },
5493
5532
  children: [
5494
5533
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { color: "#ef4444", size: "1.6vh" }),
5495
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "#ef4444", children: "Reset Defaults" })
5534
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "#ef4444", children: locale("cfgpanel_reset_defaults") })
5496
5535
  ]
5497
5536
  }
5498
5537
  ),
5499
5538
  version && /* @__PURE__ */ jsxRuntime.jsxs(core.Text, { size: "0.95vh", c: "dimmed", ta: "center", style: { letterSpacing: "0.04em", opacity: 0.8 }, children: [
5500
- "Version ",
5539
+ locale("cfgpanel_version"),
5540
+ " ",
5501
5541
  version
5502
5542
  ] })
5503
5543
  ] })
@@ -6588,24 +6628,6 @@ var MANTINE_COLOR_OPTIONS = [
6588
6628
  "yellow",
6589
6629
  "orange"
6590
6630
  ].map((value) => ({ value, label: value }));
6591
- var DEFAULT_PALETTE = [
6592
- "#f0f4ff",
6593
- "#d9e3ff",
6594
- "#bfcfff",
6595
- "#a6bbff",
6596
- "#8ca7ff",
6597
- "#7393ff",
6598
- "#5a7fff",
6599
- "#406bff",
6600
- "#2547ff",
6601
- "#0b33ff"
6602
- ];
6603
- var DEFAULT_VALUE = {
6604
- useOverride: false,
6605
- primaryColor: "dirk",
6606
- primaryShade: 5,
6607
- customTheme: DEFAULT_PALETTE
6608
- };
6609
6631
  function GroupLabel({ label: label2 }) {
6610
6632
  return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", mt: "xxs", children: [
6611
6633
  /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
@@ -6620,10 +6642,10 @@ function ThemeOverrideSection({
6620
6642
  const color = mantineTheme.colors[mantineTheme.primaryColor][5];
6621
6643
  const raw = useFormField(schemaKey);
6622
6644
  const value = {
6623
- useOverride: raw?.useOverride ?? DEFAULT_VALUE.useOverride,
6624
- primaryColor: raw?.primaryColor ?? DEFAULT_VALUE.primaryColor,
6625
- primaryShade: raw?.primaryShade ?? DEFAULT_VALUE.primaryShade,
6626
- customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : DEFAULT_VALUE.customTheme
6645
+ useOverride: raw?.useOverride ?? defaultThemeOverride.useOverride,
6646
+ primaryColor: raw?.primaryColor ?? defaultThemeOverride.primaryColor,
6647
+ primaryShade: raw?.primaryShade ?? defaultThemeOverride.primaryShade,
6648
+ customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : defaultThemeOverride.customTheme
6627
6649
  };
6628
6650
  const { setValue } = useFormActions();
6629
6651
  const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
@@ -6830,6 +6852,403 @@ function SwatchTile({
6830
6852
  ) })
6831
6853
  ] });
6832
6854
  }
6855
+ function usePlayers(opts = {}) {
6856
+ const {
6857
+ includeOffline = false,
6858
+ search = "",
6859
+ limit = 50,
6860
+ staleTimeMs,
6861
+ refetchIntervalMs
6862
+ } = opts;
6863
+ const query = reactQuery.useQuery({
6864
+ queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
6865
+ queryFn: async () => {
6866
+ const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
6867
+ const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
6868
+ const result = await fetchNui(
6869
+ "ADMIN_TOOL_QUERY",
6870
+ payload,
6871
+ // Browser-dev fallback. Returns a couple of mock players so the
6872
+ // dev shell isn't blank.
6873
+ includeOffline ? [
6874
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
6875
+ { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
6876
+ ] : [
6877
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
6878
+ ]
6879
+ );
6880
+ return Array.isArray(result) ? result : [];
6881
+ },
6882
+ staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
6883
+ gcTime: 5 * 6e4,
6884
+ refetchInterval: refetchIntervalMs ?? false,
6885
+ refetchOnWindowFocus: false,
6886
+ placeholderData: includeOffline ? reactQuery.keepPreviousData : void 0
6887
+ });
6888
+ return {
6889
+ players: query.data ?? [],
6890
+ isLoading: query.isLoading,
6891
+ isFetching: query.isFetching,
6892
+ error: query.error ?? null,
6893
+ refresh: () => query.refetch()
6894
+ };
6895
+ }
6896
+ var DEBOUNCE_MS = 300;
6897
+ var ONLINE_COLOR = "#3FA83F";
6898
+ var OFFLINE_COLOR = "#E54141";
6899
+ function PlayerSelect({
6900
+ value,
6901
+ onChange,
6902
+ includeOffline = false,
6903
+ limit = 50,
6904
+ searchable: searchableProp,
6905
+ placeholder,
6906
+ nothingFoundMessage: nothingFoundMessageProp,
6907
+ loadingLabel = "Loading\u2026",
6908
+ onlineLabel = "Online",
6909
+ offlineLabel = "Offline",
6910
+ refreshLabel = "Refresh player list",
6911
+ ...rest
6912
+ }) {
6913
+ const theme2 = core.useMantineTheme();
6914
+ const color = theme2.colors[theme2.primaryColor][5];
6915
+ const [searchInput, setSearchInput] = React4.useState("");
6916
+ const [debouncedSearch, setDebouncedSearch] = React4.useState("");
6917
+ const { players, isFetching, refresh } = usePlayers({
6918
+ includeOffline,
6919
+ search: includeOffline ? debouncedSearch : void 0,
6920
+ limit
6921
+ });
6922
+ const sortedPlayers = React4.useMemo(
6923
+ () => [...players].sort((a, b) => {
6924
+ if (a.online !== b.online) return a.online ? -1 : 1;
6925
+ return a.charName.localeCompare(b.charName);
6926
+ }),
6927
+ [players]
6928
+ );
6929
+ const playerByCitizen = React4.useMemo(() => {
6930
+ const m = /* @__PURE__ */ new Map();
6931
+ for (const p of sortedPlayers) m.set(p.citizenId, p);
6932
+ return m;
6933
+ }, [sortedPlayers]);
6934
+ const data = React4.useMemo(() => {
6935
+ const items = sortedPlayers.map((p) => ({
6936
+ value: p.citizenId,
6937
+ label: formatLabel(p)
6938
+ }));
6939
+ if (value && !items.some((i) => i.value === value)) {
6940
+ items.unshift({ value, label: value });
6941
+ }
6942
+ return items;
6943
+ }, [sortedPlayers, value]);
6944
+ const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
6945
+ const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
6946
+ React4.useEffect(() => {
6947
+ if (!includeOffline) return;
6948
+ if (selectedLabel && searchInput === selectedLabel) return;
6949
+ const t4 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
6950
+ return () => clearTimeout(t4);
6951
+ }, [searchInput, includeOffline, selectedLabel]);
6952
+ const renderOption = ({ option }) => {
6953
+ const p = playerByCitizen.get(option.value);
6954
+ if (!p) return option.label;
6955
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
6956
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
6957
+ /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
6958
+ ] });
6959
+ };
6960
+ return /* @__PURE__ */ jsxRuntime.jsx(
6961
+ core.Select,
6962
+ {
6963
+ ...rest,
6964
+ data,
6965
+ value: value ?? null,
6966
+ onChange: (v) => {
6967
+ if (!v) return onChange(null);
6968
+ const player = playerByCitizen.get(v) ?? null;
6969
+ onChange(player);
6970
+ },
6971
+ searchable: searchableProp ?? true,
6972
+ searchValue: includeOffline ? searchInput : void 0,
6973
+ onSearchChange: includeOffline ? setSearchInput : void 0,
6974
+ placeholder,
6975
+ nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
6976
+ maxDropdownHeight: 300,
6977
+ renderOption,
6978
+ leftSection: selectedPlayer ? /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
6979
+ rightSectionWidth: "3.5vh",
6980
+ rightSectionPointerEvents: "all",
6981
+ rightSection: /* @__PURE__ */ jsxRuntime.jsx(
6982
+ core.ActionIcon,
6983
+ {
6984
+ variant: "subtle",
6985
+ size: "sm",
6986
+ onClick: (e) => {
6987
+ e.stopPropagation();
6988
+ refresh();
6989
+ },
6990
+ "aria-label": refreshLabel,
6991
+ title: refreshLabel,
6992
+ style: { marginRight: "0.6vh" },
6993
+ children: isFetching ? /* @__PURE__ */ jsxRuntime.jsx(core.Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.4vh", color })
6994
+ }
6995
+ )
6996
+ }
6997
+ );
6998
+ }
6999
+ function StatusDot({ online, onlineLabel, offlineLabel }) {
7000
+ return /* @__PURE__ */ jsxRuntime.jsx(
7001
+ "span",
7002
+ {
7003
+ title: online ? onlineLabel : offlineLabel,
7004
+ style: {
7005
+ display: "inline-block",
7006
+ width: "0.9vh",
7007
+ height: "0.9vh",
7008
+ borderRadius: "50%",
7009
+ backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
7010
+ boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
7011
+ flexShrink: 0
7012
+ }
7013
+ }
7014
+ );
7015
+ }
7016
+ function formatLabel(p) {
7017
+ const parts = [p.charName || p.citizenId];
7018
+ if (p.online) {
7019
+ if (p.name) parts.push(p.name);
7020
+ if (p.id != null) parts.push(`#${p.id}`);
7021
+ }
7022
+ return parts.join(" \xB7 ");
7023
+ }
7024
+ var t = (key, fallback) => {
7025
+ const v = locale(key);
7026
+ return v === key ? fallback : v;
7027
+ };
7028
+ var DEFAULT_VALUE = {
7029
+ groups: [],
7030
+ identifiers: []
7031
+ };
7032
+ function SectionLabel({ icon: Icon, label: label2 }) {
7033
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", mt: "xs", children: [
7034
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: "1.4vh", color: "rgba(255,255,255,0.3)" }),
7035
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
7036
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, height: "0.05vh", background: "rgba(255,255,255,0.06)" } })
7037
+ ] });
7038
+ }
7039
+ function EmptyHint({ text }) {
7040
+ return /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.25)", ta: "center", py: "xs", children: text });
7041
+ }
7042
+ function AccessOverrideSection({
7043
+ schemaKey = "access",
7044
+ title,
7045
+ description: description2,
7046
+ groupType,
7047
+ includeOffline = true
7048
+ }) {
7049
+ const mantineTheme = core.useMantineTheme();
7050
+ const color = mantineTheme.colors[mantineTheme.primaryColor][5];
7051
+ const raw = useFormField(schemaKey);
7052
+ const value = {
7053
+ groups: Array.isArray(raw?.groups) ? raw.groups : DEFAULT_VALUE.groups,
7054
+ identifiers: Array.isArray(raw?.identifiers) ? raw.identifiers : DEFAULT_VALUE.identifiers
7055
+ };
7056
+ const { setValue } = useFormActions();
7057
+ const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
7058
+ const [addingGroup, setAddingGroup] = React4.useState(false);
7059
+ const [addingPlayer, setAddingPlayer] = React4.useState(false);
7060
+ const commitGroup = (name) => {
7061
+ if (!name) return;
7062
+ set("groups", [...value.groups, name]);
7063
+ setAddingGroup(false);
7064
+ };
7065
+ const setGroup = (index, name) => {
7066
+ const next = [...value.groups];
7067
+ next[index] = name;
7068
+ set("groups", next.filter((g) => g !== ""));
7069
+ };
7070
+ const removeGroup = (index) => set("groups", value.groups.filter((_, i) => i !== index));
7071
+ const commitIdentifier = (id) => {
7072
+ if (!id) return;
7073
+ set("identifiers", [...value.identifiers, id]);
7074
+ setAddingPlayer(false);
7075
+ };
7076
+ const setIdentifier = (index, id) => {
7077
+ const next = [...value.identifiers];
7078
+ next[index] = id;
7079
+ set("identifiers", next.filter((x) => x !== ""));
7080
+ };
7081
+ const removeIdentifier = (index) => set("identifiers", value.identifiers.filter((_, i) => i !== index));
7082
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7083
+ core.Flex,
7084
+ {
7085
+ direction: "column",
7086
+ gap: "xs",
7087
+ p: "sm",
7088
+ style: { flex: 1, minHeight: 0, overflowY: "auto" },
7089
+ children: [
7090
+ /* @__PURE__ */ jsxRuntime.jsx(
7091
+ AdminPageTitle,
7092
+ {
7093
+ icon: lucideReact.ShieldCheck,
7094
+ title: title || t("Access", "Access"),
7095
+ color
7096
+ }
7097
+ ),
7098
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.4)", children: description2 || t(
7099
+ "AccessOverrideDesc",
7100
+ "Grant access to this resource's admin panel to specific groups and players, in addition to your global dirk_lib admins. Enforcement is server-side."
7101
+ ) }),
7102
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { icon: lucideReact.Users, label: t("AccessGroups", "Groups") }),
7103
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "xxs", children: [
7104
+ value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsxRuntime.jsx(EmptyHint, { text: t("AccessNoGroups", "No groups added.") }),
7105
+ value.groups.map((name, i) => /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7106
+ /* @__PURE__ */ jsxRuntime.jsx(
7107
+ GroupName,
7108
+ {
7109
+ value: name,
7110
+ onChange: (v) => setGroup(i, v),
7111
+ type: groupType,
7112
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7113
+ style: { flex: 1 }
7114
+ }
7115
+ ),
7116
+ /* @__PURE__ */ jsxRuntime.jsx(
7117
+ core.ActionIcon,
7118
+ {
7119
+ variant: "subtle",
7120
+ color: "red",
7121
+ size: "md",
7122
+ onClick: () => removeGroup(i),
7123
+ title: t("Remove", "Remove"),
7124
+ "aria-label": t("Remove", "Remove"),
7125
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7126
+ }
7127
+ )
7128
+ ] }, i)),
7129
+ addingGroup && /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7130
+ /* @__PURE__ */ jsxRuntime.jsx(
7131
+ GroupName,
7132
+ {
7133
+ value: "",
7134
+ onChange: (v) => commitGroup(v),
7135
+ type: groupType,
7136
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7137
+ style: { flex: 1 }
7138
+ }
7139
+ ),
7140
+ /* @__PURE__ */ jsxRuntime.jsx(
7141
+ core.ActionIcon,
7142
+ {
7143
+ variant: "subtle",
7144
+ color: "red",
7145
+ size: "md",
7146
+ onClick: () => setAddingGroup(false),
7147
+ title: t("Remove", "Remove"),
7148
+ "aria-label": t("Remove", "Remove"),
7149
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7150
+ }
7151
+ )
7152
+ ] })
7153
+ ] }),
7154
+ !addingGroup && /* @__PURE__ */ jsxRuntime.jsx(AddRowButton, { label: t("AccessAddGroup", "Add group"), onClick: () => setAddingGroup(true) }),
7155
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { icon: lucideReact.UserRound, label: t("AccessPlayers", "Players") }),
7156
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "xxs", children: [
7157
+ value.identifiers.length === 0 && !addingPlayer && /* @__PURE__ */ jsxRuntime.jsx(EmptyHint, { text: t("AccessNoPlayers", "No players added.") }),
7158
+ value.identifiers.map((id, i) => /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7159
+ /* @__PURE__ */ jsxRuntime.jsx(
7160
+ PlayerSelect,
7161
+ {
7162
+ value: id || null,
7163
+ onChange: (p) => setIdentifier(i, p?.citizenId ?? ""),
7164
+ includeOffline,
7165
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7166
+ style: { flex: 1 },
7167
+ size: "xs"
7168
+ }
7169
+ ),
7170
+ /* @__PURE__ */ jsxRuntime.jsx(
7171
+ core.ActionIcon,
7172
+ {
7173
+ variant: "subtle",
7174
+ color: "red",
7175
+ size: "md",
7176
+ onClick: () => removeIdentifier(i),
7177
+ title: t("Remove", "Remove"),
7178
+ "aria-label": t("Remove", "Remove"),
7179
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7180
+ }
7181
+ )
7182
+ ] }, i)),
7183
+ addingPlayer && /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7184
+ /* @__PURE__ */ jsxRuntime.jsx(
7185
+ PlayerSelect,
7186
+ {
7187
+ value: null,
7188
+ onChange: (p) => commitIdentifier(p?.citizenId ?? ""),
7189
+ includeOffline,
7190
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7191
+ style: { flex: 1 },
7192
+ size: "xs"
7193
+ }
7194
+ ),
7195
+ /* @__PURE__ */ jsxRuntime.jsx(
7196
+ core.ActionIcon,
7197
+ {
7198
+ variant: "subtle",
7199
+ color: "red",
7200
+ size: "md",
7201
+ onClick: () => setAddingPlayer(false),
7202
+ title: t("Remove", "Remove"),
7203
+ "aria-label": t("Remove", "Remove"),
7204
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7205
+ }
7206
+ )
7207
+ ] })
7208
+ ] }),
7209
+ !addingPlayer && /* @__PURE__ */ jsxRuntime.jsx(AddRowButton, { label: t("AccessAddPlayer", "Add player"), onClick: () => setAddingPlayer(true) })
7210
+ ]
7211
+ }
7212
+ );
7213
+ }
7214
+ function AddRowButton({ label: label2, onClick }) {
7215
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7216
+ core.Flex,
7217
+ {
7218
+ align: "center",
7219
+ justify: "center",
7220
+ gap: "xs",
7221
+ onClick,
7222
+ role: "button",
7223
+ tabIndex: 0,
7224
+ onKeyDown: (e) => {
7225
+ if (e.key === "Enter" || e.key === " ") {
7226
+ e.preventDefault();
7227
+ onClick();
7228
+ }
7229
+ },
7230
+ py: "xs",
7231
+ style: {
7232
+ cursor: "pointer",
7233
+ border: "0.1vh dashed rgba(255,255,255,0.12)",
7234
+ borderRadius: "0.4vh",
7235
+ transition: "background 0.15s, border-color 0.15s"
7236
+ },
7237
+ onMouseEnter: (e) => {
7238
+ e.currentTarget.style.background = "rgba(255,255,255,0.03)";
7239
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.2)";
7240
+ },
7241
+ onMouseLeave: (e) => {
7242
+ e.currentTarget.style.background = "transparent";
7243
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.12)";
7244
+ },
7245
+ children: [
7246
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: "1.4vh", color: "rgba(255,255,255,0.4)" }),
7247
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "rgba(255,255,255,0.4)", children: label2 })
7248
+ ]
7249
+ }
7250
+ );
7251
+ }
6833
7252
 
6834
7253
  // src/utils/gtaAnimPostFx.ts
6835
7254
  var GTA_ANIM_POST_FX_GROUP_ORDER = [
@@ -7419,7 +7838,7 @@ function ScenarioSelect({
7419
7838
  }
7420
7839
  );
7421
7840
  }
7422
- var t = (key, fallback) => {
7841
+ var t2 = (key, fallback) => {
7423
7842
  const v = locale(key);
7424
7843
  return v === key ? fallback : v;
7425
7844
  };
@@ -7487,7 +7906,7 @@ function DiscordRoleSelect(props) {
7487
7906
  return map;
7488
7907
  }, [roles]);
7489
7908
  if (errorCode === "NotConfigured") {
7490
- const notConfiguredPlaceholder = t(
7909
+ const notConfiguredPlaceholder = t2(
7491
7910
  "DiscordNotConfigured",
7492
7911
  "Not configured \u2014 set in /dirk_lib"
7493
7912
  );
@@ -7555,9 +7974,9 @@ function DiscordRoleSelect(props) {
7555
7974
  e.stopPropagation();
7556
7975
  refresh();
7557
7976
  },
7558
- title: t("Refresh", "Refresh"),
7977
+ title: t2("Refresh", "Refresh"),
7559
7978
  loading,
7560
- "aria-label": t("Refresh", "Refresh"),
7979
+ "aria-label": t2("Refresh", "Refresh"),
7561
7980
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.2vh" })
7562
7981
  }
7563
7982
  );
@@ -7567,7 +7986,7 @@ function DiscordRoleSelect(props) {
7567
7986
  {
7568
7987
  label: label2,
7569
7988
  size,
7570
- placeholder: placeholder || t("PickRoles", "Pick roles..."),
7989
+ placeholder: placeholder || t2("PickRoles", "Pick roles..."),
7571
7990
  data,
7572
7991
  value: props.value || [],
7573
7992
  onChange: (ids) => props.onChange(ids),
@@ -7579,7 +7998,7 @@ function DiscordRoleSelect(props) {
7579
7998
  rightSectionPointerEvents: "all",
7580
7999
  style,
7581
8000
  styles,
7582
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
8001
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7583
8002
  }
7584
8003
  );
7585
8004
  }
@@ -7588,7 +8007,7 @@ function DiscordRoleSelect(props) {
7588
8007
  {
7589
8008
  label: label2,
7590
8009
  size,
7591
- placeholder: placeholder || t("PickRole", "Pick a role..."),
8010
+ placeholder: placeholder || t2("PickRole", "Pick a role..."),
7592
8011
  data,
7593
8012
  value: props.value || null,
7594
8013
  onChange: (id) => props.onChange(id),
@@ -7600,11 +8019,11 @@ function DiscordRoleSelect(props) {
7600
8019
  rightSectionPointerEvents: "all",
7601
8020
  style,
7602
8021
  styles,
7603
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
8022
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7604
8023
  }
7605
8024
  );
7606
8025
  }
7607
- var t2 = (key, fallback) => {
8026
+ var t3 = (key, fallback) => {
7608
8027
  const v = locale(key);
7609
8028
  return v === key ? fallback : v;
7610
8029
  };
@@ -7625,7 +8044,7 @@ function FrameworkHint({ framework }) {
7625
8044
  const theme2 = core.useMantineTheme();
7626
8045
  if (!framework) return null;
7627
8046
  const hintKey = `AccountFrameworkHint_${framework}`;
7628
- const text = t2(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
8047
+ const text = t3(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
7629
8048
  return /* @__PURE__ */ jsxRuntime.jsxs(
7630
8049
  core.Flex,
7631
8050
  {
@@ -7709,9 +8128,9 @@ function AccountSelect(props) {
7709
8128
  e.stopPropagation();
7710
8129
  refresh();
7711
8130
  },
7712
- title: t2("Refresh", "Refresh"),
8131
+ title: t3("Refresh", "Refresh"),
7713
8132
  loading,
7714
- "aria-label": t2("Refresh", "Refresh"),
8133
+ "aria-label": t3("Refresh", "Refresh"),
7715
8134
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.2vh" })
7716
8135
  }
7717
8136
  );
@@ -7725,14 +8144,14 @@ function AccountSelect(props) {
7725
8144
  rightSection: refreshButton,
7726
8145
  rightSectionPointerEvents: "all",
7727
8146
  style,
7728
- nothingFoundMessage: t2("NoAccounts", "No accounts available")
8147
+ nothingFoundMessage: t3("NoAccounts", "No accounts available")
7729
8148
  };
7730
8149
  return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "0.3vh", style, children: [
7731
8150
  props.multi ? /* @__PURE__ */ jsxRuntime.jsx(
7732
8151
  core.MultiSelect,
7733
8152
  {
7734
8153
  ...sharedSelectProps,
7735
- placeholder: placeholder || t2("PickAccounts", "Pick accounts (or type custom)..."),
8154
+ placeholder: placeholder || t3("PickAccounts", "Pick accounts (or type custom)..."),
7736
8155
  value: props.value || [],
7737
8156
  onChange: (names) => props.onChange(names),
7738
8157
  renderOption,
@@ -7744,7 +8163,7 @@ function AccountSelect(props) {
7744
8163
  core.Select,
7745
8164
  {
7746
8165
  ...sharedSelectProps,
7747
- placeholder: placeholder || t2("PickAccount", "Pick an account..."),
8166
+ placeholder: placeholder || t3("PickAccount", "Pick an account..."),
7748
8167
  value: props.value || null,
7749
8168
  onChange: (name) => props.onChange(name),
7750
8169
  renderOption,
@@ -8036,175 +8455,6 @@ function WorldPositionGotoButton2({
8036
8455
  }
8037
8456
  ) });
8038
8457
  }
8039
- function usePlayers(opts = {}) {
8040
- const {
8041
- includeOffline = false,
8042
- search = "",
8043
- limit = 50,
8044
- staleTimeMs,
8045
- refetchIntervalMs
8046
- } = opts;
8047
- const query = reactQuery.useQuery({
8048
- queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
8049
- queryFn: async () => {
8050
- const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
8051
- const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
8052
- const result = await fetchNui(
8053
- "ADMIN_TOOL_QUERY",
8054
- payload,
8055
- // Browser-dev fallback. Returns a couple of mock players so the
8056
- // dev shell isn't blank.
8057
- includeOffline ? [
8058
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
8059
- { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
8060
- ] : [
8061
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
8062
- ]
8063
- );
8064
- return Array.isArray(result) ? result : [];
8065
- },
8066
- staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
8067
- gcTime: 5 * 6e4,
8068
- refetchInterval: refetchIntervalMs ?? false,
8069
- refetchOnWindowFocus: false,
8070
- placeholderData: includeOffline ? reactQuery.keepPreviousData : void 0
8071
- });
8072
- return {
8073
- players: query.data ?? [],
8074
- isLoading: query.isLoading,
8075
- isFetching: query.isFetching,
8076
- error: query.error ?? null,
8077
- refresh: () => query.refetch()
8078
- };
8079
- }
8080
- var DEBOUNCE_MS = 300;
8081
- var ONLINE_COLOR = "#3FA83F";
8082
- var OFFLINE_COLOR = "#E54141";
8083
- function PlayerSelect({
8084
- value,
8085
- onChange,
8086
- includeOffline = false,
8087
- limit = 50,
8088
- searchable: searchableProp,
8089
- placeholder,
8090
- nothingFoundMessage: nothingFoundMessageProp,
8091
- loadingLabel = "Loading\u2026",
8092
- onlineLabel = "Online",
8093
- offlineLabel = "Offline",
8094
- refreshLabel = "Refresh player list",
8095
- ...rest
8096
- }) {
8097
- const theme2 = core.useMantineTheme();
8098
- const color = theme2.colors[theme2.primaryColor][5];
8099
- const [searchInput, setSearchInput] = React4.useState("");
8100
- const [debouncedSearch, setDebouncedSearch] = React4.useState("");
8101
- const { players, isFetching, refresh } = usePlayers({
8102
- includeOffline,
8103
- search: includeOffline ? debouncedSearch : void 0,
8104
- limit
8105
- });
8106
- const sortedPlayers = React4.useMemo(
8107
- () => [...players].sort((a, b) => {
8108
- if (a.online !== b.online) return a.online ? -1 : 1;
8109
- return a.charName.localeCompare(b.charName);
8110
- }),
8111
- [players]
8112
- );
8113
- const playerByCitizen = React4.useMemo(() => {
8114
- const m = /* @__PURE__ */ new Map();
8115
- for (const p of sortedPlayers) m.set(p.citizenId, p);
8116
- return m;
8117
- }, [sortedPlayers]);
8118
- const data = React4.useMemo(() => {
8119
- const items = sortedPlayers.map((p) => ({
8120
- value: p.citizenId,
8121
- label: formatLabel(p)
8122
- }));
8123
- if (value && !items.some((i) => i.value === value)) {
8124
- items.unshift({ value, label: value });
8125
- }
8126
- return items;
8127
- }, [sortedPlayers, value]);
8128
- const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
8129
- const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
8130
- React4.useEffect(() => {
8131
- if (!includeOffline) return;
8132
- if (selectedLabel && searchInput === selectedLabel) return;
8133
- const t3 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
8134
- return () => clearTimeout(t3);
8135
- }, [searchInput, includeOffline, selectedLabel]);
8136
- const renderOption = ({ option }) => {
8137
- const p = playerByCitizen.get(option.value);
8138
- if (!p) return option.label;
8139
- return /* @__PURE__ */ jsxRuntime.jsxs(core.Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
8140
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
8141
- /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
8142
- ] });
8143
- };
8144
- return /* @__PURE__ */ jsxRuntime.jsx(
8145
- core.Select,
8146
- {
8147
- ...rest,
8148
- data,
8149
- value: value ?? null,
8150
- onChange: (v) => {
8151
- if (!v) return onChange(null);
8152
- const player = playerByCitizen.get(v) ?? null;
8153
- onChange(player);
8154
- },
8155
- searchable: searchableProp ?? true,
8156
- searchValue: includeOffline ? searchInput : void 0,
8157
- onSearchChange: includeOffline ? setSearchInput : void 0,
8158
- placeholder,
8159
- nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
8160
- maxDropdownHeight: 300,
8161
- renderOption,
8162
- leftSection: selectedPlayer ? /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
8163
- rightSectionWidth: "3.5vh",
8164
- rightSectionPointerEvents: "all",
8165
- rightSection: /* @__PURE__ */ jsxRuntime.jsx(
8166
- core.ActionIcon,
8167
- {
8168
- variant: "subtle",
8169
- size: "sm",
8170
- onClick: (e) => {
8171
- e.stopPropagation();
8172
- refresh();
8173
- },
8174
- "aria-label": refreshLabel,
8175
- title: refreshLabel,
8176
- style: { marginRight: "0.6vh" },
8177
- children: isFetching ? /* @__PURE__ */ jsxRuntime.jsx(core.Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.4vh", color })
8178
- }
8179
- )
8180
- }
8181
- );
8182
- }
8183
- function StatusDot({ online, onlineLabel, offlineLabel }) {
8184
- return /* @__PURE__ */ jsxRuntime.jsx(
8185
- "span",
8186
- {
8187
- title: online ? onlineLabel : offlineLabel,
8188
- style: {
8189
- display: "inline-block",
8190
- width: "0.9vh",
8191
- height: "0.9vh",
8192
- borderRadius: "50%",
8193
- backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
8194
- boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
8195
- flexShrink: 0
8196
- }
8197
- }
8198
- );
8199
- }
8200
- function formatLabel(p) {
8201
- const parts = [p.charName || p.citizenId];
8202
- if (p.online) {
8203
- if (p.name) parts.push(p.name);
8204
- if (p.id != null) parts.push(`#${p.id}`);
8205
- }
8206
- return parts.join(" \xB7 ");
8207
- }
8208
8458
  function usePickDoor() {
8209
8459
  const begin = useAdminToolStore((s) => s.begin);
8210
8460
  return async () => {
@@ -8596,6 +8846,7 @@ var Vector4Schema = zod.z.object({
8596
8846
  w: zod.z.number()
8597
8847
  });
8598
8848
 
8849
+ exports.AccessOverrideSection = AccessOverrideSection;
8599
8850
  exports.AccountSelect = AccountSelect;
8600
8851
  exports.AdminPageTitle = AdminPageTitle;
8601
8852
  exports.AnimPostFxSelect = AnimPostFxSelect;
@@ -8610,6 +8861,7 @@ exports.ConfirmModal = ConfirmModal;
8610
8861
  exports.ControlMultiSelect = ControlMultiSelect;
8611
8862
  exports.ControlSelect = ControlSelect;
8612
8863
  exports.Counter = Counter;
8864
+ exports.DEFAULT_PALETTE = DEFAULT_PALETTE;
8613
8865
  exports.DirkProvider = DirkProvider;
8614
8866
  exports.DiscordRoleSelect = DiscordRoleSelect;
8615
8867
  exports.DoorPickerButton = DoorPickerButton;
@@ -8650,6 +8902,7 @@ exports.SegmentedProgress = SegmentedProgress;
8650
8902
  exports.SelectItem = SelectItem;
8651
8903
  exports.SwitchPanel = SwitchPanel;
8652
8904
  exports.TestBed = TestBed;
8905
+ exports.ThemeOverrideSchema = ThemeOverrideSchema;
8653
8906
  exports.ThemeOverrideSection = ThemeOverrideSection;
8654
8907
  exports.Title = Title;
8655
8908
  exports.TornEdgeSVGFilter = TornEdgeSVGFilter;
@@ -8670,6 +8923,7 @@ exports.copyToClipboard = copyToClipboard;
8670
8923
  exports.createFormStore = createFormStore;
8671
8924
  exports.createScriptConfig = createScriptConfig;
8672
8925
  exports.createSkill = createSkill;
8926
+ exports.defaultThemeOverride = defaultThemeOverride;
8673
8927
  exports.dirkQueryClient = dirkQueryClient;
8674
8928
  exports.ensureFrameworkGroups = ensureFrameworkGroups;
8675
8929
  exports.extractDefaults = extractDefaults;