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.js CHANGED
@@ -2,10 +2,11 @@ import { Flex, Text, Image as Image$1, createTheme, alpha, Box, Stack, Title as
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import React4, { createContext, memo, useRef, useEffect, useContext, useState, useCallback, useMemo, useLayoutEffect } from 'react';
4
4
  import { create, useStore, createStore } from 'zustand';
5
+ import { z } from 'zod';
5
6
  import axios from 'axios';
6
7
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7
8
  import { motion, AnimatePresence, useMotionValue } from 'framer-motion';
8
- import { Info, X, AlertTriangle, Trash2, RefreshCw, ChevronDown, Check, Copy, MapPin, Crosshair, EyeOff, Eye, RotateCcw, FlaskConical, ChevronUp, Palette, DoorOpen, Plus, Minus, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
9
+ import { Info, X, AlertTriangle, Trash2, RefreshCw, ChevronDown, Check, Copy, MapPin, Crosshair, EyeOff, Eye, RotateCcw, FlaskConical, ChevronUp, Palette, ShieldCheck, Users, UserRound, Plus, DoorOpen, Minus, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
9
10
  import clickSoundUrl from './click_sound-PNCRRTM4.mp3';
10
11
  import hoverSoundUrl from './hover_sound-NBUA222C.mp3';
11
12
  import { notifications } from '@mantine/notifications';
@@ -26,7 +27,6 @@ import { CRS, tileLayer, latLngBounds, latLng } from 'leaflet';
26
27
  import 'leaflet/dist/leaflet.css';
27
28
  import { MapContainer, useMap } from 'react-leaflet';
28
29
  import { Marker } from '@adamscybot/react-leaflet-component-marker';
29
- import { z } from 'zod';
30
30
 
31
31
  var __defProp = Object.defineProperty;
32
32
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -1312,6 +1312,32 @@ var openLink = (url) => {
1312
1312
  window.invokeNative("openLink", url);
1313
1313
  }
1314
1314
  };
1315
+ var DEFAULT_PALETTE = [
1316
+ "#f0f4ff",
1317
+ "#d9e3ff",
1318
+ "#bfcfff",
1319
+ "#a6bbff",
1320
+ "#8ca7ff",
1321
+ "#7393ff",
1322
+ "#5a7fff",
1323
+ "#406bff",
1324
+ "#2547ff",
1325
+ "#0b33ff"
1326
+ ];
1327
+ var ThemeOverrideSchema = z.object({
1328
+ useOverride: z.boolean(),
1329
+ primaryColor: z.string(),
1330
+ primaryShade: z.number(),
1331
+ customTheme: z.array(z.string())
1332
+ });
1333
+ var defaultThemeOverride = {
1334
+ useOverride: false,
1335
+ primaryColor: "dirk",
1336
+ primaryShade: 5,
1337
+ customTheme: DEFAULT_PALETTE
1338
+ };
1339
+
1340
+ // src/utils/useSettings.ts
1315
1341
  var useSettings = create(() => ({
1316
1342
  currency: "$",
1317
1343
  game: "fivem",
@@ -1319,18 +1345,7 @@ var useSettings = create(() => ({
1319
1345
  primaryShade: 9,
1320
1346
  itemImgPath: "",
1321
1347
  resourceVersion: "dev",
1322
- customTheme: [
1323
- "#f0f4ff",
1324
- "#d9e3ff",
1325
- "#bfcfff",
1326
- "#a6bbff",
1327
- "#8ca7ff",
1328
- "#7393ff",
1329
- "#5a7fff",
1330
- "#406bff",
1331
- "#2547ff",
1332
- "#0b33ff"
1333
- ]
1348
+ customTheme: DEFAULT_PALETTE
1334
1349
  }));
1335
1350
 
1336
1351
  // src/utils/fetchNui.ts
@@ -1414,11 +1429,35 @@ function reportMissingLocale(key) {
1414
1429
  fetchNui("REPORT_MISSING_LOCALE", { key }).catch(() => {
1415
1430
  });
1416
1431
  }
1432
+ var PACKAGE_DEFAULTS = {
1433
+ "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.",
1434
+ "cfgpanel_discard": "Discard",
1435
+ "cfgpanel_manual_edit": "Manual Edit",
1436
+ "cfgpanel_reset_defaults": "Reset Defaults",
1437
+ "cfgpanel_version": "Version",
1438
+ "cfgpanel_back_title": "Back to script list",
1439
+ "cfgpanel_undo": "Undo",
1440
+ "cfgpanel_redo": "Redo",
1441
+ "cfgpanel_save": "Save",
1442
+ "cfgpanel_saving": "Saving...",
1443
+ "cfgpanel_history": "History",
1444
+ "cfgpanel_reset_title": "Reset to Defaults",
1445
+ "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.",
1446
+ "cfgpanel_reset_confirm": "Reset Config",
1447
+ "cfgpanel_discard_title": "Discard Unsaved Changes?",
1448
+ "cfgpanel_discard_desc_back": "You have unsaved changes. Going back now will discard them.",
1449
+ "cfgpanel_discard_desc_close": "You have unsaved changes. Closing now will discard them.",
1450
+ "cfgpanel_discard_confirm_back": "Go Back Without Saving",
1451
+ "cfgpanel_discard_confirm_close": "Close Without Saving",
1452
+ "cfgpanel_json_title": "Config JSON",
1453
+ "cfgpanel_cancel": "Cancel",
1454
+ "cfgpanel_apply": "Apply",
1455
+ "cfgpanel_history_title": "Config History",
1456
+ "cfgpanel_close": "Close"
1457
+ };
1417
1458
  var localeStore = create((set, get) => {
1418
1459
  return {
1419
- locales: {
1420
- "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."
1421
- },
1460
+ locales: { ...PACKAGE_DEFAULTS },
1422
1461
  locale: (key, ...args) => {
1423
1462
  const exists = get().locales[key];
1424
1463
  if (!exists) reportMissingLocale(key);
@@ -1432,7 +1471,7 @@ var localeStore = create((set, get) => {
1432
1471
  });
1433
1472
  var locale = localeStore.getState().locale;
1434
1473
  registerInitialFetch("GET_LOCALES", void 0).then((data) => {
1435
- localeStore.setState({ locales: data });
1474
+ localeStore.setState({ locales: { ...PACKAGE_DEFAULTS, ...data } });
1436
1475
  }).catch(() => {
1437
1476
  });
1438
1477
  if (typeof window !== "undefined") {
@@ -1441,7 +1480,7 @@ if (typeof window !== "undefined") {
1441
1480
  if (!msg || msg.action !== "UPDATE_DIRK_LIB_LOCALES") return;
1442
1481
  if (!msg.data || typeof msg.data !== "object") return;
1443
1482
  if (Object.keys(msg.data).length === 0) return;
1444
- localeStore.setState({ locales: msg.data });
1483
+ localeStore.setState({ locales: { ...PACKAGE_DEFAULTS, ...msg.data } });
1445
1484
  });
1446
1485
  }
1447
1486
 
@@ -3718,11 +3757,11 @@ function ConfirmModal({
3718
3757
  placeholder: confirmText,
3719
3758
  value: typed,
3720
3759
  onChange: (e) => setTyped(e.currentTarget.value),
3721
- styles: (t3) => ({
3760
+ styles: (t4) => ({
3722
3761
  input: {
3723
- backgroundColor: alpha(t3.colors.dark[7], 0.5),
3762
+ backgroundColor: alpha(t4.colors.dark[7], 0.5),
3724
3763
  border: `0.1vh solid ${alpha(
3725
- typed === confirmText ? "#ef4444" : t3.colors.dark[5],
3764
+ typed === confirmText ? "#ef4444" : t4.colors.dark[5],
3726
3765
  0.5
3727
3766
  )}`,
3728
3767
  color: "rgba(255,255,255,0.85)",
@@ -4400,7 +4439,7 @@ function useAdminState(key, initial) {
4400
4439
  useEffect(() => {
4401
4440
  const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
4402
4441
  listeners.set(key, set);
4403
- const handler = () => setTick((t3) => t3 + 1);
4442
+ const handler = () => setTick((t4) => t4 + 1);
4404
4443
  set.add(handler);
4405
4444
  return () => {
4406
4445
  set.delete(handler);
@@ -5012,7 +5051,7 @@ function ConfigJsonModal({
5012
5051
  setError(e.message);
5013
5052
  }
5014
5053
  };
5015
- return /* @__PURE__ */ jsxs(Modal, { title: "Config JSON", icon: Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
5054
+ return /* @__PURE__ */ jsxs(Modal, { title: locale("cfgpanel_json_title"), icon: Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
5016
5055
  /* @__PURE__ */ jsxs(Box, { flex: 1, p: "0.8vh", style: { overflowY: "auto" }, children: [
5017
5056
  /* @__PURE__ */ jsx(
5018
5057
  JsonInput,
@@ -5040,7 +5079,7 @@ function ConfigJsonModal({
5040
5079
  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 },
5041
5080
  children: [
5042
5081
  /* @__PURE__ */ jsx(X, { color: "rgba(255,255,255,0.4)" }),
5043
- /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: "Cancel" })
5082
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: locale("cfgpanel_cancel") })
5044
5083
  ]
5045
5084
  }
5046
5085
  ),
@@ -5053,7 +5092,7 @@ function ConfigJsonModal({
5053
5092
  style: { background: alpha(color, 0.14), border: `0.1vh solid ${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 },
5054
5093
  children: [
5055
5094
  /* @__PURE__ */ jsx(Check, { color }),
5056
- /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: color, children: "Apply" })
5095
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: color, children: locale("cfgpanel_apply") })
5057
5096
  ]
5058
5097
  }
5059
5098
  )
@@ -5194,7 +5233,7 @@ function ConfigHistoryModal({
5194
5233
  historyQuery.fetchNextPage();
5195
5234
  }
5196
5235
  };
5197
- return /* @__PURE__ */ jsxs(Modal, { title: "Config History", icon: History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
5236
+ return /* @__PURE__ */ jsxs(Modal, { title: locale("cfgpanel_history_title"), icon: History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
5198
5237
  /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minHeight: 0 }, children: [
5199
5238
  /* @__PURE__ */ jsxs(Flex, { gap: "xs", p: "sm", style: { borderBottom: `0.1vh solid ${alpha(theme2.colors.dark[7], 0.8)}` }, children: [
5200
5239
  /* @__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 } }),
@@ -5237,7 +5276,7 @@ function ConfigHistoryModal({
5237
5276
  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 },
5238
5277
  children: [
5239
5278
  /* @__PURE__ */ jsx(X, { color: "rgba(255,255,255,0.4)", size: "1.5vh" }),
5240
- /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: "Close" })
5279
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: locale("cfgpanel_close") })
5241
5280
  ]
5242
5281
  }
5243
5282
  ) })
@@ -5297,9 +5336,9 @@ function ConfigPanelInner({
5297
5336
  /* @__PURE__ */ jsx(AnimatePresence, { children: resetOpen && /* @__PURE__ */ jsx(
5298
5337
  ConfirmModal,
5299
5338
  {
5300
- title: "Reset to Defaults",
5301
- description: "This will permanently reset ALL config back to the defaults. Every value you have configured will be overwritten. This cannot be undone.",
5302
- confirmLabel: "Reset Config",
5339
+ title: locale("cfgpanel_reset_title"),
5340
+ description: locale("cfgpanel_reset_desc"),
5341
+ confirmLabel: locale("cfgpanel_reset_confirm"),
5303
5342
  confirmText: resetConfirmText,
5304
5343
  onConfirm: async () => {
5305
5344
  setResetOpen(false);
@@ -5331,9 +5370,9 @@ function ConfigPanelInner({
5331
5370
  /* @__PURE__ */ jsx(AnimatePresence, { children: pendingAction !== null && /* @__PURE__ */ jsx(
5332
5371
  ConfirmModal,
5333
5372
  {
5334
- title: "Discard Unsaved Changes?",
5335
- description: pendingAction === "back" ? "You have unsaved changes. Going back now will discard them." : "You have unsaved changes. Closing now will discard them.",
5336
- confirmLabel: pendingAction === "back" ? "Go Back Without Saving" : "Close Without Saving",
5373
+ title: locale("cfgpanel_discard_title"),
5374
+ description: pendingAction === "back" ? locale("cfgpanel_discard_desc_back") : locale("cfgpanel_discard_desc_close"),
5375
+ confirmLabel: pendingAction === "back" ? locale("cfgpanel_discard_confirm_back") : locale("cfgpanel_discard_confirm_close"),
5337
5376
  onConfirm: () => {
5338
5377
  const action = pendingAction;
5339
5378
  setPendingAction(null);
@@ -5371,7 +5410,7 @@ function ConfigPanelInner({
5371
5410
  /* @__PURE__ */ jsx(
5372
5411
  motion.button,
5373
5412
  {
5374
- title: "Back to script list",
5413
+ title: locale("cfgpanel_back_title"),
5375
5414
  onClick: handleBack,
5376
5415
  whileHover: { background: alpha(color, 0.16), borderColor: alpha(color, 0.45) },
5377
5416
  whileTap: { scale: 0.95 },
@@ -5399,7 +5438,7 @@ function ConfigPanelInner({
5399
5438
  /* @__PURE__ */ jsx(
5400
5439
  motion.button,
5401
5440
  {
5402
- title: "Undo",
5441
+ title: locale("cfgpanel_undo"),
5403
5442
  onClick: () => form.canBack && form.back(),
5404
5443
  disabled: !form.canBack,
5405
5444
  whileHover: form.canBack ? { background: "rgba(255,255,255,0.07)" } : void 0,
@@ -5411,7 +5450,7 @@ function ConfigPanelInner({
5411
5450
  /* @__PURE__ */ jsx(
5412
5451
  motion.button,
5413
5452
  {
5414
- title: "Redo",
5453
+ title: locale("cfgpanel_redo"),
5415
5454
  onClick: () => form.canForward && form.forward(),
5416
5455
  disabled: !form.canForward,
5417
5456
  whileHover: form.canForward ? { background: "rgba(255,255,255,0.07)" } : void 0,
@@ -5423,7 +5462,7 @@ function ConfigPanelInner({
5423
5462
  /* @__PURE__ */ jsx(
5424
5463
  motion.button,
5425
5464
  {
5426
- title: isSaving ? "Saving..." : isDirty ? `Save (${changedCount})` : "Save",
5465
+ title: isSaving ? locale("cfgpanel_saving") : isDirty ? `${locale("cfgpanel_save")} (${changedCount})` : locale("cfgpanel_save"),
5427
5466
  onClick: () => isDirty && !isSaving && form.submit(),
5428
5467
  disabled: !isDirty || isSaving,
5429
5468
  whileHover: isDirty && !isSaving ? { background: alpha(color, 0.25), borderColor: alpha(color, 0.5) } : void 0,
@@ -5435,7 +5474,7 @@ function ConfigPanelInner({
5435
5474
  /* @__PURE__ */ jsx(
5436
5475
  motion.button,
5437
5476
  {
5438
- title: "History",
5477
+ title: locale("cfgpanel_history"),
5439
5478
  onClick: () => setHistoryOpen(true),
5440
5479
  whileHover: { background: alpha("#22d3ee", 0.16), borderColor: alpha("#22d3ee", 0.5) },
5441
5480
  whileTap: { scale: 0.97 },
@@ -5456,7 +5495,7 @@ function ConfigPanelInner({
5456
5495
  style: { width: "100%", background: "transparent", border: `0.1vh solid ${isDirty ? alpha("#f97316", 0.35) : 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" },
5457
5496
  children: [
5458
5497
  /* @__PURE__ */ jsx(XCircle, { color: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", size: "1.6vh" }),
5459
- /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", children: "Discard" })
5498
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", children: locale("cfgpanel_discard") })
5460
5499
  ]
5461
5500
  }
5462
5501
  ),
@@ -5470,7 +5509,7 @@ function ConfigPanelInner({
5470
5509
  style: { width: "100%", background: alpha(color, 0.07), border: `0.1vh solid ${alpha(color, 0.28)}`, borderRadius: theme2.radius.xs, padding: "0.65vh 0.8vh", cursor: "pointer", display: "flex", alignItems: "center", gap: "0.55vh" },
5471
5510
  children: [
5472
5511
  /* @__PURE__ */ jsx(Code2, { color, size: "1.6vh" }),
5473
- /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: "Manual Edit" })
5512
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: locale("cfgpanel_manual_edit") })
5474
5513
  ]
5475
5514
  }
5476
5515
  ),
@@ -5483,12 +5522,13 @@ function ConfigPanelInner({
5483
5522
  style: { width: "100%", background: alpha("#ef4444", 0.07), border: `0.1vh solid ${alpha("#ef4444", 0.25)}`, borderRadius: theme2.radius.xs, padding: "0.65vh 0.8vh", cursor: "pointer", display: "flex", alignItems: "center", gap: "0.55vh" },
5484
5523
  children: [
5485
5524
  /* @__PURE__ */ jsx(RotateCcw, { color: "#ef4444", size: "1.6vh" }),
5486
- /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "#ef4444", children: "Reset Defaults" })
5525
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "#ef4444", children: locale("cfgpanel_reset_defaults") })
5487
5526
  ]
5488
5527
  }
5489
5528
  ),
5490
5529
  version && /* @__PURE__ */ jsxs(Text, { size: "0.95vh", c: "dimmed", ta: "center", style: { letterSpacing: "0.04em", opacity: 0.8 }, children: [
5491
- "Version ",
5530
+ locale("cfgpanel_version"),
5531
+ " ",
5492
5532
  version
5493
5533
  ] })
5494
5534
  ] })
@@ -6579,24 +6619,6 @@ var MANTINE_COLOR_OPTIONS = [
6579
6619
  "yellow",
6580
6620
  "orange"
6581
6621
  ].map((value) => ({ value, label: value }));
6582
- var DEFAULT_PALETTE = [
6583
- "#f0f4ff",
6584
- "#d9e3ff",
6585
- "#bfcfff",
6586
- "#a6bbff",
6587
- "#8ca7ff",
6588
- "#7393ff",
6589
- "#5a7fff",
6590
- "#406bff",
6591
- "#2547ff",
6592
- "#0b33ff"
6593
- ];
6594
- var DEFAULT_VALUE = {
6595
- useOverride: false,
6596
- primaryColor: "dirk",
6597
- primaryShade: 5,
6598
- customTheme: DEFAULT_PALETTE
6599
- };
6600
6622
  function GroupLabel({ label: label2 }) {
6601
6623
  return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", mt: "xxs", children: [
6602
6624
  /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
@@ -6611,10 +6633,10 @@ function ThemeOverrideSection({
6611
6633
  const color = mantineTheme.colors[mantineTheme.primaryColor][5];
6612
6634
  const raw = useFormField(schemaKey);
6613
6635
  const value = {
6614
- useOverride: raw?.useOverride ?? DEFAULT_VALUE.useOverride,
6615
- primaryColor: raw?.primaryColor ?? DEFAULT_VALUE.primaryColor,
6616
- primaryShade: raw?.primaryShade ?? DEFAULT_VALUE.primaryShade,
6617
- customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : DEFAULT_VALUE.customTheme
6636
+ useOverride: raw?.useOverride ?? defaultThemeOverride.useOverride,
6637
+ primaryColor: raw?.primaryColor ?? defaultThemeOverride.primaryColor,
6638
+ primaryShade: raw?.primaryShade ?? defaultThemeOverride.primaryShade,
6639
+ customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : defaultThemeOverride.customTheme
6618
6640
  };
6619
6641
  const { setValue } = useFormActions();
6620
6642
  const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
@@ -6821,6 +6843,403 @@ function SwatchTile({
6821
6843
  ) })
6822
6844
  ] });
6823
6845
  }
6846
+ function usePlayers(opts = {}) {
6847
+ const {
6848
+ includeOffline = false,
6849
+ search = "",
6850
+ limit = 50,
6851
+ staleTimeMs,
6852
+ refetchIntervalMs
6853
+ } = opts;
6854
+ const query = useQuery({
6855
+ queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
6856
+ queryFn: async () => {
6857
+ const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
6858
+ const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
6859
+ const result = await fetchNui(
6860
+ "ADMIN_TOOL_QUERY",
6861
+ payload,
6862
+ // Browser-dev fallback. Returns a couple of mock players so the
6863
+ // dev shell isn't blank.
6864
+ includeOffline ? [
6865
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
6866
+ { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
6867
+ ] : [
6868
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
6869
+ ]
6870
+ );
6871
+ return Array.isArray(result) ? result : [];
6872
+ },
6873
+ staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
6874
+ gcTime: 5 * 6e4,
6875
+ refetchInterval: refetchIntervalMs ?? false,
6876
+ refetchOnWindowFocus: false,
6877
+ placeholderData: includeOffline ? keepPreviousData : void 0
6878
+ });
6879
+ return {
6880
+ players: query.data ?? [],
6881
+ isLoading: query.isLoading,
6882
+ isFetching: query.isFetching,
6883
+ error: query.error ?? null,
6884
+ refresh: () => query.refetch()
6885
+ };
6886
+ }
6887
+ var DEBOUNCE_MS = 300;
6888
+ var ONLINE_COLOR = "#3FA83F";
6889
+ var OFFLINE_COLOR = "#E54141";
6890
+ function PlayerSelect({
6891
+ value,
6892
+ onChange,
6893
+ includeOffline = false,
6894
+ limit = 50,
6895
+ searchable: searchableProp,
6896
+ placeholder,
6897
+ nothingFoundMessage: nothingFoundMessageProp,
6898
+ loadingLabel = "Loading\u2026",
6899
+ onlineLabel = "Online",
6900
+ offlineLabel = "Offline",
6901
+ refreshLabel = "Refresh player list",
6902
+ ...rest
6903
+ }) {
6904
+ const theme2 = useMantineTheme();
6905
+ const color = theme2.colors[theme2.primaryColor][5];
6906
+ const [searchInput, setSearchInput] = useState("");
6907
+ const [debouncedSearch, setDebouncedSearch] = useState("");
6908
+ const { players, isFetching, refresh } = usePlayers({
6909
+ includeOffline,
6910
+ search: includeOffline ? debouncedSearch : void 0,
6911
+ limit
6912
+ });
6913
+ const sortedPlayers = useMemo(
6914
+ () => [...players].sort((a, b) => {
6915
+ if (a.online !== b.online) return a.online ? -1 : 1;
6916
+ return a.charName.localeCompare(b.charName);
6917
+ }),
6918
+ [players]
6919
+ );
6920
+ const playerByCitizen = useMemo(() => {
6921
+ const m = /* @__PURE__ */ new Map();
6922
+ for (const p of sortedPlayers) m.set(p.citizenId, p);
6923
+ return m;
6924
+ }, [sortedPlayers]);
6925
+ const data = useMemo(() => {
6926
+ const items = sortedPlayers.map((p) => ({
6927
+ value: p.citizenId,
6928
+ label: formatLabel(p)
6929
+ }));
6930
+ if (value && !items.some((i) => i.value === value)) {
6931
+ items.unshift({ value, label: value });
6932
+ }
6933
+ return items;
6934
+ }, [sortedPlayers, value]);
6935
+ const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
6936
+ const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
6937
+ useEffect(() => {
6938
+ if (!includeOffline) return;
6939
+ if (selectedLabel && searchInput === selectedLabel) return;
6940
+ const t4 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
6941
+ return () => clearTimeout(t4);
6942
+ }, [searchInput, includeOffline, selectedLabel]);
6943
+ const renderOption = ({ option }) => {
6944
+ const p = playerByCitizen.get(option.value);
6945
+ if (!p) return option.label;
6946
+ return /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
6947
+ /* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
6948
+ /* @__PURE__ */ jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
6949
+ ] });
6950
+ };
6951
+ return /* @__PURE__ */ jsx(
6952
+ Select,
6953
+ {
6954
+ ...rest,
6955
+ data,
6956
+ value: value ?? null,
6957
+ onChange: (v) => {
6958
+ if (!v) return onChange(null);
6959
+ const player = playerByCitizen.get(v) ?? null;
6960
+ onChange(player);
6961
+ },
6962
+ searchable: searchableProp ?? true,
6963
+ searchValue: includeOffline ? searchInput : void 0,
6964
+ onSearchChange: includeOffline ? setSearchInput : void 0,
6965
+ placeholder,
6966
+ nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
6967
+ maxDropdownHeight: 300,
6968
+ renderOption,
6969
+ leftSection: selectedPlayer ? /* @__PURE__ */ jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
6970
+ rightSectionWidth: "3.5vh",
6971
+ rightSectionPointerEvents: "all",
6972
+ rightSection: /* @__PURE__ */ jsx(
6973
+ ActionIcon,
6974
+ {
6975
+ variant: "subtle",
6976
+ size: "sm",
6977
+ onClick: (e) => {
6978
+ e.stopPropagation();
6979
+ refresh();
6980
+ },
6981
+ "aria-label": refreshLabel,
6982
+ title: refreshLabel,
6983
+ style: { marginRight: "0.6vh" },
6984
+ children: isFetching ? /* @__PURE__ */ jsx(Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsx(RefreshCw, { size: "1.4vh", color })
6985
+ }
6986
+ )
6987
+ }
6988
+ );
6989
+ }
6990
+ function StatusDot({ online, onlineLabel, offlineLabel }) {
6991
+ return /* @__PURE__ */ jsx(
6992
+ "span",
6993
+ {
6994
+ title: online ? onlineLabel : offlineLabel,
6995
+ style: {
6996
+ display: "inline-block",
6997
+ width: "0.9vh",
6998
+ height: "0.9vh",
6999
+ borderRadius: "50%",
7000
+ backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
7001
+ boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
7002
+ flexShrink: 0
7003
+ }
7004
+ }
7005
+ );
7006
+ }
7007
+ function formatLabel(p) {
7008
+ const parts = [p.charName || p.citizenId];
7009
+ if (p.online) {
7010
+ if (p.name) parts.push(p.name);
7011
+ if (p.id != null) parts.push(`#${p.id}`);
7012
+ }
7013
+ return parts.join(" \xB7 ");
7014
+ }
7015
+ var t = (key, fallback) => {
7016
+ const v = locale(key);
7017
+ return v === key ? fallback : v;
7018
+ };
7019
+ var DEFAULT_VALUE = {
7020
+ groups: [],
7021
+ identifiers: []
7022
+ };
7023
+ function SectionLabel({ icon: Icon, label: label2 }) {
7024
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", mt: "xs", children: [
7025
+ /* @__PURE__ */ jsx(Icon, { size: "1.4vh", color: "rgba(255,255,255,0.3)" }),
7026
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
7027
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: "0.05vh", background: "rgba(255,255,255,0.06)" } })
7028
+ ] });
7029
+ }
7030
+ function EmptyHint({ text }) {
7031
+ return /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.25)", ta: "center", py: "xs", children: text });
7032
+ }
7033
+ function AccessOverrideSection({
7034
+ schemaKey = "access",
7035
+ title,
7036
+ description: description2,
7037
+ groupType,
7038
+ includeOffline = true
7039
+ }) {
7040
+ const mantineTheme = useMantineTheme();
7041
+ const color = mantineTheme.colors[mantineTheme.primaryColor][5];
7042
+ const raw = useFormField(schemaKey);
7043
+ const value = {
7044
+ groups: Array.isArray(raw?.groups) ? raw.groups : DEFAULT_VALUE.groups,
7045
+ identifiers: Array.isArray(raw?.identifiers) ? raw.identifiers : DEFAULT_VALUE.identifiers
7046
+ };
7047
+ const { setValue } = useFormActions();
7048
+ const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
7049
+ const [addingGroup, setAddingGroup] = useState(false);
7050
+ const [addingPlayer, setAddingPlayer] = useState(false);
7051
+ const commitGroup = (name) => {
7052
+ if (!name) return;
7053
+ set("groups", [...value.groups, name]);
7054
+ setAddingGroup(false);
7055
+ };
7056
+ const setGroup = (index, name) => {
7057
+ const next = [...value.groups];
7058
+ next[index] = name;
7059
+ set("groups", next.filter((g) => g !== ""));
7060
+ };
7061
+ const removeGroup = (index) => set("groups", value.groups.filter((_, i) => i !== index));
7062
+ const commitIdentifier = (id) => {
7063
+ if (!id) return;
7064
+ set("identifiers", [...value.identifiers, id]);
7065
+ setAddingPlayer(false);
7066
+ };
7067
+ const setIdentifier = (index, id) => {
7068
+ const next = [...value.identifiers];
7069
+ next[index] = id;
7070
+ set("identifiers", next.filter((x) => x !== ""));
7071
+ };
7072
+ const removeIdentifier = (index) => set("identifiers", value.identifiers.filter((_, i) => i !== index));
7073
+ return /* @__PURE__ */ jsxs(
7074
+ Flex,
7075
+ {
7076
+ direction: "column",
7077
+ gap: "xs",
7078
+ p: "sm",
7079
+ style: { flex: 1, minHeight: 0, overflowY: "auto" },
7080
+ children: [
7081
+ /* @__PURE__ */ jsx(
7082
+ AdminPageTitle,
7083
+ {
7084
+ icon: ShieldCheck,
7085
+ title: title || t("Access", "Access"),
7086
+ color
7087
+ }
7088
+ ),
7089
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.4)", children: description2 || t(
7090
+ "AccessOverrideDesc",
7091
+ "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."
7092
+ ) }),
7093
+ /* @__PURE__ */ jsx(SectionLabel, { icon: Users, label: t("AccessGroups", "Groups") }),
7094
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "xxs", children: [
7095
+ value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsx(EmptyHint, { text: t("AccessNoGroups", "No groups added.") }),
7096
+ value.groups.map((name, i) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7097
+ /* @__PURE__ */ jsx(
7098
+ GroupName,
7099
+ {
7100
+ value: name,
7101
+ onChange: (v) => setGroup(i, v),
7102
+ type: groupType,
7103
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7104
+ style: { flex: 1 }
7105
+ }
7106
+ ),
7107
+ /* @__PURE__ */ jsx(
7108
+ ActionIcon,
7109
+ {
7110
+ variant: "subtle",
7111
+ color: "red",
7112
+ size: "md",
7113
+ onClick: () => removeGroup(i),
7114
+ title: t("Remove", "Remove"),
7115
+ "aria-label": t("Remove", "Remove"),
7116
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7117
+ }
7118
+ )
7119
+ ] }, i)),
7120
+ addingGroup && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7121
+ /* @__PURE__ */ jsx(
7122
+ GroupName,
7123
+ {
7124
+ value: "",
7125
+ onChange: (v) => commitGroup(v),
7126
+ type: groupType,
7127
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7128
+ style: { flex: 1 }
7129
+ }
7130
+ ),
7131
+ /* @__PURE__ */ jsx(
7132
+ ActionIcon,
7133
+ {
7134
+ variant: "subtle",
7135
+ color: "red",
7136
+ size: "md",
7137
+ onClick: () => setAddingGroup(false),
7138
+ title: t("Remove", "Remove"),
7139
+ "aria-label": t("Remove", "Remove"),
7140
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7141
+ }
7142
+ )
7143
+ ] })
7144
+ ] }),
7145
+ !addingGroup && /* @__PURE__ */ jsx(AddRowButton, { label: t("AccessAddGroup", "Add group"), onClick: () => setAddingGroup(true) }),
7146
+ /* @__PURE__ */ jsx(SectionLabel, { icon: UserRound, label: t("AccessPlayers", "Players") }),
7147
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "xxs", children: [
7148
+ value.identifiers.length === 0 && !addingPlayer && /* @__PURE__ */ jsx(EmptyHint, { text: t("AccessNoPlayers", "No players added.") }),
7149
+ value.identifiers.map((id, i) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7150
+ /* @__PURE__ */ jsx(
7151
+ PlayerSelect,
7152
+ {
7153
+ value: id || null,
7154
+ onChange: (p) => setIdentifier(i, p?.citizenId ?? ""),
7155
+ includeOffline,
7156
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7157
+ style: { flex: 1 },
7158
+ size: "xs"
7159
+ }
7160
+ ),
7161
+ /* @__PURE__ */ jsx(
7162
+ ActionIcon,
7163
+ {
7164
+ variant: "subtle",
7165
+ color: "red",
7166
+ size: "md",
7167
+ onClick: () => removeIdentifier(i),
7168
+ title: t("Remove", "Remove"),
7169
+ "aria-label": t("Remove", "Remove"),
7170
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7171
+ }
7172
+ )
7173
+ ] }, i)),
7174
+ addingPlayer && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7175
+ /* @__PURE__ */ jsx(
7176
+ PlayerSelect,
7177
+ {
7178
+ value: null,
7179
+ onChange: (p) => commitIdentifier(p?.citizenId ?? ""),
7180
+ includeOffline,
7181
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7182
+ style: { flex: 1 },
7183
+ size: "xs"
7184
+ }
7185
+ ),
7186
+ /* @__PURE__ */ jsx(
7187
+ ActionIcon,
7188
+ {
7189
+ variant: "subtle",
7190
+ color: "red",
7191
+ size: "md",
7192
+ onClick: () => setAddingPlayer(false),
7193
+ title: t("Remove", "Remove"),
7194
+ "aria-label": t("Remove", "Remove"),
7195
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7196
+ }
7197
+ )
7198
+ ] })
7199
+ ] }),
7200
+ !addingPlayer && /* @__PURE__ */ jsx(AddRowButton, { label: t("AccessAddPlayer", "Add player"), onClick: () => setAddingPlayer(true) })
7201
+ ]
7202
+ }
7203
+ );
7204
+ }
7205
+ function AddRowButton({ label: label2, onClick }) {
7206
+ return /* @__PURE__ */ jsxs(
7207
+ Flex,
7208
+ {
7209
+ align: "center",
7210
+ justify: "center",
7211
+ gap: "xs",
7212
+ onClick,
7213
+ role: "button",
7214
+ tabIndex: 0,
7215
+ onKeyDown: (e) => {
7216
+ if (e.key === "Enter" || e.key === " ") {
7217
+ e.preventDefault();
7218
+ onClick();
7219
+ }
7220
+ },
7221
+ py: "xs",
7222
+ style: {
7223
+ cursor: "pointer",
7224
+ border: "0.1vh dashed rgba(255,255,255,0.12)",
7225
+ borderRadius: "0.4vh",
7226
+ transition: "background 0.15s, border-color 0.15s"
7227
+ },
7228
+ onMouseEnter: (e) => {
7229
+ e.currentTarget.style.background = "rgba(255,255,255,0.03)";
7230
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.2)";
7231
+ },
7232
+ onMouseLeave: (e) => {
7233
+ e.currentTarget.style.background = "transparent";
7234
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.12)";
7235
+ },
7236
+ children: [
7237
+ /* @__PURE__ */ jsx(Plus, { size: "1.4vh", color: "rgba(255,255,255,0.4)" }),
7238
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "rgba(255,255,255,0.4)", children: label2 })
7239
+ ]
7240
+ }
7241
+ );
7242
+ }
6824
7243
 
6825
7244
  // src/utils/gtaAnimPostFx.ts
6826
7245
  var GTA_ANIM_POST_FX_GROUP_ORDER = [
@@ -7410,7 +7829,7 @@ function ScenarioSelect({
7410
7829
  }
7411
7830
  );
7412
7831
  }
7413
- var t = (key, fallback) => {
7832
+ var t2 = (key, fallback) => {
7414
7833
  const v = locale(key);
7415
7834
  return v === key ? fallback : v;
7416
7835
  };
@@ -7478,7 +7897,7 @@ function DiscordRoleSelect(props) {
7478
7897
  return map;
7479
7898
  }, [roles]);
7480
7899
  if (errorCode === "NotConfigured") {
7481
- const notConfiguredPlaceholder = t(
7900
+ const notConfiguredPlaceholder = t2(
7482
7901
  "DiscordNotConfigured",
7483
7902
  "Not configured \u2014 set in /dirk_lib"
7484
7903
  );
@@ -7546,9 +7965,9 @@ function DiscordRoleSelect(props) {
7546
7965
  e.stopPropagation();
7547
7966
  refresh();
7548
7967
  },
7549
- title: t("Refresh", "Refresh"),
7968
+ title: t2("Refresh", "Refresh"),
7550
7969
  loading,
7551
- "aria-label": t("Refresh", "Refresh"),
7970
+ "aria-label": t2("Refresh", "Refresh"),
7552
7971
  children: /* @__PURE__ */ jsx(RefreshCw, { size: "1.2vh" })
7553
7972
  }
7554
7973
  );
@@ -7558,7 +7977,7 @@ function DiscordRoleSelect(props) {
7558
7977
  {
7559
7978
  label: label2,
7560
7979
  size,
7561
- placeholder: placeholder || t("PickRoles", "Pick roles..."),
7980
+ placeholder: placeholder || t2("PickRoles", "Pick roles..."),
7562
7981
  data,
7563
7982
  value: props.value || [],
7564
7983
  onChange: (ids) => props.onChange(ids),
@@ -7570,7 +7989,7 @@ function DiscordRoleSelect(props) {
7570
7989
  rightSectionPointerEvents: "all",
7571
7990
  style,
7572
7991
  styles,
7573
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
7992
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7574
7993
  }
7575
7994
  );
7576
7995
  }
@@ -7579,7 +7998,7 @@ function DiscordRoleSelect(props) {
7579
7998
  {
7580
7999
  label: label2,
7581
8000
  size,
7582
- placeholder: placeholder || t("PickRole", "Pick a role..."),
8001
+ placeholder: placeholder || t2("PickRole", "Pick a role..."),
7583
8002
  data,
7584
8003
  value: props.value || null,
7585
8004
  onChange: (id) => props.onChange(id),
@@ -7591,11 +8010,11 @@ function DiscordRoleSelect(props) {
7591
8010
  rightSectionPointerEvents: "all",
7592
8011
  style,
7593
8012
  styles,
7594
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
8013
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7595
8014
  }
7596
8015
  );
7597
8016
  }
7598
- var t2 = (key, fallback) => {
8017
+ var t3 = (key, fallback) => {
7599
8018
  const v = locale(key);
7600
8019
  return v === key ? fallback : v;
7601
8020
  };
@@ -7616,7 +8035,7 @@ function FrameworkHint({ framework }) {
7616
8035
  const theme2 = useMantineTheme();
7617
8036
  if (!framework) return null;
7618
8037
  const hintKey = `AccountFrameworkHint_${framework}`;
7619
- const text = t2(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
8038
+ const text = t3(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
7620
8039
  return /* @__PURE__ */ jsxs(
7621
8040
  Flex,
7622
8041
  {
@@ -7700,9 +8119,9 @@ function AccountSelect(props) {
7700
8119
  e.stopPropagation();
7701
8120
  refresh();
7702
8121
  },
7703
- title: t2("Refresh", "Refresh"),
8122
+ title: t3("Refresh", "Refresh"),
7704
8123
  loading,
7705
- "aria-label": t2("Refresh", "Refresh"),
8124
+ "aria-label": t3("Refresh", "Refresh"),
7706
8125
  children: /* @__PURE__ */ jsx(RefreshCw, { size: "1.2vh" })
7707
8126
  }
7708
8127
  );
@@ -7716,14 +8135,14 @@ function AccountSelect(props) {
7716
8135
  rightSection: refreshButton,
7717
8136
  rightSectionPointerEvents: "all",
7718
8137
  style,
7719
- nothingFoundMessage: t2("NoAccounts", "No accounts available")
8138
+ nothingFoundMessage: t3("NoAccounts", "No accounts available")
7720
8139
  };
7721
8140
  return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "0.3vh", style, children: [
7722
8141
  props.multi ? /* @__PURE__ */ jsx(
7723
8142
  MultiSelect,
7724
8143
  {
7725
8144
  ...sharedSelectProps,
7726
- placeholder: placeholder || t2("PickAccounts", "Pick accounts (or type custom)..."),
8145
+ placeholder: placeholder || t3("PickAccounts", "Pick accounts (or type custom)..."),
7727
8146
  value: props.value || [],
7728
8147
  onChange: (names) => props.onChange(names),
7729
8148
  renderOption,
@@ -7735,7 +8154,7 @@ function AccountSelect(props) {
7735
8154
  Select,
7736
8155
  {
7737
8156
  ...sharedSelectProps,
7738
- placeholder: placeholder || t2("PickAccount", "Pick an account..."),
8157
+ placeholder: placeholder || t3("PickAccount", "Pick an account..."),
7739
8158
  value: props.value || null,
7740
8159
  onChange: (name) => props.onChange(name),
7741
8160
  renderOption,
@@ -8027,175 +8446,6 @@ function WorldPositionGotoButton2({
8027
8446
  }
8028
8447
  ) });
8029
8448
  }
8030
- function usePlayers(opts = {}) {
8031
- const {
8032
- includeOffline = false,
8033
- search = "",
8034
- limit = 50,
8035
- staleTimeMs,
8036
- refetchIntervalMs
8037
- } = opts;
8038
- const query = useQuery({
8039
- queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
8040
- queryFn: async () => {
8041
- const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
8042
- const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
8043
- const result = await fetchNui(
8044
- "ADMIN_TOOL_QUERY",
8045
- payload,
8046
- // Browser-dev fallback. Returns a couple of mock players so the
8047
- // dev shell isn't blank.
8048
- includeOffline ? [
8049
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
8050
- { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
8051
- ] : [
8052
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
8053
- ]
8054
- );
8055
- return Array.isArray(result) ? result : [];
8056
- },
8057
- staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
8058
- gcTime: 5 * 6e4,
8059
- refetchInterval: refetchIntervalMs ?? false,
8060
- refetchOnWindowFocus: false,
8061
- placeholderData: includeOffline ? keepPreviousData : void 0
8062
- });
8063
- return {
8064
- players: query.data ?? [],
8065
- isLoading: query.isLoading,
8066
- isFetching: query.isFetching,
8067
- error: query.error ?? null,
8068
- refresh: () => query.refetch()
8069
- };
8070
- }
8071
- var DEBOUNCE_MS = 300;
8072
- var ONLINE_COLOR = "#3FA83F";
8073
- var OFFLINE_COLOR = "#E54141";
8074
- function PlayerSelect({
8075
- value,
8076
- onChange,
8077
- includeOffline = false,
8078
- limit = 50,
8079
- searchable: searchableProp,
8080
- placeholder,
8081
- nothingFoundMessage: nothingFoundMessageProp,
8082
- loadingLabel = "Loading\u2026",
8083
- onlineLabel = "Online",
8084
- offlineLabel = "Offline",
8085
- refreshLabel = "Refresh player list",
8086
- ...rest
8087
- }) {
8088
- const theme2 = useMantineTheme();
8089
- const color = theme2.colors[theme2.primaryColor][5];
8090
- const [searchInput, setSearchInput] = useState("");
8091
- const [debouncedSearch, setDebouncedSearch] = useState("");
8092
- const { players, isFetching, refresh } = usePlayers({
8093
- includeOffline,
8094
- search: includeOffline ? debouncedSearch : void 0,
8095
- limit
8096
- });
8097
- const sortedPlayers = useMemo(
8098
- () => [...players].sort((a, b) => {
8099
- if (a.online !== b.online) return a.online ? -1 : 1;
8100
- return a.charName.localeCompare(b.charName);
8101
- }),
8102
- [players]
8103
- );
8104
- const playerByCitizen = useMemo(() => {
8105
- const m = /* @__PURE__ */ new Map();
8106
- for (const p of sortedPlayers) m.set(p.citizenId, p);
8107
- return m;
8108
- }, [sortedPlayers]);
8109
- const data = useMemo(() => {
8110
- const items = sortedPlayers.map((p) => ({
8111
- value: p.citizenId,
8112
- label: formatLabel(p)
8113
- }));
8114
- if (value && !items.some((i) => i.value === value)) {
8115
- items.unshift({ value, label: value });
8116
- }
8117
- return items;
8118
- }, [sortedPlayers, value]);
8119
- const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
8120
- const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
8121
- useEffect(() => {
8122
- if (!includeOffline) return;
8123
- if (selectedLabel && searchInput === selectedLabel) return;
8124
- const t3 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
8125
- return () => clearTimeout(t3);
8126
- }, [searchInput, includeOffline, selectedLabel]);
8127
- const renderOption = ({ option }) => {
8128
- const p = playerByCitizen.get(option.value);
8129
- if (!p) return option.label;
8130
- return /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
8131
- /* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
8132
- /* @__PURE__ */ jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
8133
- ] });
8134
- };
8135
- return /* @__PURE__ */ jsx(
8136
- Select,
8137
- {
8138
- ...rest,
8139
- data,
8140
- value: value ?? null,
8141
- onChange: (v) => {
8142
- if (!v) return onChange(null);
8143
- const player = playerByCitizen.get(v) ?? null;
8144
- onChange(player);
8145
- },
8146
- searchable: searchableProp ?? true,
8147
- searchValue: includeOffline ? searchInput : void 0,
8148
- onSearchChange: includeOffline ? setSearchInput : void 0,
8149
- placeholder,
8150
- nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
8151
- maxDropdownHeight: 300,
8152
- renderOption,
8153
- leftSection: selectedPlayer ? /* @__PURE__ */ jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
8154
- rightSectionWidth: "3.5vh",
8155
- rightSectionPointerEvents: "all",
8156
- rightSection: /* @__PURE__ */ jsx(
8157
- ActionIcon,
8158
- {
8159
- variant: "subtle",
8160
- size: "sm",
8161
- onClick: (e) => {
8162
- e.stopPropagation();
8163
- refresh();
8164
- },
8165
- "aria-label": refreshLabel,
8166
- title: refreshLabel,
8167
- style: { marginRight: "0.6vh" },
8168
- children: isFetching ? /* @__PURE__ */ jsx(Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsx(RefreshCw, { size: "1.4vh", color })
8169
- }
8170
- )
8171
- }
8172
- );
8173
- }
8174
- function StatusDot({ online, onlineLabel, offlineLabel }) {
8175
- return /* @__PURE__ */ jsx(
8176
- "span",
8177
- {
8178
- title: online ? onlineLabel : offlineLabel,
8179
- style: {
8180
- display: "inline-block",
8181
- width: "0.9vh",
8182
- height: "0.9vh",
8183
- borderRadius: "50%",
8184
- backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
8185
- boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
8186
- flexShrink: 0
8187
- }
8188
- }
8189
- );
8190
- }
8191
- function formatLabel(p) {
8192
- const parts = [p.charName || p.citizenId];
8193
- if (p.online) {
8194
- if (p.name) parts.push(p.name);
8195
- if (p.id != null) parts.push(`#${p.id}`);
8196
- }
8197
- return parts.join(" \xB7 ");
8198
- }
8199
8449
  function usePickDoor() {
8200
8450
  const begin = useAdminToolStore((s) => s.begin);
8201
8451
  return async () => {
@@ -8587,6 +8837,6 @@ var Vector4Schema = z.object({
8587
8837
  w: z.number()
8588
8838
  });
8589
8839
 
8590
- export { AccountSelect, AdminPageTitle, AnimPostFxSelect, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BlipMarker, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, DirkProvider, DiscordRoleSelect, DoorPickerButton, FiveMKeyBindInput, FloatingParticles, FormProvider, GTA_CONTROLS, GTA_CONTROL_GROUP_ORDER, GroupName, GroupRank, GroupSelect, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, InfoBox, InputContainer, InstructionPanel, LevelBanner, LevelPanel, Map2 as Map, MapLayer, MissingItemsBanner, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PlayerSelect, PositionPicker, PromptModal, ScenarioSelect, SegmentedControl, SegmentedProgress, SelectItem, SwitchPanel, TestBed, ThemeOverrideSection, Title, TornEdgeSVGFilter, Vector2Schema, Vector3Schema, Vector4DeleteButton, Vector4Display, Vector4Schema, WorldPositionGotoButton, WorldPositionPicker, WorldPositionSetButton, ZoomControls, blipUrl, blipUrlForSprite, clearAdminState, colorWithAlpha, copyToClipboard, createFormStore, createScriptConfig, createSkill, dirkQueryClient, ensureFrameworkGroups, extractDefaults, fetchLuaTable, fetchNui, formatGtaControl, gameToMap, getBlipColor, getBlipEntry, getGtaControl, getImageShape, getItemImageUrl, getScriptConfigInstance, initialFetches, internalEvent, isEnvBrowser, isProfanity, latPr100, locale, localeStore, mapCenter, mapToGame, noop, numberToRoman, openLink, registerInitialFetch, registerInitialLuaTableFetch, runFetches, selectAllGroups, splitFAString, updatePresignedURL, uploadImage, useAdminState, useAdminToolStore, useAudio, useAutoFetcher, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useFrameworkGroups, useItems, useItemsList, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore, useNuiEvent, usePickDoor, usePlayers, useProfanityStore, useSettings, useTornEdges, useValidModels };
8840
+ export { AccessOverrideSection, AccountSelect, AdminPageTitle, AnimPostFxSelect, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BlipMarker, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, DEFAULT_PALETTE, DirkProvider, DiscordRoleSelect, DoorPickerButton, FiveMKeyBindInput, FloatingParticles, FormProvider, GTA_CONTROLS, GTA_CONTROL_GROUP_ORDER, GroupName, GroupRank, GroupSelect, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, InfoBox, InputContainer, InstructionPanel, LevelBanner, LevelPanel, Map2 as Map, MapLayer, MissingItemsBanner, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PlayerSelect, PositionPicker, PromptModal, ScenarioSelect, SegmentedControl, SegmentedProgress, SelectItem, SwitchPanel, TestBed, ThemeOverrideSchema, ThemeOverrideSection, Title, TornEdgeSVGFilter, Vector2Schema, Vector3Schema, Vector4DeleteButton, Vector4Display, Vector4Schema, WorldPositionGotoButton, WorldPositionPicker, WorldPositionSetButton, ZoomControls, blipUrl, blipUrlForSprite, clearAdminState, colorWithAlpha, copyToClipboard, createFormStore, createScriptConfig, createSkill, defaultThemeOverride, dirkQueryClient, ensureFrameworkGroups, extractDefaults, fetchLuaTable, fetchNui, formatGtaControl, gameToMap, getBlipColor, getBlipEntry, getGtaControl, getImageShape, getItemImageUrl, getScriptConfigInstance, initialFetches, internalEvent, isEnvBrowser, isProfanity, latPr100, locale, localeStore, mapCenter, mapToGame, noop, numberToRoman, openLink, registerInitialFetch, registerInitialLuaTableFetch, runFetches, selectAllGroups, splitFAString, updatePresignedURL, uploadImage, useAdminState, useAdminToolStore, useAudio, useAutoFetcher, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useFrameworkGroups, useItems, useItemsList, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore, useNuiEvent, usePickDoor, usePlayers, useProfanityStore, useSettings, useTornEdges, useValidModels };
8591
8841
  //# sourceMappingURL=index.js.map
8592
8842
  //# sourceMappingURL=index.js.map