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.
@@ -4,6 +4,7 @@ var core = require('@mantine/core');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var react = require('react');
6
6
  var zustand = require('zustand');
7
+ var zod = require('zod');
7
8
  var reactFontawesome = require('@fortawesome/react-fontawesome');
8
9
  var framerMotion = require('framer-motion');
9
10
  var lucideReact = require('lucide-react');
@@ -1289,6 +1290,32 @@ function colorWithAlpha(color, alpha17) {
1289
1290
 
1290
1291
  // src/utils/misc.ts
1291
1292
  var isEnvBrowser = () => !window.invokeNative;
1293
+ var DEFAULT_PALETTE = [
1294
+ "#f0f4ff",
1295
+ "#d9e3ff",
1296
+ "#bfcfff",
1297
+ "#a6bbff",
1298
+ "#8ca7ff",
1299
+ "#7393ff",
1300
+ "#5a7fff",
1301
+ "#406bff",
1302
+ "#2547ff",
1303
+ "#0b33ff"
1304
+ ];
1305
+ zod.z.object({
1306
+ useOverride: zod.z.boolean(),
1307
+ primaryColor: zod.z.string(),
1308
+ primaryShade: zod.z.number(),
1309
+ customTheme: zod.z.array(zod.z.string())
1310
+ });
1311
+ var defaultThemeOverride = {
1312
+ useOverride: false,
1313
+ primaryColor: "dirk",
1314
+ primaryShade: 5,
1315
+ customTheme: DEFAULT_PALETTE
1316
+ };
1317
+
1318
+ // src/utils/useSettings.ts
1292
1319
  var useSettings = zustand.create(() => ({
1293
1320
  currency: "$",
1294
1321
  game: "fivem",
@@ -1296,18 +1323,7 @@ var useSettings = zustand.create(() => ({
1296
1323
  primaryShade: 9,
1297
1324
  itemImgPath: "",
1298
1325
  resourceVersion: "dev",
1299
- customTheme: [
1300
- "#f0f4ff",
1301
- "#d9e3ff",
1302
- "#bfcfff",
1303
- "#a6bbff",
1304
- "#8ca7ff",
1305
- "#7393ff",
1306
- "#5a7fff",
1307
- "#406bff",
1308
- "#2547ff",
1309
- "#0b33ff"
1310
- ]
1326
+ customTheme: DEFAULT_PALETTE
1311
1327
  }));
1312
1328
 
1313
1329
  // src/utils/fetchNui.ts
@@ -1350,11 +1366,35 @@ function reportMissingLocale(key) {
1350
1366
  fetchNui("REPORT_MISSING_LOCALE", { key }).catch(() => {
1351
1367
  });
1352
1368
  }
1369
+ var PACKAGE_DEFAULTS = {
1370
+ "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.",
1371
+ "cfgpanel_discard": "Discard",
1372
+ "cfgpanel_manual_edit": "Manual Edit",
1373
+ "cfgpanel_reset_defaults": "Reset Defaults",
1374
+ "cfgpanel_version": "Version",
1375
+ "cfgpanel_back_title": "Back to script list",
1376
+ "cfgpanel_undo": "Undo",
1377
+ "cfgpanel_redo": "Redo",
1378
+ "cfgpanel_save": "Save",
1379
+ "cfgpanel_saving": "Saving...",
1380
+ "cfgpanel_history": "History",
1381
+ "cfgpanel_reset_title": "Reset to Defaults",
1382
+ "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.",
1383
+ "cfgpanel_reset_confirm": "Reset Config",
1384
+ "cfgpanel_discard_title": "Discard Unsaved Changes?",
1385
+ "cfgpanel_discard_desc_back": "You have unsaved changes. Going back now will discard them.",
1386
+ "cfgpanel_discard_desc_close": "You have unsaved changes. Closing now will discard them.",
1387
+ "cfgpanel_discard_confirm_back": "Go Back Without Saving",
1388
+ "cfgpanel_discard_confirm_close": "Close Without Saving",
1389
+ "cfgpanel_json_title": "Config JSON",
1390
+ "cfgpanel_cancel": "Cancel",
1391
+ "cfgpanel_apply": "Apply",
1392
+ "cfgpanel_history_title": "Config History",
1393
+ "cfgpanel_close": "Close"
1394
+ };
1353
1395
  var localeStore = zustand.create((set, get) => {
1354
1396
  return {
1355
- locales: {
1356
- "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."
1357
- },
1397
+ locales: { ...PACKAGE_DEFAULTS },
1358
1398
  locale: (key, ...args) => {
1359
1399
  const exists = get().locales[key];
1360
1400
  if (!exists) reportMissingLocale(key);
@@ -1368,7 +1408,7 @@ var localeStore = zustand.create((set, get) => {
1368
1408
  });
1369
1409
  var locale = localeStore.getState().locale;
1370
1410
  registerInitialFetch("GET_LOCALES", void 0).then((data) => {
1371
- localeStore.setState({ locales: data });
1411
+ localeStore.setState({ locales: { ...PACKAGE_DEFAULTS, ...data } });
1372
1412
  }).catch(() => {
1373
1413
  });
1374
1414
  if (typeof window !== "undefined") {
@@ -1377,7 +1417,7 @@ if (typeof window !== "undefined") {
1377
1417
  if (!msg || msg.action !== "UPDATE_DIRK_LIB_LOCALES") return;
1378
1418
  if (!msg.data || typeof msg.data !== "object") return;
1379
1419
  if (Object.keys(msg.data).length === 0) return;
1380
- localeStore.setState({ locales: msg.data });
1420
+ localeStore.setState({ locales: { ...PACKAGE_DEFAULTS, ...msg.data } });
1381
1421
  });
1382
1422
  }
1383
1423
 
@@ -3137,11 +3177,11 @@ function ConfirmModal({
3137
3177
  placeholder: confirmText,
3138
3178
  value: typed,
3139
3179
  onChange: (e) => setTyped(e.currentTarget.value),
3140
- styles: (t3) => ({
3180
+ styles: (t4) => ({
3141
3181
  input: {
3142
- backgroundColor: core.alpha(t3.colors.dark[7], 0.5),
3182
+ backgroundColor: core.alpha(t4.colors.dark[7], 0.5),
3143
3183
  border: `0.1vh solid ${core.alpha(
3144
- typed === confirmText ? "#ef4444" : t3.colors.dark[5],
3184
+ typed === confirmText ? "#ef4444" : t4.colors.dark[5],
3145
3185
  0.5
3146
3186
  )}`,
3147
3187
  color: "rgba(255,255,255,0.85)",
@@ -3786,7 +3826,7 @@ function useAdminState(key, initial) {
3786
3826
  react.useEffect(() => {
3787
3827
  const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
3788
3828
  listeners.set(key, set);
3789
- const handler = () => setTick((t3) => t3 + 1);
3829
+ const handler = () => setTick((t4) => t4 + 1);
3790
3830
  set.add(handler);
3791
3831
  return () => {
3792
3832
  set.delete(handler);
@@ -3906,7 +3946,7 @@ function ConfigJsonModal({
3906
3946
  setError(e.message);
3907
3947
  }
3908
3948
  };
3909
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config JSON", icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
3949
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: locale("cfgpanel_json_title"), icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
3910
3950
  /* @__PURE__ */ jsxRuntime.jsxs(core.Box, { flex: 1, p: "0.8vh", style: { overflowY: "auto" }, children: [
3911
3951
  /* @__PURE__ */ jsxRuntime.jsx(
3912
3952
  core.JsonInput,
@@ -3934,7 +3974,7 @@ function ConfigJsonModal({
3934
3974
  style: { background: "transparent", border: `0.1vh solid ${theme.colors.dark[6]}`, borderRadius: theme.radius.xs, padding: `${theme.spacing.xxs} ${theme.spacing.xs}`, cursor: "pointer", display: "flex", alignItems: "center", gap: theme.spacing.xxs },
3935
3975
  children: [
3936
3976
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { color: "rgba(255,255,255,0.4)" }),
3937
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: "Cancel" })
3977
+ /* @__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") })
3938
3978
  ]
3939
3979
  }
3940
3980
  ),
@@ -3947,7 +3987,7 @@ function ConfigJsonModal({
3947
3987
  style: { background: core.alpha(color, 0.14), border: `0.1vh solid ${core.alpha(color, 0.4)}`, borderRadius: theme.radius.xs, padding: `${theme.spacing.xxs} ${theme.spacing.xs}`, cursor: "pointer", display: "flex", alignItems: "center", gap: theme.spacing.xxs },
3948
3988
  children: [
3949
3989
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { color }),
3950
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: color, children: "Apply" })
3990
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: color, children: locale("cfgpanel_apply") })
3951
3991
  ]
3952
3992
  }
3953
3993
  )
@@ -4088,7 +4128,7 @@ function ConfigHistoryModal({
4088
4128
  historyQuery.fetchNextPage();
4089
4129
  }
4090
4130
  };
4091
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config History", icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
4131
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: locale("cfgpanel_history_title"), icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
4092
4132
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minHeight: 0 }, children: [
4093
4133
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { gap: "xs", p: "sm", style: { borderBottom: `0.1vh solid ${core.alpha(theme.colors.dark[7], 0.8)}` }, children: [
4094
4134
  /* @__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 } }),
@@ -4131,7 +4171,7 @@ function ConfigHistoryModal({
4131
4171
  style: { background: "transparent", border: `0.1vh solid ${theme.colors.dark[6]}`, borderRadius: theme.radius.xs, padding: `${theme.spacing.xxs} ${theme.spacing.xs}`, cursor: "pointer", display: "flex", alignItems: "center", gap: theme.spacing.xxs },
4132
4172
  children: [
4133
4173
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { color: "rgba(255,255,255,0.4)", size: "1.5vh" }),
4134
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: "Close" })
4174
+ /* @__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") })
4135
4175
  ]
4136
4176
  }
4137
4177
  ) })
@@ -4191,9 +4231,9 @@ function ConfigPanelInner({
4191
4231
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: resetOpen && /* @__PURE__ */ jsxRuntime.jsx(
4192
4232
  ConfirmModal,
4193
4233
  {
4194
- title: "Reset to Defaults",
4195
- description: "This will permanently reset ALL config back to the defaults. Every value you have configured will be overwritten. This cannot be undone.",
4196
- confirmLabel: "Reset Config",
4234
+ title: locale("cfgpanel_reset_title"),
4235
+ description: locale("cfgpanel_reset_desc"),
4236
+ confirmLabel: locale("cfgpanel_reset_confirm"),
4197
4237
  confirmText: resetConfirmText,
4198
4238
  onConfirm: async () => {
4199
4239
  setResetOpen(false);
@@ -4225,9 +4265,9 @@ function ConfigPanelInner({
4225
4265
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: pendingAction !== null && /* @__PURE__ */ jsxRuntime.jsx(
4226
4266
  ConfirmModal,
4227
4267
  {
4228
- title: "Discard Unsaved Changes?",
4229
- description: pendingAction === "back" ? "You have unsaved changes. Going back now will discard them." : "You have unsaved changes. Closing now will discard them.",
4230
- confirmLabel: pendingAction === "back" ? "Go Back Without Saving" : "Close Without Saving",
4268
+ title: locale("cfgpanel_discard_title"),
4269
+ description: pendingAction === "back" ? locale("cfgpanel_discard_desc_back") : locale("cfgpanel_discard_desc_close"),
4270
+ confirmLabel: pendingAction === "back" ? locale("cfgpanel_discard_confirm_back") : locale("cfgpanel_discard_confirm_close"),
4231
4271
  onConfirm: () => {
4232
4272
  const action = pendingAction;
4233
4273
  setPendingAction(null);
@@ -4265,7 +4305,7 @@ function ConfigPanelInner({
4265
4305
  /* @__PURE__ */ jsxRuntime.jsx(
4266
4306
  framerMotion.motion.button,
4267
4307
  {
4268
- title: "Back to script list",
4308
+ title: locale("cfgpanel_back_title"),
4269
4309
  onClick: handleBack,
4270
4310
  whileHover: { background: core.alpha(color, 0.16), borderColor: core.alpha(color, 0.45) },
4271
4311
  whileTap: { scale: 0.95 },
@@ -4293,7 +4333,7 @@ function ConfigPanelInner({
4293
4333
  /* @__PURE__ */ jsxRuntime.jsx(
4294
4334
  framerMotion.motion.button,
4295
4335
  {
4296
- title: "Undo",
4336
+ title: locale("cfgpanel_undo"),
4297
4337
  onClick: () => form.canBack && form.back(),
4298
4338
  disabled: !form.canBack,
4299
4339
  whileHover: form.canBack ? { background: "rgba(255,255,255,0.07)" } : void 0,
@@ -4305,7 +4345,7 @@ function ConfigPanelInner({
4305
4345
  /* @__PURE__ */ jsxRuntime.jsx(
4306
4346
  framerMotion.motion.button,
4307
4347
  {
4308
- title: "Redo",
4348
+ title: locale("cfgpanel_redo"),
4309
4349
  onClick: () => form.canForward && form.forward(),
4310
4350
  disabled: !form.canForward,
4311
4351
  whileHover: form.canForward ? { background: "rgba(255,255,255,0.07)" } : void 0,
@@ -4317,7 +4357,7 @@ function ConfigPanelInner({
4317
4357
  /* @__PURE__ */ jsxRuntime.jsx(
4318
4358
  framerMotion.motion.button,
4319
4359
  {
4320
- title: isSaving ? "Saving..." : isDirty ? `Save (${changedCount})` : "Save",
4360
+ title: isSaving ? locale("cfgpanel_saving") : isDirty ? `${locale("cfgpanel_save")} (${changedCount})` : locale("cfgpanel_save"),
4321
4361
  onClick: () => isDirty && !isSaving && form.submit(),
4322
4362
  disabled: !isDirty || isSaving,
4323
4363
  whileHover: isDirty && !isSaving ? { background: core.alpha(color, 0.25), borderColor: core.alpha(color, 0.5) } : void 0,
@@ -4329,7 +4369,7 @@ function ConfigPanelInner({
4329
4369
  /* @__PURE__ */ jsxRuntime.jsx(
4330
4370
  framerMotion.motion.button,
4331
4371
  {
4332
- title: "History",
4372
+ title: locale("cfgpanel_history"),
4333
4373
  onClick: () => setHistoryOpen(true),
4334
4374
  whileHover: { background: core.alpha("#22d3ee", 0.16), borderColor: core.alpha("#22d3ee", 0.5) },
4335
4375
  whileTap: { scale: 0.97 },
@@ -4350,7 +4390,7 @@ function ConfigPanelInner({
4350
4390
  style: { width: "100%", background: "transparent", border: `0.1vh solid ${isDirty ? core.alpha("#f97316", 0.35) : core.alpha(theme.colors.dark[5], 0.3)}`, borderRadius: theme.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" },
4351
4391
  children: [
4352
4392
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { color: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", size: "1.6vh" }),
4353
- /* @__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" })
4393
+ /* @__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") })
4354
4394
  ]
4355
4395
  }
4356
4396
  ),
@@ -4364,7 +4404,7 @@ function ConfigPanelInner({
4364
4404
  style: { width: "100%", background: core.alpha(color, 0.07), border: `0.1vh solid ${core.alpha(color, 0.28)}`, borderRadius: theme.radius.xs, padding: "0.65vh 0.8vh", cursor: "pointer", display: "flex", alignItems: "center", gap: "0.55vh" },
4365
4405
  children: [
4366
4406
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code2, { color, size: "1.6vh" }),
4367
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: "Manual Edit" })
4407
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: locale("cfgpanel_manual_edit") })
4368
4408
  ]
4369
4409
  }
4370
4410
  ),
@@ -4377,12 +4417,13 @@ function ConfigPanelInner({
4377
4417
  style: { width: "100%", background: core.alpha("#ef4444", 0.07), border: `0.1vh solid ${core.alpha("#ef4444", 0.25)}`, borderRadius: theme.radius.xs, padding: "0.65vh 0.8vh", cursor: "pointer", display: "flex", alignItems: "center", gap: "0.55vh" },
4378
4418
  children: [
4379
4419
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { color: "#ef4444", size: "1.6vh" }),
4380
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "#ef4444", children: "Reset Defaults" })
4420
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "#ef4444", children: locale("cfgpanel_reset_defaults") })
4381
4421
  ]
4382
4422
  }
4383
4423
  ),
4384
4424
  version && /* @__PURE__ */ jsxRuntime.jsxs(core.Text, { size: "0.95vh", c: "dimmed", ta: "center", style: { letterSpacing: "0.04em", opacity: 0.8 }, children: [
4385
- "Version ",
4425
+ locale("cfgpanel_version"),
4426
+ " ",
4386
4427
  version
4387
4428
  ] })
4388
4429
  ] })
@@ -5473,24 +5514,6 @@ var MANTINE_COLOR_OPTIONS = [
5473
5514
  "yellow",
5474
5515
  "orange"
5475
5516
  ].map((value) => ({ value, label: value }));
5476
- var DEFAULT_PALETTE = [
5477
- "#f0f4ff",
5478
- "#d9e3ff",
5479
- "#bfcfff",
5480
- "#a6bbff",
5481
- "#8ca7ff",
5482
- "#7393ff",
5483
- "#5a7fff",
5484
- "#406bff",
5485
- "#2547ff",
5486
- "#0b33ff"
5487
- ];
5488
- var DEFAULT_VALUE = {
5489
- useOverride: false,
5490
- primaryColor: "dirk",
5491
- primaryShade: 5,
5492
- customTheme: DEFAULT_PALETTE
5493
- };
5494
5517
  function GroupLabel({ label }) {
5495
5518
  return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", mt: "xxs", children: [
5496
5519
  /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label }),
@@ -5505,10 +5528,10 @@ function ThemeOverrideSection({
5505
5528
  const color = mantineTheme.colors[mantineTheme.primaryColor][5];
5506
5529
  const raw = useFormField(schemaKey);
5507
5530
  const value = {
5508
- useOverride: raw?.useOverride ?? DEFAULT_VALUE.useOverride,
5509
- primaryColor: raw?.primaryColor ?? DEFAULT_VALUE.primaryColor,
5510
- primaryShade: raw?.primaryShade ?? DEFAULT_VALUE.primaryShade,
5511
- customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : DEFAULT_VALUE.customTheme
5531
+ useOverride: raw?.useOverride ?? defaultThemeOverride.useOverride,
5532
+ primaryColor: raw?.primaryColor ?? defaultThemeOverride.primaryColor,
5533
+ primaryShade: raw?.primaryShade ?? defaultThemeOverride.primaryShade,
5534
+ customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : defaultThemeOverride.customTheme
5512
5535
  };
5513
5536
  const { setValue } = useFormActions();
5514
5537
  const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
@@ -5715,6 +5738,403 @@ function SwatchTile({
5715
5738
  ) })
5716
5739
  ] });
5717
5740
  }
5741
+ function usePlayers(opts = {}) {
5742
+ const {
5743
+ includeOffline = false,
5744
+ search = "",
5745
+ limit = 50,
5746
+ staleTimeMs,
5747
+ refetchIntervalMs
5748
+ } = opts;
5749
+ const query = reactQuery.useQuery({
5750
+ queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
5751
+ queryFn: async () => {
5752
+ const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
5753
+ const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
5754
+ const result = await fetchNui(
5755
+ "ADMIN_TOOL_QUERY",
5756
+ payload,
5757
+ // Browser-dev fallback. Returns a couple of mock players so the
5758
+ // dev shell isn't blank.
5759
+ includeOffline ? [
5760
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
5761
+ { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
5762
+ ] : [
5763
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
5764
+ ]
5765
+ );
5766
+ return Array.isArray(result) ? result : [];
5767
+ },
5768
+ staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
5769
+ gcTime: 5 * 6e4,
5770
+ refetchInterval: refetchIntervalMs ?? false,
5771
+ refetchOnWindowFocus: false,
5772
+ placeholderData: includeOffline ? reactQuery.keepPreviousData : void 0
5773
+ });
5774
+ return {
5775
+ players: query.data ?? [],
5776
+ isLoading: query.isLoading,
5777
+ isFetching: query.isFetching,
5778
+ error: query.error ?? null,
5779
+ refresh: () => query.refetch()
5780
+ };
5781
+ }
5782
+ var DEBOUNCE_MS = 300;
5783
+ var ONLINE_COLOR = "#3FA83F";
5784
+ var OFFLINE_COLOR = "#E54141";
5785
+ function PlayerSelect({
5786
+ value,
5787
+ onChange,
5788
+ includeOffline = false,
5789
+ limit = 50,
5790
+ searchable: searchableProp,
5791
+ placeholder,
5792
+ nothingFoundMessage: nothingFoundMessageProp,
5793
+ loadingLabel = "Loading\u2026",
5794
+ onlineLabel = "Online",
5795
+ offlineLabel = "Offline",
5796
+ refreshLabel = "Refresh player list",
5797
+ ...rest
5798
+ }) {
5799
+ const theme = core.useMantineTheme();
5800
+ const color = theme.colors[theme.primaryColor][5];
5801
+ const [searchInput, setSearchInput] = react.useState("");
5802
+ const [debouncedSearch, setDebouncedSearch] = react.useState("");
5803
+ const { players, isFetching, refresh } = usePlayers({
5804
+ includeOffline,
5805
+ search: includeOffline ? debouncedSearch : void 0,
5806
+ limit
5807
+ });
5808
+ const sortedPlayers = react.useMemo(
5809
+ () => [...players].sort((a, b) => {
5810
+ if (a.online !== b.online) return a.online ? -1 : 1;
5811
+ return a.charName.localeCompare(b.charName);
5812
+ }),
5813
+ [players]
5814
+ );
5815
+ const playerByCitizen = react.useMemo(() => {
5816
+ const m = /* @__PURE__ */ new Map();
5817
+ for (const p of sortedPlayers) m.set(p.citizenId, p);
5818
+ return m;
5819
+ }, [sortedPlayers]);
5820
+ const data = react.useMemo(() => {
5821
+ const items = sortedPlayers.map((p) => ({
5822
+ value: p.citizenId,
5823
+ label: formatLabel(p)
5824
+ }));
5825
+ if (value && !items.some((i) => i.value === value)) {
5826
+ items.unshift({ value, label: value });
5827
+ }
5828
+ return items;
5829
+ }, [sortedPlayers, value]);
5830
+ const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
5831
+ const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
5832
+ react.useEffect(() => {
5833
+ if (!includeOffline) return;
5834
+ if (selectedLabel && searchInput === selectedLabel) return;
5835
+ const t4 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
5836
+ return () => clearTimeout(t4);
5837
+ }, [searchInput, includeOffline, selectedLabel]);
5838
+ const renderOption = ({ option }) => {
5839
+ const p = playerByCitizen.get(option.value);
5840
+ if (!p) return option.label;
5841
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
5842
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
5843
+ /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
5844
+ ] });
5845
+ };
5846
+ return /* @__PURE__ */ jsxRuntime.jsx(
5847
+ core.Select,
5848
+ {
5849
+ ...rest,
5850
+ data,
5851
+ value: value ?? null,
5852
+ onChange: (v) => {
5853
+ if (!v) return onChange(null);
5854
+ const player = playerByCitizen.get(v) ?? null;
5855
+ onChange(player);
5856
+ },
5857
+ searchable: searchableProp ?? true,
5858
+ searchValue: includeOffline ? searchInput : void 0,
5859
+ onSearchChange: includeOffline ? setSearchInput : void 0,
5860
+ placeholder,
5861
+ nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
5862
+ maxDropdownHeight: 300,
5863
+ renderOption,
5864
+ leftSection: selectedPlayer ? /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
5865
+ rightSectionWidth: "3.5vh",
5866
+ rightSectionPointerEvents: "all",
5867
+ rightSection: /* @__PURE__ */ jsxRuntime.jsx(
5868
+ core.ActionIcon,
5869
+ {
5870
+ variant: "subtle",
5871
+ size: "sm",
5872
+ onClick: (e) => {
5873
+ e.stopPropagation();
5874
+ refresh();
5875
+ },
5876
+ "aria-label": refreshLabel,
5877
+ title: refreshLabel,
5878
+ style: { marginRight: "0.6vh" },
5879
+ children: isFetching ? /* @__PURE__ */ jsxRuntime.jsx(core.Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.4vh", color })
5880
+ }
5881
+ )
5882
+ }
5883
+ );
5884
+ }
5885
+ function StatusDot({ online, onlineLabel, offlineLabel }) {
5886
+ return /* @__PURE__ */ jsxRuntime.jsx(
5887
+ "span",
5888
+ {
5889
+ title: online ? onlineLabel : offlineLabel,
5890
+ style: {
5891
+ display: "inline-block",
5892
+ width: "0.9vh",
5893
+ height: "0.9vh",
5894
+ borderRadius: "50%",
5895
+ backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
5896
+ boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
5897
+ flexShrink: 0
5898
+ }
5899
+ }
5900
+ );
5901
+ }
5902
+ function formatLabel(p) {
5903
+ const parts = [p.charName || p.citizenId];
5904
+ if (p.online) {
5905
+ if (p.name) parts.push(p.name);
5906
+ if (p.id != null) parts.push(`#${p.id}`);
5907
+ }
5908
+ return parts.join(" \xB7 ");
5909
+ }
5910
+ var t = (key, fallback) => {
5911
+ const v = locale(key);
5912
+ return v === key ? fallback : v;
5913
+ };
5914
+ var DEFAULT_VALUE = {
5915
+ groups: [],
5916
+ identifiers: []
5917
+ };
5918
+ function SectionLabel({ icon: Icon, label }) {
5919
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", mt: "xs", children: [
5920
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: "1.4vh", color: "rgba(255,255,255,0.3)" }),
5921
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label }),
5922
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, height: "0.05vh", background: "rgba(255,255,255,0.06)" } })
5923
+ ] });
5924
+ }
5925
+ function EmptyHint({ text }) {
5926
+ return /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.25)", ta: "center", py: "xs", children: text });
5927
+ }
5928
+ function AccessOverrideSection({
5929
+ schemaKey = "access",
5930
+ title,
5931
+ description,
5932
+ groupType,
5933
+ includeOffline = true
5934
+ }) {
5935
+ const mantineTheme = core.useMantineTheme();
5936
+ const color = mantineTheme.colors[mantineTheme.primaryColor][5];
5937
+ const raw = useFormField(schemaKey);
5938
+ const value = {
5939
+ groups: Array.isArray(raw?.groups) ? raw.groups : DEFAULT_VALUE.groups,
5940
+ identifiers: Array.isArray(raw?.identifiers) ? raw.identifiers : DEFAULT_VALUE.identifiers
5941
+ };
5942
+ const { setValue } = useFormActions();
5943
+ const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
5944
+ const [addingGroup, setAddingGroup] = react.useState(false);
5945
+ const [addingPlayer, setAddingPlayer] = react.useState(false);
5946
+ const commitGroup = (name) => {
5947
+ if (!name) return;
5948
+ set("groups", [...value.groups, name]);
5949
+ setAddingGroup(false);
5950
+ };
5951
+ const setGroup = (index, name) => {
5952
+ const next = [...value.groups];
5953
+ next[index] = name;
5954
+ set("groups", next.filter((g) => g !== ""));
5955
+ };
5956
+ const removeGroup = (index) => set("groups", value.groups.filter((_, i) => i !== index));
5957
+ const commitIdentifier = (id) => {
5958
+ if (!id) return;
5959
+ set("identifiers", [...value.identifiers, id]);
5960
+ setAddingPlayer(false);
5961
+ };
5962
+ const setIdentifier = (index, id) => {
5963
+ const next = [...value.identifiers];
5964
+ next[index] = id;
5965
+ set("identifiers", next.filter((x) => x !== ""));
5966
+ };
5967
+ const removeIdentifier = (index) => set("identifiers", value.identifiers.filter((_, i) => i !== index));
5968
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5969
+ core.Flex,
5970
+ {
5971
+ direction: "column",
5972
+ gap: "xs",
5973
+ p: "sm",
5974
+ style: { flex: 1, minHeight: 0, overflowY: "auto" },
5975
+ children: [
5976
+ /* @__PURE__ */ jsxRuntime.jsx(
5977
+ AdminPageTitle,
5978
+ {
5979
+ icon: lucideReact.ShieldCheck,
5980
+ title: title || t("Access", "Access"),
5981
+ color
5982
+ }
5983
+ ),
5984
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.4)", children: description || t(
5985
+ "AccessOverrideDesc",
5986
+ "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."
5987
+ ) }),
5988
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { icon: lucideReact.Users, label: t("AccessGroups", "Groups") }),
5989
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "xxs", children: [
5990
+ value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsxRuntime.jsx(EmptyHint, { text: t("AccessNoGroups", "No groups added.") }),
5991
+ value.groups.map((name, i) => /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
5992
+ /* @__PURE__ */ jsxRuntime.jsx(
5993
+ GroupName,
5994
+ {
5995
+ value: name,
5996
+ onChange: (v) => setGroup(i, v),
5997
+ type: groupType,
5998
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
5999
+ style: { flex: 1 }
6000
+ }
6001
+ ),
6002
+ /* @__PURE__ */ jsxRuntime.jsx(
6003
+ core.ActionIcon,
6004
+ {
6005
+ variant: "subtle",
6006
+ color: "red",
6007
+ size: "md",
6008
+ onClick: () => removeGroup(i),
6009
+ title: t("Remove", "Remove"),
6010
+ "aria-label": t("Remove", "Remove"),
6011
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
6012
+ }
6013
+ )
6014
+ ] }, i)),
6015
+ addingGroup && /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
6016
+ /* @__PURE__ */ jsxRuntime.jsx(
6017
+ GroupName,
6018
+ {
6019
+ value: "",
6020
+ onChange: (v) => commitGroup(v),
6021
+ type: groupType,
6022
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
6023
+ style: { flex: 1 }
6024
+ }
6025
+ ),
6026
+ /* @__PURE__ */ jsxRuntime.jsx(
6027
+ core.ActionIcon,
6028
+ {
6029
+ variant: "subtle",
6030
+ color: "red",
6031
+ size: "md",
6032
+ onClick: () => setAddingGroup(false),
6033
+ title: t("Remove", "Remove"),
6034
+ "aria-label": t("Remove", "Remove"),
6035
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
6036
+ }
6037
+ )
6038
+ ] })
6039
+ ] }),
6040
+ !addingGroup && /* @__PURE__ */ jsxRuntime.jsx(AddRowButton, { label: t("AccessAddGroup", "Add group"), onClick: () => setAddingGroup(true) }),
6041
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { icon: lucideReact.UserRound, label: t("AccessPlayers", "Players") }),
6042
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "xxs", children: [
6043
+ value.identifiers.length === 0 && !addingPlayer && /* @__PURE__ */ jsxRuntime.jsx(EmptyHint, { text: t("AccessNoPlayers", "No players added.") }),
6044
+ value.identifiers.map((id, i) => /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
6045
+ /* @__PURE__ */ jsxRuntime.jsx(
6046
+ PlayerSelect,
6047
+ {
6048
+ value: id || null,
6049
+ onChange: (p) => setIdentifier(i, p?.citizenId ?? ""),
6050
+ includeOffline,
6051
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
6052
+ style: { flex: 1 },
6053
+ size: "xs"
6054
+ }
6055
+ ),
6056
+ /* @__PURE__ */ jsxRuntime.jsx(
6057
+ core.ActionIcon,
6058
+ {
6059
+ variant: "subtle",
6060
+ color: "red",
6061
+ size: "md",
6062
+ onClick: () => removeIdentifier(i),
6063
+ title: t("Remove", "Remove"),
6064
+ "aria-label": t("Remove", "Remove"),
6065
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
6066
+ }
6067
+ )
6068
+ ] }, i)),
6069
+ addingPlayer && /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
6070
+ /* @__PURE__ */ jsxRuntime.jsx(
6071
+ PlayerSelect,
6072
+ {
6073
+ value: null,
6074
+ onChange: (p) => commitIdentifier(p?.citizenId ?? ""),
6075
+ includeOffline,
6076
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
6077
+ style: { flex: 1 },
6078
+ size: "xs"
6079
+ }
6080
+ ),
6081
+ /* @__PURE__ */ jsxRuntime.jsx(
6082
+ core.ActionIcon,
6083
+ {
6084
+ variant: "subtle",
6085
+ color: "red",
6086
+ size: "md",
6087
+ onClick: () => setAddingPlayer(false),
6088
+ title: t("Remove", "Remove"),
6089
+ "aria-label": t("Remove", "Remove"),
6090
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
6091
+ }
6092
+ )
6093
+ ] })
6094
+ ] }),
6095
+ !addingPlayer && /* @__PURE__ */ jsxRuntime.jsx(AddRowButton, { label: t("AccessAddPlayer", "Add player"), onClick: () => setAddingPlayer(true) })
6096
+ ]
6097
+ }
6098
+ );
6099
+ }
6100
+ function AddRowButton({ label, onClick }) {
6101
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6102
+ core.Flex,
6103
+ {
6104
+ align: "center",
6105
+ justify: "center",
6106
+ gap: "xs",
6107
+ onClick,
6108
+ role: "button",
6109
+ tabIndex: 0,
6110
+ onKeyDown: (e) => {
6111
+ if (e.key === "Enter" || e.key === " ") {
6112
+ e.preventDefault();
6113
+ onClick();
6114
+ }
6115
+ },
6116
+ py: "xs",
6117
+ style: {
6118
+ cursor: "pointer",
6119
+ border: "0.1vh dashed rgba(255,255,255,0.12)",
6120
+ borderRadius: "0.4vh",
6121
+ transition: "background 0.15s, border-color 0.15s"
6122
+ },
6123
+ onMouseEnter: (e) => {
6124
+ e.currentTarget.style.background = "rgba(255,255,255,0.03)";
6125
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.2)";
6126
+ },
6127
+ onMouseLeave: (e) => {
6128
+ e.currentTarget.style.background = "transparent";
6129
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.12)";
6130
+ },
6131
+ children: [
6132
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: "1.4vh", color: "rgba(255,255,255,0.4)" }),
6133
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "rgba(255,255,255,0.4)", children: label })
6134
+ ]
6135
+ }
6136
+ );
6137
+ }
5718
6138
 
5719
6139
  // src/utils/gtaAnimPostFx.ts
5720
6140
  var GTA_ANIM_POST_FX_GROUP_ORDER = [
@@ -6304,7 +6724,7 @@ function ScenarioSelect({
6304
6724
  }
6305
6725
  );
6306
6726
  }
6307
- var t = (key, fallback) => {
6727
+ var t2 = (key, fallback) => {
6308
6728
  const v = locale(key);
6309
6729
  return v === key ? fallback : v;
6310
6730
  };
@@ -6372,7 +6792,7 @@ function DiscordRoleSelect(props) {
6372
6792
  return map;
6373
6793
  }, [roles]);
6374
6794
  if (errorCode === "NotConfigured") {
6375
- const notConfiguredPlaceholder = t(
6795
+ const notConfiguredPlaceholder = t2(
6376
6796
  "DiscordNotConfigured",
6377
6797
  "Not configured \u2014 set in /dirk_lib"
6378
6798
  );
@@ -6440,9 +6860,9 @@ function DiscordRoleSelect(props) {
6440
6860
  e.stopPropagation();
6441
6861
  refresh();
6442
6862
  },
6443
- title: t("Refresh", "Refresh"),
6863
+ title: t2("Refresh", "Refresh"),
6444
6864
  loading,
6445
- "aria-label": t("Refresh", "Refresh"),
6865
+ "aria-label": t2("Refresh", "Refresh"),
6446
6866
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.2vh" })
6447
6867
  }
6448
6868
  );
@@ -6452,7 +6872,7 @@ function DiscordRoleSelect(props) {
6452
6872
  {
6453
6873
  label,
6454
6874
  size,
6455
- placeholder: placeholder || t("PickRoles", "Pick roles..."),
6875
+ placeholder: placeholder || t2("PickRoles", "Pick roles..."),
6456
6876
  data,
6457
6877
  value: props.value || [],
6458
6878
  onChange: (ids) => props.onChange(ids),
@@ -6464,7 +6884,7 @@ function DiscordRoleSelect(props) {
6464
6884
  rightSectionPointerEvents: "all",
6465
6885
  style,
6466
6886
  styles,
6467
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
6887
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
6468
6888
  }
6469
6889
  );
6470
6890
  }
@@ -6473,7 +6893,7 @@ function DiscordRoleSelect(props) {
6473
6893
  {
6474
6894
  label,
6475
6895
  size,
6476
- placeholder: placeholder || t("PickRole", "Pick a role..."),
6896
+ placeholder: placeholder || t2("PickRole", "Pick a role..."),
6477
6897
  data,
6478
6898
  value: props.value || null,
6479
6899
  onChange: (id) => props.onChange(id),
@@ -6485,11 +6905,11 @@ function DiscordRoleSelect(props) {
6485
6905
  rightSectionPointerEvents: "all",
6486
6906
  style,
6487
6907
  styles,
6488
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
6908
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
6489
6909
  }
6490
6910
  );
6491
6911
  }
6492
- var t2 = (key, fallback) => {
6912
+ var t3 = (key, fallback) => {
6493
6913
  const v = locale(key);
6494
6914
  return v === key ? fallback : v;
6495
6915
  };
@@ -6510,7 +6930,7 @@ function FrameworkHint({ framework }) {
6510
6930
  const theme = core.useMantineTheme();
6511
6931
  if (!framework) return null;
6512
6932
  const hintKey = `AccountFrameworkHint_${framework}`;
6513
- const text = t2(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
6933
+ const text = t3(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
6514
6934
  return /* @__PURE__ */ jsxRuntime.jsxs(
6515
6935
  core.Flex,
6516
6936
  {
@@ -6594,9 +7014,9 @@ function AccountSelect(props) {
6594
7014
  e.stopPropagation();
6595
7015
  refresh();
6596
7016
  },
6597
- title: t2("Refresh", "Refresh"),
7017
+ title: t3("Refresh", "Refresh"),
6598
7018
  loading,
6599
- "aria-label": t2("Refresh", "Refresh"),
7019
+ "aria-label": t3("Refresh", "Refresh"),
6600
7020
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.2vh" })
6601
7021
  }
6602
7022
  );
@@ -6610,14 +7030,14 @@ function AccountSelect(props) {
6610
7030
  rightSection: refreshButton,
6611
7031
  rightSectionPointerEvents: "all",
6612
7032
  style,
6613
- nothingFoundMessage: t2("NoAccounts", "No accounts available")
7033
+ nothingFoundMessage: t3("NoAccounts", "No accounts available")
6614
7034
  };
6615
7035
  return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "0.3vh", style, children: [
6616
7036
  props.multi ? /* @__PURE__ */ jsxRuntime.jsx(
6617
7037
  core.MultiSelect,
6618
7038
  {
6619
7039
  ...sharedSelectProps,
6620
- placeholder: placeholder || t2("PickAccounts", "Pick accounts (or type custom)..."),
7040
+ placeholder: placeholder || t3("PickAccounts", "Pick accounts (or type custom)..."),
6621
7041
  value: props.value || [],
6622
7042
  onChange: (names) => props.onChange(names),
6623
7043
  renderOption,
@@ -6629,7 +7049,7 @@ function AccountSelect(props) {
6629
7049
  core.Select,
6630
7050
  {
6631
7051
  ...sharedSelectProps,
6632
- placeholder: placeholder || t2("PickAccount", "Pick an account..."),
7052
+ placeholder: placeholder || t3("PickAccount", "Pick an account..."),
6633
7053
  value: props.value || null,
6634
7054
  onChange: (name) => props.onChange(name),
6635
7055
  renderOption,
@@ -6921,175 +7341,6 @@ function WorldPositionGotoButton2({
6921
7341
  }
6922
7342
  ) });
6923
7343
  }
6924
- function usePlayers(opts = {}) {
6925
- const {
6926
- includeOffline = false,
6927
- search = "",
6928
- limit = 50,
6929
- staleTimeMs,
6930
- refetchIntervalMs
6931
- } = opts;
6932
- const query = reactQuery.useQuery({
6933
- queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
6934
- queryFn: async () => {
6935
- const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
6936
- const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
6937
- const result = await fetchNui(
6938
- "ADMIN_TOOL_QUERY",
6939
- payload,
6940
- // Browser-dev fallback. Returns a couple of mock players so the
6941
- // dev shell isn't blank.
6942
- includeOffline ? [
6943
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
6944
- { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
6945
- ] : [
6946
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
6947
- ]
6948
- );
6949
- return Array.isArray(result) ? result : [];
6950
- },
6951
- staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
6952
- gcTime: 5 * 6e4,
6953
- refetchInterval: refetchIntervalMs ?? false,
6954
- refetchOnWindowFocus: false,
6955
- placeholderData: includeOffline ? reactQuery.keepPreviousData : void 0
6956
- });
6957
- return {
6958
- players: query.data ?? [],
6959
- isLoading: query.isLoading,
6960
- isFetching: query.isFetching,
6961
- error: query.error ?? null,
6962
- refresh: () => query.refetch()
6963
- };
6964
- }
6965
- var DEBOUNCE_MS = 300;
6966
- var ONLINE_COLOR = "#3FA83F";
6967
- var OFFLINE_COLOR = "#E54141";
6968
- function PlayerSelect({
6969
- value,
6970
- onChange,
6971
- includeOffline = false,
6972
- limit = 50,
6973
- searchable: searchableProp,
6974
- placeholder,
6975
- nothingFoundMessage: nothingFoundMessageProp,
6976
- loadingLabel = "Loading\u2026",
6977
- onlineLabel = "Online",
6978
- offlineLabel = "Offline",
6979
- refreshLabel = "Refresh player list",
6980
- ...rest
6981
- }) {
6982
- const theme = core.useMantineTheme();
6983
- const color = theme.colors[theme.primaryColor][5];
6984
- const [searchInput, setSearchInput] = react.useState("");
6985
- const [debouncedSearch, setDebouncedSearch] = react.useState("");
6986
- const { players, isFetching, refresh } = usePlayers({
6987
- includeOffline,
6988
- search: includeOffline ? debouncedSearch : void 0,
6989
- limit
6990
- });
6991
- const sortedPlayers = react.useMemo(
6992
- () => [...players].sort((a, b) => {
6993
- if (a.online !== b.online) return a.online ? -1 : 1;
6994
- return a.charName.localeCompare(b.charName);
6995
- }),
6996
- [players]
6997
- );
6998
- const playerByCitizen = react.useMemo(() => {
6999
- const m = /* @__PURE__ */ new Map();
7000
- for (const p of sortedPlayers) m.set(p.citizenId, p);
7001
- return m;
7002
- }, [sortedPlayers]);
7003
- const data = react.useMemo(() => {
7004
- const items = sortedPlayers.map((p) => ({
7005
- value: p.citizenId,
7006
- label: formatLabel(p)
7007
- }));
7008
- if (value && !items.some((i) => i.value === value)) {
7009
- items.unshift({ value, label: value });
7010
- }
7011
- return items;
7012
- }, [sortedPlayers, value]);
7013
- const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
7014
- const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
7015
- react.useEffect(() => {
7016
- if (!includeOffline) return;
7017
- if (selectedLabel && searchInput === selectedLabel) return;
7018
- const t3 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
7019
- return () => clearTimeout(t3);
7020
- }, [searchInput, includeOffline, selectedLabel]);
7021
- const renderOption = ({ option }) => {
7022
- const p = playerByCitizen.get(option.value);
7023
- if (!p) return option.label;
7024
- return /* @__PURE__ */ jsxRuntime.jsxs(core.Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
7025
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
7026
- /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
7027
- ] });
7028
- };
7029
- return /* @__PURE__ */ jsxRuntime.jsx(
7030
- core.Select,
7031
- {
7032
- ...rest,
7033
- data,
7034
- value: value ?? null,
7035
- onChange: (v) => {
7036
- if (!v) return onChange(null);
7037
- const player = playerByCitizen.get(v) ?? null;
7038
- onChange(player);
7039
- },
7040
- searchable: searchableProp ?? true,
7041
- searchValue: includeOffline ? searchInput : void 0,
7042
- onSearchChange: includeOffline ? setSearchInput : void 0,
7043
- placeholder,
7044
- nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
7045
- maxDropdownHeight: 300,
7046
- renderOption,
7047
- leftSection: selectedPlayer ? /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
7048
- rightSectionWidth: "3.5vh",
7049
- rightSectionPointerEvents: "all",
7050
- rightSection: /* @__PURE__ */ jsxRuntime.jsx(
7051
- core.ActionIcon,
7052
- {
7053
- variant: "subtle",
7054
- size: "sm",
7055
- onClick: (e) => {
7056
- e.stopPropagation();
7057
- refresh();
7058
- },
7059
- "aria-label": refreshLabel,
7060
- title: refreshLabel,
7061
- style: { marginRight: "0.6vh" },
7062
- children: isFetching ? /* @__PURE__ */ jsxRuntime.jsx(core.Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.4vh", color })
7063
- }
7064
- )
7065
- }
7066
- );
7067
- }
7068
- function StatusDot({ online, onlineLabel, offlineLabel }) {
7069
- return /* @__PURE__ */ jsxRuntime.jsx(
7070
- "span",
7071
- {
7072
- title: online ? onlineLabel : offlineLabel,
7073
- style: {
7074
- display: "inline-block",
7075
- width: "0.9vh",
7076
- height: "0.9vh",
7077
- borderRadius: "50%",
7078
- backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
7079
- boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
7080
- flexShrink: 0
7081
- }
7082
- }
7083
- );
7084
- }
7085
- function formatLabel(p) {
7086
- const parts = [p.charName || p.citizenId];
7087
- if (p.online) {
7088
- if (p.name) parts.push(p.name);
7089
- if (p.id != null) parts.push(`#${p.id}`);
7090
- }
7091
- return parts.join(" \xB7 ");
7092
- }
7093
7344
  function usePickDoor() {
7094
7345
  const begin = useAdminToolStore((s) => s.begin);
7095
7346
  return async () => {
@@ -7366,6 +7617,7 @@ function BlipMarker({
7366
7617
  );
7367
7618
  }
7368
7619
 
7620
+ exports.AccessOverrideSection = AccessOverrideSection;
7369
7621
  exports.AccountSelect = AccountSelect;
7370
7622
  exports.AdminPageTitle = AdminPageTitle;
7371
7623
  exports.AnimPostFxSelect = AnimPostFxSelect;