dirk-cfx-react 1.1.90 → 1.1.92

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, Plus, UserRound, 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;
@@ -1261,11 +1261,11 @@ var colorNames = {
1261
1261
  Yellow: { r: 255, g: 255, b: 0 },
1262
1262
  YellowGreen: { r: 154, g: 205, b: 50 }
1263
1263
  };
1264
- function colorWithAlpha(color, alpha18) {
1264
+ function colorWithAlpha(color, alpha19) {
1265
1265
  const lowerCasedColor = color.toLowerCase();
1266
1266
  if (colorNames[lowerCasedColor]) {
1267
1267
  const rgb = colorNames[lowerCasedColor];
1268
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha18})`;
1268
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha19})`;
1269
1269
  }
1270
1270
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1271
1271
  const hex = color.slice(1);
@@ -1273,12 +1273,12 @@ function colorWithAlpha(color, alpha18) {
1273
1273
  const r = bigint >> 16 & 255;
1274
1274
  const g = bigint >> 8 & 255;
1275
1275
  const b = bigint & 255;
1276
- return `rgba(${r}, ${g}, ${b}, ${alpha18})`;
1276
+ return `rgba(${r}, ${g}, ${b}, ${alpha19})`;
1277
1277
  }
1278
1278
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1279
1279
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1280
1280
  if (result) {
1281
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha18})`;
1281
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha19})`;
1282
1282
  }
1283
1283
  }
1284
1284
  return color;
@@ -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
@@ -3742,11 +3757,11 @@ function ConfirmModal({
3742
3757
  placeholder: confirmText,
3743
3758
  value: typed,
3744
3759
  onChange: (e) => setTyped(e.currentTarget.value),
3745
- styles: (t3) => ({
3760
+ styles: (t4) => ({
3746
3761
  input: {
3747
- backgroundColor: alpha(t3.colors.dark[7], 0.5),
3762
+ backgroundColor: alpha(t4.colors.dark[7], 0.5),
3748
3763
  border: `0.1vh solid ${alpha(
3749
- typed === confirmText ? "#ef4444" : t3.colors.dark[5],
3764
+ typed === confirmText ? "#ef4444" : t4.colors.dark[5],
3750
3765
  0.5
3751
3766
  )}`,
3752
3767
  color: "rgba(255,255,255,0.85)",
@@ -4424,7 +4439,7 @@ function useAdminState(key, initial) {
4424
4439
  useEffect(() => {
4425
4440
  const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
4426
4441
  listeners.set(key, set);
4427
- const handler = () => setTick((t3) => t3 + 1);
4442
+ const handler = () => setTick((t4) => t4 + 1);
4428
4443
  set.add(handler);
4429
4444
  return () => {
4430
4445
  set.delete(handler);
@@ -4663,6 +4678,16 @@ function mergeMantineThemeSafe(base, custom, override) {
4663
4678
  }
4664
4679
  };
4665
4680
  }
4681
+ function changedTopLevelSections(changedFields) {
4682
+ if (!changedFields || changedFields.length === 0) return [];
4683
+ const sections = /* @__PURE__ */ new Set();
4684
+ for (const path of changedFields) {
4685
+ if (typeof path !== "string" || path.length === 0) continue;
4686
+ const dot = path.indexOf(".");
4687
+ sections.add(dot === -1 ? path : path.slice(0, dot));
4688
+ }
4689
+ return Array.from(sections);
4690
+ }
4666
4691
  var _instance = null;
4667
4692
  function getScriptConfigInstance() {
4668
4693
  if (!_instance) throw new Error("[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel");
@@ -4672,15 +4697,18 @@ function createScriptConfig(defaultValue) {
4672
4697
  const store2 = create(() => defaultValue);
4673
4698
  let clientVersion = 0;
4674
4699
  const useScriptConfigHooks = () => {
4675
- useNuiEvent("UPDATE_SCRIPT_CONFIG", (data) => {
4676
- if (!data) return;
4677
- if (typeof data.clientVersion === "number") {
4678
- clientVersion = data.clientVersion;
4679
- }
4680
- if (data.config && typeof data.config === "object") {
4681
- store2.setState((prev) => ({ ...prev, ...data.config }));
4700
+ useNuiEvent(
4701
+ "UPDATE_SCRIPT_CONFIG",
4702
+ (data) => {
4703
+ if (!data) return;
4704
+ if (typeof data.clientVersion === "number") {
4705
+ clientVersion = data.clientVersion;
4706
+ }
4707
+ if (data.config && typeof data.config === "object") {
4708
+ store2.setState((prev) => ({ ...prev, ...data.config }));
4709
+ }
4682
4710
  }
4683
- });
4711
+ );
4684
4712
  };
4685
4713
  const fetchScriptConfig = async () => {
4686
4714
  try {
@@ -4696,12 +4724,19 @@ function createScriptConfig(defaultValue) {
4696
4724
  }
4697
4725
  return null;
4698
4726
  };
4699
- const updateScriptConfig = async (newConfig) => {
4727
+ const updateScriptConfig = async (newConfig, changedFields) => {
4700
4728
  store2.setState((prev) => ({ ...prev, ...newConfig }));
4701
- const response = await fetchNui("UPDATE_SCRIPT_CONFIG", {
4702
- data: newConfig,
4703
- expectedVersion: clientVersion
4704
- });
4729
+ const sections = changedTopLevelSections(changedFields);
4730
+ let payload;
4731
+ if (sections.length > 0) {
4732
+ const current = store2.getState();
4733
+ const delta = {};
4734
+ for (const key of sections) delta[key] = current[key];
4735
+ payload = { data: delta, expectedVersion: clientVersion, sectionReplace: true };
4736
+ } else {
4737
+ payload = { data: newConfig, expectedVersion: clientVersion };
4738
+ }
4739
+ const response = await fetchNui("UPDATE_SCRIPT_CONFIG", payload);
4705
4740
  if (response?.meta?.client_version != null) {
4706
4741
  clientVersion = response.meta.client_version;
4707
4742
  }
@@ -5570,7 +5605,7 @@ function ConfigPanel(props) {
5570
5605
  if (isSaving) return;
5571
5606
  setIsSaving(true);
5572
5607
  try {
5573
- const result = await updateConfig(form.values);
5608
+ const result = await updateConfig(form.values, form.changedFields);
5574
5609
  if (result?.success) {
5575
5610
  form.reinitialize(cloneConfig(form.values));
5576
5611
  dirkQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
@@ -6604,24 +6639,6 @@ var MANTINE_COLOR_OPTIONS = [
6604
6639
  "yellow",
6605
6640
  "orange"
6606
6641
  ].map((value) => ({ value, label: value }));
6607
- var DEFAULT_PALETTE = [
6608
- "#f0f4ff",
6609
- "#d9e3ff",
6610
- "#bfcfff",
6611
- "#a6bbff",
6612
- "#8ca7ff",
6613
- "#7393ff",
6614
- "#5a7fff",
6615
- "#406bff",
6616
- "#2547ff",
6617
- "#0b33ff"
6618
- ];
6619
- var DEFAULT_VALUE = {
6620
- useOverride: false,
6621
- primaryColor: "dirk",
6622
- primaryShade: 5,
6623
- customTheme: DEFAULT_PALETTE
6624
- };
6625
6642
  function GroupLabel({ label: label2 }) {
6626
6643
  return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", mt: "xxs", children: [
6627
6644
  /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
@@ -6636,10 +6653,10 @@ function ThemeOverrideSection({
6636
6653
  const color = mantineTheme.colors[mantineTheme.primaryColor][5];
6637
6654
  const raw = useFormField(schemaKey);
6638
6655
  const value = {
6639
- useOverride: raw?.useOverride ?? DEFAULT_VALUE.useOverride,
6640
- primaryColor: raw?.primaryColor ?? DEFAULT_VALUE.primaryColor,
6641
- primaryShade: raw?.primaryShade ?? DEFAULT_VALUE.primaryShade,
6642
- customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : DEFAULT_VALUE.customTheme
6656
+ useOverride: raw?.useOverride ?? defaultThemeOverride.useOverride,
6657
+ primaryColor: raw?.primaryColor ?? defaultThemeOverride.primaryColor,
6658
+ primaryShade: raw?.primaryShade ?? defaultThemeOverride.primaryShade,
6659
+ customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : defaultThemeOverride.customTheme
6643
6660
  };
6644
6661
  const { setValue } = useFormActions();
6645
6662
  const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
@@ -6846,212 +6863,695 @@ function SwatchTile({
6846
6863
  ) })
6847
6864
  ] });
6848
6865
  }
6849
-
6850
- // src/utils/gtaAnimPostFx.ts
6851
- var GTA_ANIM_POST_FX_GROUP_ORDER = [
6852
- "Wake Up / Sleep",
6853
- "Character Switch",
6854
- "Cinematic Push-In",
6855
- "Death / Fail",
6856
- "Heist",
6857
- "Minigame",
6858
- "Drug / Trip",
6859
- "Color Filter / Tint",
6860
- "Multiplayer",
6861
- "Misc"
6862
- ];
6863
- var GTA_ANIM_POST_FX = [
6864
- // ── Wake Up / Sleep (community-known; not in altV docs) ────────────────────
6865
- { name: "ULP_PlayerWakeUp", label: "Player Wake Up", group: "Wake Up / Sleep" },
6866
- { name: "MinigameEndNeutral", label: "Minigame End \u2014 Neutral", group: "Wake Up / Sleep" },
6867
- { name: "MinigameEndFranklin", label: "Minigame End \u2014 Franklin", group: "Wake Up / Sleep" },
6868
- { name: "MinigameEndMichael", label: "Minigame End \u2014 Michael", group: "Wake Up / Sleep" },
6869
- { name: "MinigameEndTrevor", label: "Minigame End \u2014 Trevor", group: "Wake Up / Sleep" },
6870
- // ── Character Switch (Franklin / Michael / Trevor / Neutral variants) ─────
6871
- { name: "SwitchHUDIn", group: "Character Switch" },
6872
- { name: "SwitchHUDOut", group: "Character Switch" },
6873
- { name: "SwitchHUDFranklinIn", group: "Character Switch" },
6874
- { name: "SwitchHUDFranklinOut", group: "Character Switch" },
6875
- { name: "SwitchHUDMichaelIn", group: "Character Switch" },
6876
- { name: "SwitchHUDMichaelOut", group: "Character Switch" },
6877
- { name: "SwitchHUDTrevorIn", group: "Character Switch" },
6878
- { name: "SwitchHUDTrevorOut", group: "Character Switch" },
6879
- { name: "SwitchOpenFranklin", group: "Character Switch" },
6880
- { name: "SwitchOpenFranklinIn", group: "Character Switch" },
6881
- { name: "SwitchOpenFranklinOut", group: "Character Switch" },
6882
- { name: "SwitchOpenMichaelIn", group: "Character Switch" },
6883
- { name: "SwitchOpenMichaelMid", group: "Character Switch" },
6884
- { name: "SwitchOpenMichaelOut", group: "Character Switch" },
6885
- { name: "SwitchOpenTrevorIn", group: "Character Switch" },
6886
- { name: "SwitchOpenTrevorOut", group: "Character Switch" },
6887
- { name: "SwitchOpenNeutralFIB5", group: "Character Switch" },
6888
- { name: "SwitchOpenNeutralOutHeist", group: "Character Switch" },
6889
- { name: "SwitchSceneFranklin", group: "Character Switch" },
6890
- { name: "SwitchSceneMichael", group: "Character Switch" },
6891
- { name: "SwitchSceneNeutral", group: "Character Switch" },
6892
- { name: "SwitchSceneTrevor", group: "Character Switch" },
6893
- { name: "SwitchShortFranklinIn", group: "Character Switch" },
6894
- { name: "SwitchShortFranklinMid", group: "Character Switch" },
6895
- { name: "SwitchShortMichaelIn", group: "Character Switch" },
6896
- { name: "SwitchShortMichaelMid", group: "Character Switch" },
6897
- { name: "SwitchShortNeutralIn", group: "Character Switch" },
6898
- { name: "SwitchShortNeutralMid", group: "Character Switch" },
6899
- { name: "SwitchShortTrevorIn", group: "Character Switch" },
6900
- { name: "SwitchShortTrevorMid", group: "Character Switch" },
6901
- { name: "switch_cam_1", group: "Character Switch" },
6902
- { name: "switch_cam_2", group: "Character Switch" },
6903
- // ── Cinematic Push-In (slow zoom intro to a character) ────────────────────
6904
- { name: "CamPushInFranklin", group: "Cinematic Push-In" },
6905
- { name: "CamPushInMichael", group: "Cinematic Push-In" },
6906
- { name: "CamPushInTrevor", group: "Cinematic Push-In" },
6907
- { name: "CamPushInNeutral", group: "Cinematic Push-In" },
6908
- { name: "BeastIntroScene", label: "Beast Intro", group: "Cinematic Push-In" },
6909
- { name: "BeastLaunch", group: "Cinematic Push-In" },
6910
- { name: "BeastTransition", group: "Cinematic Push-In" },
6911
- // ── Death / Fail (red tint, slow-mo blur) ─────────────────────────────────
6912
- { name: "DeathFailNeutralIn", group: "Death / Fail" },
6913
- { name: "DeathFailFranklinIn", group: "Death / Fail" },
6914
- { name: "DeathFailMichaelIn", group: "Death / Fail" },
6915
- { name: "DeathFailTrevorIn", group: "Death / Fail" },
6916
- { name: "DeathFailMPIn", label: "Death Fail \u2014 MP", group: "Death / Fail" },
6917
- { name: "DeathFailMPDark", label: "Death Fail \u2014 MP Dark", group: "Death / Fail" },
6918
- { name: "DeathFailOut", group: "Death / Fail" },
6919
- { name: "Rampage", group: "Death / Fail" },
6920
- { name: "RampageOut", group: "Death / Fail" },
6921
- // ── Heist (celebration / locate / pass) ───────────────────────────────────
6922
- { name: "HeistCelebEnd", group: "Heist" },
6923
- { name: "HeistCelebPass", group: "Heist" },
6924
- { name: "HeistCelebPassBW", label: "Heist Celeb Pass (B/W)", group: "Heist" },
6925
- { name: "HeistCelebToast", group: "Heist" },
6926
- { name: "HeistLocate", group: "Heist" },
6927
- { name: "HeistTripSkipFade", group: "Heist" },
6928
- // ── Minigame / Menu ───────────────────────────────────────────────────────
6929
- { name: "MenuMGIn", group: "Minigame" },
6930
- { name: "MenuMGHeistIn", group: "Minigame" },
6931
- { name: "MenuMGHeistIntro", group: "Minigame" },
6932
- { name: "MenuMGHeistOut", group: "Minigame" },
6933
- { name: "MenuMGHeistTint", group: "Minigame" },
6934
- { name: "MenuMGSelectionIn", group: "Minigame" },
6935
- { name: "MenuMGSelectionTint", group: "Minigame" },
6936
- { name: "MenuMGTournamentIn", group: "Minigame" },
6937
- { name: "MenuMGTournamentTint", group: "Minigame" },
6938
- { name: "MinigameTransitionIn", group: "Minigame" },
6939
- { name: "MinigameTransitionOut", group: "Minigame" },
6940
- // ── Drug / Trip (psychedelic, blur, distortion) ───────────────────────────
6941
- { name: "DMT_flight", group: "Drug / Trip" },
6942
- { name: "DMT_flight_intro", group: "Drug / Trip" },
6943
- { name: "PeyoteIn", group: "Drug / Trip" },
6944
- { name: "PeyoteOut", group: "Drug / Trip" },
6945
- { name: "PeyoteEndIn", group: "Drug / Trip" },
6946
- { name: "PeyoteEndOut", group: "Drug / Trip" },
6947
- { name: "DrugsDrivingIn", group: "Drug / Trip" },
6948
- { name: "DrugsDrivingOut", group: "Drug / Trip" },
6949
- { name: "DrugsMichaelAliensFight", group: "Drug / Trip" },
6950
- { name: "DrugsMichaelAliensFightIn", group: "Drug / Trip" },
6951
- { name: "DrugsMichaelAliensFightOut", group: "Drug / Trip" },
6952
- { name: "DrugsTrevorClownsFight", group: "Drug / Trip" },
6953
- { name: "DrugsTrevorClownsFightIn", group: "Drug / Trip" },
6954
- { name: "DrugsTrevorClownsFightOut", group: "Drug / Trip" },
6955
- { name: "ChopVision", label: "Chop Vision (dog)", group: "Drug / Trip" },
6956
- // ── Color Filter / Tint (one-off and biker formation filters) ─────────────
6957
- { name: "PPFilter", group: "Color Filter / Tint" },
6958
- { name: "PPFilterOut", group: "Color Filter / Tint" },
6959
- { name: "PPGreen", group: "Color Filter / Tint" },
6960
- { name: "PPGreenOut", group: "Color Filter / Tint" },
6961
- { name: "PPOrange", group: "Color Filter / Tint" },
6962
- { name: "PPOrangeOut", group: "Color Filter / Tint" },
6963
- { name: "PPPink", group: "Color Filter / Tint" },
6964
- { name: "PPPinkOut", group: "Color Filter / Tint" },
6965
- { name: "PPPurple", group: "Color Filter / Tint" },
6966
- { name: "PPPurpleOut", group: "Color Filter / Tint" },
6967
- { name: "BikerFilter", group: "Color Filter / Tint" },
6968
- { name: "BikerFilterOut", group: "Color Filter / Tint" },
6969
- { name: "BikerFormation", group: "Color Filter / Tint" },
6970
- { name: "BikerFormationOut", group: "Color Filter / Tint" },
6971
- { name: "InchOrange", group: "Color Filter / Tint" },
6972
- { name: "InchOrangeOut", group: "Color Filter / Tint" },
6973
- { name: "InchPurple", group: "Color Filter / Tint" },
6974
- { name: "InchPurpleOut", group: "Color Filter / Tint" },
6975
- { name: "InchPickup", group: "Color Filter / Tint" },
6976
- { name: "InchPickupOut", group: "Color Filter / Tint" },
6977
- { name: "TinyRacerGreen", group: "Color Filter / Tint" },
6978
- { name: "TinyRacerGreenOut", group: "Color Filter / Tint" },
6979
- { name: "TinyRacerPink", group: "Color Filter / Tint" },
6980
- { name: "TinyRacerPinkOut", group: "Color Filter / Tint" },
6981
- { name: "TinyRacerIntroCam", group: "Color Filter / Tint" },
6982
- { name: "DeadlineNeon", group: "Color Filter / Tint" },
6983
- // ── Multiplayer (celeb, killstreak, race, etc.) ───────────────────────────
6984
- { name: "MP_Celeb_Win", group: "Multiplayer" },
6985
- { name: "MP_Celeb_Win_Out", group: "Multiplayer" },
6986
- { name: "MP_Celeb_Lose", group: "Multiplayer" },
6987
- { name: "MP_Celeb_Lose_Out", group: "Multiplayer" },
6988
- { name: "MP_Celeb_Preload", group: "Multiplayer" },
6989
- { name: "MP_Celeb_Preload_Fade", group: "Multiplayer" },
6990
- { name: "MP_Bull_tost", group: "Multiplayer" },
6991
- { name: "MP_Bull_tost_Out", group: "Multiplayer" },
6992
- { name: "MP_Killstreak", group: "Multiplayer" },
6993
- { name: "MP_Killstreak_Out", group: "Multiplayer" },
6994
- { name: "MP_Loser_Streak_Out", group: "Multiplayer" },
6995
- { name: "MP_OrbitalCannon", group: "Multiplayer" },
6996
- { name: "MP_Powerplay", group: "Multiplayer" },
6997
- { name: "MP_Powerplay_Out", group: "Multiplayer" },
6998
- { name: "MP_SmugglerCheckpoint", group: "Multiplayer" },
6999
- { name: "MP_TransformRaceFlash", group: "Multiplayer" },
7000
- { name: "MP_WarpCheckpoint", group: "Multiplayer" },
7001
- { name: "MP_corona_switch", group: "Multiplayer" },
7002
- { name: "MP_intro_logo", group: "Multiplayer" },
7003
- { name: "MP_job_load", group: "Multiplayer" },
7004
- { name: "MP_race_crash", group: "Multiplayer" },
7005
- // ── Misc (success notifs, special weapons, sniper / pause / explosions) ───
7006
- { name: "SuccessFranklin", group: "Misc" },
7007
- { name: "SuccessMichael", group: "Misc" },
7008
- { name: "SuccessNeutral", group: "Misc" },
7009
- { name: "SuccessTrevor", group: "Misc" },
7010
- { name: "FocusIn", group: "Misc" },
7011
- { name: "FocusOut", group: "Misc" },
7012
- { name: "CrossLine", group: "Misc" },
7013
- { name: "CrossLineOut", group: "Misc" },
7014
- { name: "SniperOverlay", group: "Misc" },
7015
- { name: "ExplosionJosh3", group: "Misc" },
7016
- { name: "WeaponUpgrade", group: "Misc" },
7017
- { name: "Dont_tazeme_bro", label: "Don't Taze Me Bro", group: "Misc" },
7018
- { name: "LostTimeDay", group: "Misc" },
7019
- { name: "LostTimeNight", group: "Misc" },
7020
- { name: "PauseMenuOut", group: "Misc" },
7021
- { name: "pennedIn", group: "Misc" },
7022
- { name: "PennedInOut", group: "Misc" },
7023
- { name: "RaceTurbo", group: "Misc" },
7024
- { name: "DefaultFlash", group: "Misc" }
7025
- ];
7026
- function formatGtaAnimPostFx(name) {
7027
- const entry = GTA_ANIM_POST_FX.find((fx) => fx.name === name);
7028
- return entry?.label ?? name;
7029
- }
7030
- function buildGroupedData2(extraNames = []) {
7031
- const groups = /* @__PURE__ */ new Map();
7032
- const seen = /* @__PURE__ */ new Set();
7033
- for (const fx of GTA_ANIM_POST_FX) {
7034
- if (seen.has(fx.name)) continue;
7035
- seen.add(fx.name);
7036
- if (!groups.has(fx.group)) groups.set(fx.group, []);
7037
- groups.get(fx.group).push(fx);
7038
- }
7039
- const data = GTA_ANIM_POST_FX_GROUP_ORDER.flatMap((g) => {
7040
- const items = groups.get(g);
7041
- if (!items || items.length === 0) return [];
7042
- return [
7043
- {
7044
- group: g,
7045
- items: items.map((fx) => ({
7046
- value: fx.name,
7047
- label: formatGtaAnimPostFx(fx.name)
7048
- }))
7049
- }
7050
- ];
7051
- });
7052
- const knownNames = new Set(GTA_ANIM_POST_FX.map((fx) => fx.name));
7053
- const customs = extraNames.filter((n) => n && !knownNames.has(n));
7054
- if (customs.length > 0) {
6866
+ function usePlayers(opts = {}) {
6867
+ const {
6868
+ includeOffline = false,
6869
+ search = "",
6870
+ limit = 50,
6871
+ staleTimeMs,
6872
+ refetchIntervalMs
6873
+ } = opts;
6874
+ const query = useQuery({
6875
+ queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
6876
+ queryFn: async () => {
6877
+ const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
6878
+ const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
6879
+ const result = await fetchNui(
6880
+ "ADMIN_TOOL_QUERY",
6881
+ payload,
6882
+ // Browser-dev fallback. Returns a couple of mock players so the
6883
+ // dev shell isn't blank.
6884
+ includeOffline ? [
6885
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
6886
+ { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
6887
+ ] : [
6888
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
6889
+ ]
6890
+ );
6891
+ return Array.isArray(result) ? result : [];
6892
+ },
6893
+ staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
6894
+ gcTime: 5 * 6e4,
6895
+ refetchInterval: refetchIntervalMs ?? false,
6896
+ refetchOnWindowFocus: false,
6897
+ placeholderData: includeOffline ? keepPreviousData : void 0
6898
+ });
6899
+ return {
6900
+ players: query.data ?? [],
6901
+ isLoading: query.isLoading,
6902
+ isFetching: query.isFetching,
6903
+ error: query.error ?? null,
6904
+ refresh: () => query.refetch()
6905
+ };
6906
+ }
6907
+ var DEBOUNCE_MS = 300;
6908
+ var ONLINE_COLOR = "#3FA83F";
6909
+ var OFFLINE_COLOR = "#E54141";
6910
+ function PlayerSelect({
6911
+ value,
6912
+ onChange,
6913
+ includeOffline = false,
6914
+ limit = 50,
6915
+ searchable: searchableProp,
6916
+ placeholder,
6917
+ nothingFoundMessage: nothingFoundMessageProp,
6918
+ loadingLabel = "Loading\u2026",
6919
+ onlineLabel = "Online",
6920
+ offlineLabel = "Offline",
6921
+ refreshLabel = "Refresh player list",
6922
+ ...rest
6923
+ }) {
6924
+ const theme2 = useMantineTheme();
6925
+ const color = theme2.colors[theme2.primaryColor][5];
6926
+ const [searchInput, setSearchInput] = useState("");
6927
+ const [debouncedSearch, setDebouncedSearch] = useState("");
6928
+ const { players, isFetching, refresh } = usePlayers({
6929
+ includeOffline,
6930
+ search: includeOffline ? debouncedSearch : void 0,
6931
+ limit
6932
+ });
6933
+ const sortedPlayers = useMemo(
6934
+ () => [...players].sort((a, b) => {
6935
+ if (a.online !== b.online) return a.online ? -1 : 1;
6936
+ return a.charName.localeCompare(b.charName);
6937
+ }),
6938
+ [players]
6939
+ );
6940
+ const playerByCitizen = useMemo(() => {
6941
+ const m = /* @__PURE__ */ new Map();
6942
+ for (const p of sortedPlayers) m.set(p.citizenId, p);
6943
+ return m;
6944
+ }, [sortedPlayers]);
6945
+ const data = useMemo(() => {
6946
+ const items = sortedPlayers.map((p) => ({
6947
+ value: p.citizenId,
6948
+ label: formatLabel(p)
6949
+ }));
6950
+ if (value && !items.some((i) => i.value === value)) {
6951
+ items.unshift({ value, label: value });
6952
+ }
6953
+ return items;
6954
+ }, [sortedPlayers, value]);
6955
+ const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
6956
+ const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
6957
+ useEffect(() => {
6958
+ if (!includeOffline) return;
6959
+ if (selectedLabel && searchInput === selectedLabel) return;
6960
+ const t4 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
6961
+ return () => clearTimeout(t4);
6962
+ }, [searchInput, includeOffline, selectedLabel]);
6963
+ const renderOption = ({ option }) => {
6964
+ const p = playerByCitizen.get(option.value);
6965
+ if (!p) return option.label;
6966
+ return /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
6967
+ /* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
6968
+ /* @__PURE__ */ jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
6969
+ ] });
6970
+ };
6971
+ return /* @__PURE__ */ jsx(
6972
+ Select,
6973
+ {
6974
+ ...rest,
6975
+ data,
6976
+ value: value ?? null,
6977
+ onChange: (v) => {
6978
+ if (!v) return onChange(null);
6979
+ const player = playerByCitizen.get(v) ?? null;
6980
+ onChange(player);
6981
+ },
6982
+ searchable: searchableProp ?? true,
6983
+ searchValue: includeOffline ? searchInput : void 0,
6984
+ onSearchChange: includeOffline ? setSearchInput : void 0,
6985
+ placeholder,
6986
+ nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
6987
+ maxDropdownHeight: 300,
6988
+ renderOption,
6989
+ leftSection: selectedPlayer ? /* @__PURE__ */ jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
6990
+ rightSectionWidth: "3.5vh",
6991
+ rightSectionPointerEvents: "all",
6992
+ rightSection: /* @__PURE__ */ jsx(
6993
+ ActionIcon,
6994
+ {
6995
+ variant: "subtle",
6996
+ size: "sm",
6997
+ onClick: (e) => {
6998
+ e.stopPropagation();
6999
+ refresh();
7000
+ },
7001
+ "aria-label": refreshLabel,
7002
+ title: refreshLabel,
7003
+ style: { marginRight: "0.6vh" },
7004
+ children: isFetching ? /* @__PURE__ */ jsx(Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsx(RefreshCw, { size: "1.4vh", color })
7005
+ }
7006
+ )
7007
+ }
7008
+ );
7009
+ }
7010
+ function StatusDot({ online, onlineLabel, offlineLabel }) {
7011
+ return /* @__PURE__ */ jsx(
7012
+ "span",
7013
+ {
7014
+ title: online ? onlineLabel : offlineLabel,
7015
+ style: {
7016
+ display: "inline-block",
7017
+ width: "0.9vh",
7018
+ height: "0.9vh",
7019
+ borderRadius: "50%",
7020
+ backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
7021
+ boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
7022
+ flexShrink: 0
7023
+ }
7024
+ }
7025
+ );
7026
+ }
7027
+ function formatLabel(p) {
7028
+ const parts = [p.charName || p.citizenId];
7029
+ if (p.online) {
7030
+ if (p.name) parts.push(p.name);
7031
+ if (p.id != null) parts.push(`#${p.id}`);
7032
+ }
7033
+ return parts.join(" \xB7 ");
7034
+ }
7035
+ var COMMON_ACE_GROUPS = ["group.admin", "group.mod", "group.superadmin"];
7036
+ var t = (key, fallback) => {
7037
+ const v = locale(key);
7038
+ return v === key ? fallback : v;
7039
+ };
7040
+ var DEFAULT_VALUE = {
7041
+ groups: [],
7042
+ identifiers: []
7043
+ };
7044
+ function SectionLabel({ icon: Icon, label: label2 }) {
7045
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", mt: "xs", children: [
7046
+ /* @__PURE__ */ jsx(Icon, { size: "1.4vh", color: "rgba(255,255,255,0.3)" }),
7047
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
7048
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: "0.05vh", background: "rgba(255,255,255,0.06)" } })
7049
+ ] });
7050
+ }
7051
+ function EmptyHint({ text }) {
7052
+ return /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.25)", ta: "center", py: "xs", children: text });
7053
+ }
7054
+ function AccessOverrideSection({
7055
+ schemaKey = "access",
7056
+ title,
7057
+ description: description2,
7058
+ includeOffline = true
7059
+ }) {
7060
+ const mantineTheme = useMantineTheme();
7061
+ const color = mantineTheme.colors[mantineTheme.primaryColor][5];
7062
+ const raw = useFormField(schemaKey);
7063
+ const value = {
7064
+ groups: Array.isArray(raw?.groups) ? raw.groups : DEFAULT_VALUE.groups,
7065
+ identifiers: Array.isArray(raw?.identifiers) ? raw.identifiers : DEFAULT_VALUE.identifiers
7066
+ };
7067
+ const { setValue } = useFormActions();
7068
+ const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
7069
+ const [addingGroup, setAddingGroup] = useState(false);
7070
+ const [addingPlayer, setAddingPlayer] = useState(false);
7071
+ const commitGroup = (name) => {
7072
+ const g = name.trim();
7073
+ if (!g) return;
7074
+ if (!value.groups.includes(g)) set("groups", [...value.groups, g]);
7075
+ setAddingGroup(false);
7076
+ };
7077
+ const setGroup = (index, name) => {
7078
+ const next = [...value.groups];
7079
+ next[index] = name;
7080
+ set("groups", next);
7081
+ };
7082
+ const quickAddGroup = (name) => {
7083
+ if (value.groups.includes(name)) return;
7084
+ set("groups", [...value.groups, name]);
7085
+ };
7086
+ const removeGroup = (index) => set("groups", value.groups.filter((_, i) => i !== index));
7087
+ const commitIdentifier = (id) => {
7088
+ if (!id) return;
7089
+ set("identifiers", [...value.identifiers, id]);
7090
+ setAddingPlayer(false);
7091
+ };
7092
+ const setIdentifier = (index, id) => {
7093
+ const next = [...value.identifiers];
7094
+ next[index] = id;
7095
+ set("identifiers", next.filter((x) => x !== ""));
7096
+ };
7097
+ const removeIdentifier = (index) => set("identifiers", value.identifiers.filter((_, i) => i !== index));
7098
+ return /* @__PURE__ */ jsxs(
7099
+ Flex,
7100
+ {
7101
+ direction: "column",
7102
+ gap: "xs",
7103
+ p: "sm",
7104
+ style: { flex: 1, minHeight: 0, overflowY: "auto" },
7105
+ children: [
7106
+ /* @__PURE__ */ jsx(
7107
+ AdminPageTitle,
7108
+ {
7109
+ icon: ShieldCheck,
7110
+ title: title || t("Access", "Access"),
7111
+ color
7112
+ }
7113
+ ),
7114
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.4)", children: description2 || t(
7115
+ "AccessOverrideDesc",
7116
+ "Grant access to this resource's admin panel to specific ACE groups and players, in addition to your global dirk_lib admins. Enforcement is server-side."
7117
+ ) }),
7118
+ /* @__PURE__ */ jsx(SectionLabel, { icon: Users, label: t("AccessGroups", "Admin groups (ACE)") }),
7119
+ /* @__PURE__ */ jsx(Flex, { gap: "xs", wrap: "wrap", children: COMMON_ACE_GROUPS.map((g) => {
7120
+ const added = value.groups.includes(g);
7121
+ return /* @__PURE__ */ jsxs(
7122
+ Flex,
7123
+ {
7124
+ align: "center",
7125
+ gap: "xxs",
7126
+ onClick: added ? void 0 : () => quickAddGroup(g),
7127
+ role: "button",
7128
+ tabIndex: added ? -1 : 0,
7129
+ "aria-disabled": added,
7130
+ onKeyDown: (e) => {
7131
+ if (added) return;
7132
+ if (e.key === "Enter" || e.key === " ") {
7133
+ e.preventDefault();
7134
+ quickAddGroup(g);
7135
+ }
7136
+ },
7137
+ px: "xs",
7138
+ py: "xxs",
7139
+ style: {
7140
+ cursor: added ? "default" : "pointer",
7141
+ opacity: added ? 0.4 : 1,
7142
+ background: alpha(color, 0.12),
7143
+ border: `0.1vh solid ${alpha(color, 0.4)}`,
7144
+ borderRadius: "0.4vh",
7145
+ transition: "background 0.15s, border-color 0.15s"
7146
+ },
7147
+ onMouseEnter: (e) => {
7148
+ if (added) return;
7149
+ e.currentTarget.style.background = alpha(color, 0.22);
7150
+ },
7151
+ onMouseLeave: (e) => {
7152
+ if (added) return;
7153
+ e.currentTarget.style.background = alpha(color, 0.12);
7154
+ },
7155
+ children: [
7156
+ !added && /* @__PURE__ */ jsx(Plus, { size: "1.2vh", color }),
7157
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", lts: "0.04em", c: color, children: g })
7158
+ ]
7159
+ },
7160
+ g
7161
+ );
7162
+ }) }),
7163
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "xxs", children: [
7164
+ value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsx(
7165
+ EmptyHint,
7166
+ {
7167
+ text: t("AccessNoGroups", "No ACE groups added. e.g. group.admin")
7168
+ }
7169
+ ),
7170
+ value.groups.map((name, i) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7171
+ /* @__PURE__ */ jsx(
7172
+ TextInput,
7173
+ {
7174
+ value: name,
7175
+ onChange: (e) => setGroup(i, e.currentTarget.value),
7176
+ placeholder: t("AccessGroupPlaceholder", "ACE group or permission, e.g. group.admin"),
7177
+ size: "xs",
7178
+ style: { flex: 1 }
7179
+ }
7180
+ ),
7181
+ /* @__PURE__ */ jsx(
7182
+ ActionIcon,
7183
+ {
7184
+ variant: "subtle",
7185
+ color: "red",
7186
+ size: "md",
7187
+ onClick: () => removeGroup(i),
7188
+ title: t("Remove", "Remove"),
7189
+ "aria-label": t("Remove", "Remove"),
7190
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7191
+ }
7192
+ )
7193
+ ] }, i)),
7194
+ addingGroup && /* @__PURE__ */ jsx(
7195
+ AceGroupAddRow,
7196
+ {
7197
+ placeholder: t("AccessGroupPlaceholder", "ACE group or permission, e.g. group.admin"),
7198
+ onCommit: commitGroup,
7199
+ onCancel: () => setAddingGroup(false),
7200
+ removeLabel: t("Remove", "Remove")
7201
+ }
7202
+ )
7203
+ ] }),
7204
+ !addingGroup && /* @__PURE__ */ jsx(AddRowButton, { label: t("AccessAddGroup", "Add group"), onClick: () => setAddingGroup(true) }),
7205
+ /* @__PURE__ */ jsx(SectionLabel, { icon: UserRound, label: t("AccessPlayers", "Players") }),
7206
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "xxs", children: [
7207
+ value.identifiers.length === 0 && !addingPlayer && /* @__PURE__ */ jsx(EmptyHint, { text: t("AccessNoPlayers", "No players added.") }),
7208
+ value.identifiers.map((id, i) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7209
+ /* @__PURE__ */ jsx(
7210
+ PlayerSelect,
7211
+ {
7212
+ value: id || null,
7213
+ onChange: (p) => setIdentifier(i, p?.citizenId ?? ""),
7214
+ includeOffline,
7215
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7216
+ style: { flex: 1 },
7217
+ size: "xs"
7218
+ }
7219
+ ),
7220
+ /* @__PURE__ */ jsx(
7221
+ ActionIcon,
7222
+ {
7223
+ variant: "subtle",
7224
+ color: "red",
7225
+ size: "md",
7226
+ onClick: () => removeIdentifier(i),
7227
+ title: t("Remove", "Remove"),
7228
+ "aria-label": t("Remove", "Remove"),
7229
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7230
+ }
7231
+ )
7232
+ ] }, i)),
7233
+ addingPlayer && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7234
+ /* @__PURE__ */ jsx(
7235
+ PlayerSelect,
7236
+ {
7237
+ value: null,
7238
+ onChange: (p) => commitIdentifier(p?.citizenId ?? ""),
7239
+ includeOffline,
7240
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7241
+ style: { flex: 1 },
7242
+ size: "xs"
7243
+ }
7244
+ ),
7245
+ /* @__PURE__ */ jsx(
7246
+ ActionIcon,
7247
+ {
7248
+ variant: "subtle",
7249
+ color: "red",
7250
+ size: "md",
7251
+ onClick: () => setAddingPlayer(false),
7252
+ title: t("Remove", "Remove"),
7253
+ "aria-label": t("Remove", "Remove"),
7254
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7255
+ }
7256
+ )
7257
+ ] })
7258
+ ] }),
7259
+ !addingPlayer && /* @__PURE__ */ jsx(AddRowButton, { label: t("AccessAddPlayer", "Add player"), onClick: () => setAddingPlayer(true) })
7260
+ ]
7261
+ }
7262
+ );
7263
+ }
7264
+ function AddRowButton({ label: label2, onClick }) {
7265
+ return /* @__PURE__ */ jsxs(
7266
+ Flex,
7267
+ {
7268
+ align: "center",
7269
+ justify: "center",
7270
+ gap: "xs",
7271
+ onClick,
7272
+ role: "button",
7273
+ tabIndex: 0,
7274
+ onKeyDown: (e) => {
7275
+ if (e.key === "Enter" || e.key === " ") {
7276
+ e.preventDefault();
7277
+ onClick();
7278
+ }
7279
+ },
7280
+ py: "xs",
7281
+ style: {
7282
+ cursor: "pointer",
7283
+ border: "0.1vh dashed rgba(255,255,255,0.12)",
7284
+ borderRadius: "0.4vh",
7285
+ transition: "background 0.15s, border-color 0.15s"
7286
+ },
7287
+ onMouseEnter: (e) => {
7288
+ e.currentTarget.style.background = "rgba(255,255,255,0.03)";
7289
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.2)";
7290
+ },
7291
+ onMouseLeave: (e) => {
7292
+ e.currentTarget.style.background = "transparent";
7293
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.12)";
7294
+ },
7295
+ children: [
7296
+ /* @__PURE__ */ jsx(Plus, { size: "1.4vh", color: "rgba(255,255,255,0.4)" }),
7297
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "rgba(255,255,255,0.4)", children: label2 })
7298
+ ]
7299
+ }
7300
+ );
7301
+ }
7302
+ function AceGroupAddRow({
7303
+ placeholder,
7304
+ onCommit,
7305
+ onCancel,
7306
+ removeLabel
7307
+ }) {
7308
+ const [draft, setDraft] = useState("");
7309
+ const commit = () => {
7310
+ if (draft.trim()) onCommit(draft);
7311
+ else onCancel();
7312
+ };
7313
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7314
+ /* @__PURE__ */ jsx(
7315
+ TextInput,
7316
+ {
7317
+ value: draft,
7318
+ onChange: (e) => setDraft(e.currentTarget.value),
7319
+ onKeyDown: (e) => {
7320
+ if (e.key === "Enter") {
7321
+ e.preventDefault();
7322
+ commit();
7323
+ } else if (e.key === "Escape") {
7324
+ e.preventDefault();
7325
+ onCancel();
7326
+ }
7327
+ },
7328
+ onBlur: commit,
7329
+ placeholder,
7330
+ size: "xs",
7331
+ autoFocus: true,
7332
+ style: { flex: 1 }
7333
+ }
7334
+ ),
7335
+ /* @__PURE__ */ jsx(
7336
+ ActionIcon,
7337
+ {
7338
+ variant: "subtle",
7339
+ color: "red",
7340
+ size: "md",
7341
+ onClick: onCancel,
7342
+ title: removeLabel,
7343
+ "aria-label": removeLabel,
7344
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7345
+ }
7346
+ )
7347
+ ] });
7348
+ }
7349
+
7350
+ // src/utils/gtaAnimPostFx.ts
7351
+ var GTA_ANIM_POST_FX_GROUP_ORDER = [
7352
+ "Wake Up / Sleep",
7353
+ "Character Switch",
7354
+ "Cinematic Push-In",
7355
+ "Death / Fail",
7356
+ "Heist",
7357
+ "Minigame",
7358
+ "Drug / Trip",
7359
+ "Color Filter / Tint",
7360
+ "Multiplayer",
7361
+ "Misc"
7362
+ ];
7363
+ var GTA_ANIM_POST_FX = [
7364
+ // ── Wake Up / Sleep (community-known; not in altV docs) ────────────────────
7365
+ { name: "ULP_PlayerWakeUp", label: "Player Wake Up", group: "Wake Up / Sleep" },
7366
+ { name: "MinigameEndNeutral", label: "Minigame End \u2014 Neutral", group: "Wake Up / Sleep" },
7367
+ { name: "MinigameEndFranklin", label: "Minigame End \u2014 Franklin", group: "Wake Up / Sleep" },
7368
+ { name: "MinigameEndMichael", label: "Minigame End \u2014 Michael", group: "Wake Up / Sleep" },
7369
+ { name: "MinigameEndTrevor", label: "Minigame End \u2014 Trevor", group: "Wake Up / Sleep" },
7370
+ // ── Character Switch (Franklin / Michael / Trevor / Neutral variants) ─────
7371
+ { name: "SwitchHUDIn", group: "Character Switch" },
7372
+ { name: "SwitchHUDOut", group: "Character Switch" },
7373
+ { name: "SwitchHUDFranklinIn", group: "Character Switch" },
7374
+ { name: "SwitchHUDFranklinOut", group: "Character Switch" },
7375
+ { name: "SwitchHUDMichaelIn", group: "Character Switch" },
7376
+ { name: "SwitchHUDMichaelOut", group: "Character Switch" },
7377
+ { name: "SwitchHUDTrevorIn", group: "Character Switch" },
7378
+ { name: "SwitchHUDTrevorOut", group: "Character Switch" },
7379
+ { name: "SwitchOpenFranklin", group: "Character Switch" },
7380
+ { name: "SwitchOpenFranklinIn", group: "Character Switch" },
7381
+ { name: "SwitchOpenFranklinOut", group: "Character Switch" },
7382
+ { name: "SwitchOpenMichaelIn", group: "Character Switch" },
7383
+ { name: "SwitchOpenMichaelMid", group: "Character Switch" },
7384
+ { name: "SwitchOpenMichaelOut", group: "Character Switch" },
7385
+ { name: "SwitchOpenTrevorIn", group: "Character Switch" },
7386
+ { name: "SwitchOpenTrevorOut", group: "Character Switch" },
7387
+ { name: "SwitchOpenNeutralFIB5", group: "Character Switch" },
7388
+ { name: "SwitchOpenNeutralOutHeist", group: "Character Switch" },
7389
+ { name: "SwitchSceneFranklin", group: "Character Switch" },
7390
+ { name: "SwitchSceneMichael", group: "Character Switch" },
7391
+ { name: "SwitchSceneNeutral", group: "Character Switch" },
7392
+ { name: "SwitchSceneTrevor", group: "Character Switch" },
7393
+ { name: "SwitchShortFranklinIn", group: "Character Switch" },
7394
+ { name: "SwitchShortFranklinMid", group: "Character Switch" },
7395
+ { name: "SwitchShortMichaelIn", group: "Character Switch" },
7396
+ { name: "SwitchShortMichaelMid", group: "Character Switch" },
7397
+ { name: "SwitchShortNeutralIn", group: "Character Switch" },
7398
+ { name: "SwitchShortNeutralMid", group: "Character Switch" },
7399
+ { name: "SwitchShortTrevorIn", group: "Character Switch" },
7400
+ { name: "SwitchShortTrevorMid", group: "Character Switch" },
7401
+ { name: "switch_cam_1", group: "Character Switch" },
7402
+ { name: "switch_cam_2", group: "Character Switch" },
7403
+ // ── Cinematic Push-In (slow zoom intro to a character) ────────────────────
7404
+ { name: "CamPushInFranklin", group: "Cinematic Push-In" },
7405
+ { name: "CamPushInMichael", group: "Cinematic Push-In" },
7406
+ { name: "CamPushInTrevor", group: "Cinematic Push-In" },
7407
+ { name: "CamPushInNeutral", group: "Cinematic Push-In" },
7408
+ { name: "BeastIntroScene", label: "Beast Intro", group: "Cinematic Push-In" },
7409
+ { name: "BeastLaunch", group: "Cinematic Push-In" },
7410
+ { name: "BeastTransition", group: "Cinematic Push-In" },
7411
+ // ── Death / Fail (red tint, slow-mo blur) ─────────────────────────────────
7412
+ { name: "DeathFailNeutralIn", group: "Death / Fail" },
7413
+ { name: "DeathFailFranklinIn", group: "Death / Fail" },
7414
+ { name: "DeathFailMichaelIn", group: "Death / Fail" },
7415
+ { name: "DeathFailTrevorIn", group: "Death / Fail" },
7416
+ { name: "DeathFailMPIn", label: "Death Fail \u2014 MP", group: "Death / Fail" },
7417
+ { name: "DeathFailMPDark", label: "Death Fail \u2014 MP Dark", group: "Death / Fail" },
7418
+ { name: "DeathFailOut", group: "Death / Fail" },
7419
+ { name: "Rampage", group: "Death / Fail" },
7420
+ { name: "RampageOut", group: "Death / Fail" },
7421
+ // ── Heist (celebration / locate / pass) ───────────────────────────────────
7422
+ { name: "HeistCelebEnd", group: "Heist" },
7423
+ { name: "HeistCelebPass", group: "Heist" },
7424
+ { name: "HeistCelebPassBW", label: "Heist Celeb Pass (B/W)", group: "Heist" },
7425
+ { name: "HeistCelebToast", group: "Heist" },
7426
+ { name: "HeistLocate", group: "Heist" },
7427
+ { name: "HeistTripSkipFade", group: "Heist" },
7428
+ // ── Minigame / Menu ───────────────────────────────────────────────────────
7429
+ { name: "MenuMGIn", group: "Minigame" },
7430
+ { name: "MenuMGHeistIn", group: "Minigame" },
7431
+ { name: "MenuMGHeistIntro", group: "Minigame" },
7432
+ { name: "MenuMGHeistOut", group: "Minigame" },
7433
+ { name: "MenuMGHeistTint", group: "Minigame" },
7434
+ { name: "MenuMGSelectionIn", group: "Minigame" },
7435
+ { name: "MenuMGSelectionTint", group: "Minigame" },
7436
+ { name: "MenuMGTournamentIn", group: "Minigame" },
7437
+ { name: "MenuMGTournamentTint", group: "Minigame" },
7438
+ { name: "MinigameTransitionIn", group: "Minigame" },
7439
+ { name: "MinigameTransitionOut", group: "Minigame" },
7440
+ // ── Drug / Trip (psychedelic, blur, distortion) ───────────────────────────
7441
+ { name: "DMT_flight", group: "Drug / Trip" },
7442
+ { name: "DMT_flight_intro", group: "Drug / Trip" },
7443
+ { name: "PeyoteIn", group: "Drug / Trip" },
7444
+ { name: "PeyoteOut", group: "Drug / Trip" },
7445
+ { name: "PeyoteEndIn", group: "Drug / Trip" },
7446
+ { name: "PeyoteEndOut", group: "Drug / Trip" },
7447
+ { name: "DrugsDrivingIn", group: "Drug / Trip" },
7448
+ { name: "DrugsDrivingOut", group: "Drug / Trip" },
7449
+ { name: "DrugsMichaelAliensFight", group: "Drug / Trip" },
7450
+ { name: "DrugsMichaelAliensFightIn", group: "Drug / Trip" },
7451
+ { name: "DrugsMichaelAliensFightOut", group: "Drug / Trip" },
7452
+ { name: "DrugsTrevorClownsFight", group: "Drug / Trip" },
7453
+ { name: "DrugsTrevorClownsFightIn", group: "Drug / Trip" },
7454
+ { name: "DrugsTrevorClownsFightOut", group: "Drug / Trip" },
7455
+ { name: "ChopVision", label: "Chop Vision (dog)", group: "Drug / Trip" },
7456
+ // ── Color Filter / Tint (one-off and biker formation filters) ─────────────
7457
+ { name: "PPFilter", group: "Color Filter / Tint" },
7458
+ { name: "PPFilterOut", group: "Color Filter / Tint" },
7459
+ { name: "PPGreen", group: "Color Filter / Tint" },
7460
+ { name: "PPGreenOut", group: "Color Filter / Tint" },
7461
+ { name: "PPOrange", group: "Color Filter / Tint" },
7462
+ { name: "PPOrangeOut", group: "Color Filter / Tint" },
7463
+ { name: "PPPink", group: "Color Filter / Tint" },
7464
+ { name: "PPPinkOut", group: "Color Filter / Tint" },
7465
+ { name: "PPPurple", group: "Color Filter / Tint" },
7466
+ { name: "PPPurpleOut", group: "Color Filter / Tint" },
7467
+ { name: "BikerFilter", group: "Color Filter / Tint" },
7468
+ { name: "BikerFilterOut", group: "Color Filter / Tint" },
7469
+ { name: "BikerFormation", group: "Color Filter / Tint" },
7470
+ { name: "BikerFormationOut", group: "Color Filter / Tint" },
7471
+ { name: "InchOrange", group: "Color Filter / Tint" },
7472
+ { name: "InchOrangeOut", group: "Color Filter / Tint" },
7473
+ { name: "InchPurple", group: "Color Filter / Tint" },
7474
+ { name: "InchPurpleOut", group: "Color Filter / Tint" },
7475
+ { name: "InchPickup", group: "Color Filter / Tint" },
7476
+ { name: "InchPickupOut", group: "Color Filter / Tint" },
7477
+ { name: "TinyRacerGreen", group: "Color Filter / Tint" },
7478
+ { name: "TinyRacerGreenOut", group: "Color Filter / Tint" },
7479
+ { name: "TinyRacerPink", group: "Color Filter / Tint" },
7480
+ { name: "TinyRacerPinkOut", group: "Color Filter / Tint" },
7481
+ { name: "TinyRacerIntroCam", group: "Color Filter / Tint" },
7482
+ { name: "DeadlineNeon", group: "Color Filter / Tint" },
7483
+ // ── Multiplayer (celeb, killstreak, race, etc.) ───────────────────────────
7484
+ { name: "MP_Celeb_Win", group: "Multiplayer" },
7485
+ { name: "MP_Celeb_Win_Out", group: "Multiplayer" },
7486
+ { name: "MP_Celeb_Lose", group: "Multiplayer" },
7487
+ { name: "MP_Celeb_Lose_Out", group: "Multiplayer" },
7488
+ { name: "MP_Celeb_Preload", group: "Multiplayer" },
7489
+ { name: "MP_Celeb_Preload_Fade", group: "Multiplayer" },
7490
+ { name: "MP_Bull_tost", group: "Multiplayer" },
7491
+ { name: "MP_Bull_tost_Out", group: "Multiplayer" },
7492
+ { name: "MP_Killstreak", group: "Multiplayer" },
7493
+ { name: "MP_Killstreak_Out", group: "Multiplayer" },
7494
+ { name: "MP_Loser_Streak_Out", group: "Multiplayer" },
7495
+ { name: "MP_OrbitalCannon", group: "Multiplayer" },
7496
+ { name: "MP_Powerplay", group: "Multiplayer" },
7497
+ { name: "MP_Powerplay_Out", group: "Multiplayer" },
7498
+ { name: "MP_SmugglerCheckpoint", group: "Multiplayer" },
7499
+ { name: "MP_TransformRaceFlash", group: "Multiplayer" },
7500
+ { name: "MP_WarpCheckpoint", group: "Multiplayer" },
7501
+ { name: "MP_corona_switch", group: "Multiplayer" },
7502
+ { name: "MP_intro_logo", group: "Multiplayer" },
7503
+ { name: "MP_job_load", group: "Multiplayer" },
7504
+ { name: "MP_race_crash", group: "Multiplayer" },
7505
+ // ── Misc (success notifs, special weapons, sniper / pause / explosions) ───
7506
+ { name: "SuccessFranklin", group: "Misc" },
7507
+ { name: "SuccessMichael", group: "Misc" },
7508
+ { name: "SuccessNeutral", group: "Misc" },
7509
+ { name: "SuccessTrevor", group: "Misc" },
7510
+ { name: "FocusIn", group: "Misc" },
7511
+ { name: "FocusOut", group: "Misc" },
7512
+ { name: "CrossLine", group: "Misc" },
7513
+ { name: "CrossLineOut", group: "Misc" },
7514
+ { name: "SniperOverlay", group: "Misc" },
7515
+ { name: "ExplosionJosh3", group: "Misc" },
7516
+ { name: "WeaponUpgrade", group: "Misc" },
7517
+ { name: "Dont_tazeme_bro", label: "Don't Taze Me Bro", group: "Misc" },
7518
+ { name: "LostTimeDay", group: "Misc" },
7519
+ { name: "LostTimeNight", group: "Misc" },
7520
+ { name: "PauseMenuOut", group: "Misc" },
7521
+ { name: "pennedIn", group: "Misc" },
7522
+ { name: "PennedInOut", group: "Misc" },
7523
+ { name: "RaceTurbo", group: "Misc" },
7524
+ { name: "DefaultFlash", group: "Misc" }
7525
+ ];
7526
+ function formatGtaAnimPostFx(name) {
7527
+ const entry = GTA_ANIM_POST_FX.find((fx) => fx.name === name);
7528
+ return entry?.label ?? name;
7529
+ }
7530
+ function buildGroupedData2(extraNames = []) {
7531
+ const groups = /* @__PURE__ */ new Map();
7532
+ const seen = /* @__PURE__ */ new Set();
7533
+ for (const fx of GTA_ANIM_POST_FX) {
7534
+ if (seen.has(fx.name)) continue;
7535
+ seen.add(fx.name);
7536
+ if (!groups.has(fx.group)) groups.set(fx.group, []);
7537
+ groups.get(fx.group).push(fx);
7538
+ }
7539
+ const data = GTA_ANIM_POST_FX_GROUP_ORDER.flatMap((g) => {
7540
+ const items = groups.get(g);
7541
+ if (!items || items.length === 0) return [];
7542
+ return [
7543
+ {
7544
+ group: g,
7545
+ items: items.map((fx) => ({
7546
+ value: fx.name,
7547
+ label: formatGtaAnimPostFx(fx.name)
7548
+ }))
7549
+ }
7550
+ ];
7551
+ });
7552
+ const knownNames = new Set(GTA_ANIM_POST_FX.map((fx) => fx.name));
7553
+ const customs = extraNames.filter((n) => n && !knownNames.has(n));
7554
+ if (customs.length > 0) {
7055
7555
  data.push({
7056
7556
  group: "Custom",
7057
7557
  items: customs.map((n) => ({ value: n, label: n }))
@@ -7435,7 +7935,7 @@ function ScenarioSelect({
7435
7935
  }
7436
7936
  );
7437
7937
  }
7438
- var t = (key, fallback) => {
7938
+ var t2 = (key, fallback) => {
7439
7939
  const v = locale(key);
7440
7940
  return v === key ? fallback : v;
7441
7941
  };
@@ -7503,7 +8003,7 @@ function DiscordRoleSelect(props) {
7503
8003
  return map;
7504
8004
  }, [roles]);
7505
8005
  if (errorCode === "NotConfigured") {
7506
- const notConfiguredPlaceholder = t(
8006
+ const notConfiguredPlaceholder = t2(
7507
8007
  "DiscordNotConfigured",
7508
8008
  "Not configured \u2014 set in /dirk_lib"
7509
8009
  );
@@ -7571,9 +8071,9 @@ function DiscordRoleSelect(props) {
7571
8071
  e.stopPropagation();
7572
8072
  refresh();
7573
8073
  },
7574
- title: t("Refresh", "Refresh"),
8074
+ title: t2("Refresh", "Refresh"),
7575
8075
  loading,
7576
- "aria-label": t("Refresh", "Refresh"),
8076
+ "aria-label": t2("Refresh", "Refresh"),
7577
8077
  children: /* @__PURE__ */ jsx(RefreshCw, { size: "1.2vh" })
7578
8078
  }
7579
8079
  );
@@ -7583,7 +8083,7 @@ function DiscordRoleSelect(props) {
7583
8083
  {
7584
8084
  label: label2,
7585
8085
  size,
7586
- placeholder: placeholder || t("PickRoles", "Pick roles..."),
8086
+ placeholder: placeholder || t2("PickRoles", "Pick roles..."),
7587
8087
  data,
7588
8088
  value: props.value || [],
7589
8089
  onChange: (ids) => props.onChange(ids),
@@ -7595,7 +8095,7 @@ function DiscordRoleSelect(props) {
7595
8095
  rightSectionPointerEvents: "all",
7596
8096
  style,
7597
8097
  styles,
7598
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
8098
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7599
8099
  }
7600
8100
  );
7601
8101
  }
@@ -7604,7 +8104,7 @@ function DiscordRoleSelect(props) {
7604
8104
  {
7605
8105
  label: label2,
7606
8106
  size,
7607
- placeholder: placeholder || t("PickRole", "Pick a role..."),
8107
+ placeholder: placeholder || t2("PickRole", "Pick a role..."),
7608
8108
  data,
7609
8109
  value: props.value || null,
7610
8110
  onChange: (id) => props.onChange(id),
@@ -7616,11 +8116,11 @@ function DiscordRoleSelect(props) {
7616
8116
  rightSectionPointerEvents: "all",
7617
8117
  style,
7618
8118
  styles,
7619
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
8119
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7620
8120
  }
7621
8121
  );
7622
8122
  }
7623
- var t2 = (key, fallback) => {
8123
+ var t3 = (key, fallback) => {
7624
8124
  const v = locale(key);
7625
8125
  return v === key ? fallback : v;
7626
8126
  };
@@ -7641,7 +8141,7 @@ function FrameworkHint({ framework }) {
7641
8141
  const theme2 = useMantineTheme();
7642
8142
  if (!framework) return null;
7643
8143
  const hintKey = `AccountFrameworkHint_${framework}`;
7644
- const text = t2(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
8144
+ const text = t3(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
7645
8145
  return /* @__PURE__ */ jsxs(
7646
8146
  Flex,
7647
8147
  {
@@ -7725,9 +8225,9 @@ function AccountSelect(props) {
7725
8225
  e.stopPropagation();
7726
8226
  refresh();
7727
8227
  },
7728
- title: t2("Refresh", "Refresh"),
8228
+ title: t3("Refresh", "Refresh"),
7729
8229
  loading,
7730
- "aria-label": t2("Refresh", "Refresh"),
8230
+ "aria-label": t3("Refresh", "Refresh"),
7731
8231
  children: /* @__PURE__ */ jsx(RefreshCw, { size: "1.2vh" })
7732
8232
  }
7733
8233
  );
@@ -7741,14 +8241,14 @@ function AccountSelect(props) {
7741
8241
  rightSection: refreshButton,
7742
8242
  rightSectionPointerEvents: "all",
7743
8243
  style,
7744
- nothingFoundMessage: t2("NoAccounts", "No accounts available")
8244
+ nothingFoundMessage: t3("NoAccounts", "No accounts available")
7745
8245
  };
7746
8246
  return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "0.3vh", style, children: [
7747
8247
  props.multi ? /* @__PURE__ */ jsx(
7748
8248
  MultiSelect,
7749
8249
  {
7750
8250
  ...sharedSelectProps,
7751
- placeholder: placeholder || t2("PickAccounts", "Pick accounts (or type custom)..."),
8251
+ placeholder: placeholder || t3("PickAccounts", "Pick accounts (or type custom)..."),
7752
8252
  value: props.value || [],
7753
8253
  onChange: (names) => props.onChange(names),
7754
8254
  renderOption,
@@ -7760,7 +8260,7 @@ function AccountSelect(props) {
7760
8260
  Select,
7761
8261
  {
7762
8262
  ...sharedSelectProps,
7763
- placeholder: placeholder || t2("PickAccount", "Pick an account..."),
8263
+ placeholder: placeholder || t3("PickAccount", "Pick an account..."),
7764
8264
  value: props.value || null,
7765
8265
  onChange: (name) => props.onChange(name),
7766
8266
  renderOption,
@@ -8052,175 +8552,6 @@ function WorldPositionGotoButton2({
8052
8552
  }
8053
8553
  ) });
8054
8554
  }
8055
- function usePlayers(opts = {}) {
8056
- const {
8057
- includeOffline = false,
8058
- search = "",
8059
- limit = 50,
8060
- staleTimeMs,
8061
- refetchIntervalMs
8062
- } = opts;
8063
- const query = useQuery({
8064
- queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
8065
- queryFn: async () => {
8066
- const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
8067
- const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
8068
- const result = await fetchNui(
8069
- "ADMIN_TOOL_QUERY",
8070
- payload,
8071
- // Browser-dev fallback. Returns a couple of mock players so the
8072
- // dev shell isn't blank.
8073
- includeOffline ? [
8074
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
8075
- { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
8076
- ] : [
8077
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
8078
- ]
8079
- );
8080
- return Array.isArray(result) ? result : [];
8081
- },
8082
- staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
8083
- gcTime: 5 * 6e4,
8084
- refetchInterval: refetchIntervalMs ?? false,
8085
- refetchOnWindowFocus: false,
8086
- placeholderData: includeOffline ? keepPreviousData : void 0
8087
- });
8088
- return {
8089
- players: query.data ?? [],
8090
- isLoading: query.isLoading,
8091
- isFetching: query.isFetching,
8092
- error: query.error ?? null,
8093
- refresh: () => query.refetch()
8094
- };
8095
- }
8096
- var DEBOUNCE_MS = 300;
8097
- var ONLINE_COLOR = "#3FA83F";
8098
- var OFFLINE_COLOR = "#E54141";
8099
- function PlayerSelect({
8100
- value,
8101
- onChange,
8102
- includeOffline = false,
8103
- limit = 50,
8104
- searchable: searchableProp,
8105
- placeholder,
8106
- nothingFoundMessage: nothingFoundMessageProp,
8107
- loadingLabel = "Loading\u2026",
8108
- onlineLabel = "Online",
8109
- offlineLabel = "Offline",
8110
- refreshLabel = "Refresh player list",
8111
- ...rest
8112
- }) {
8113
- const theme2 = useMantineTheme();
8114
- const color = theme2.colors[theme2.primaryColor][5];
8115
- const [searchInput, setSearchInput] = useState("");
8116
- const [debouncedSearch, setDebouncedSearch] = useState("");
8117
- const { players, isFetching, refresh } = usePlayers({
8118
- includeOffline,
8119
- search: includeOffline ? debouncedSearch : void 0,
8120
- limit
8121
- });
8122
- const sortedPlayers = useMemo(
8123
- () => [...players].sort((a, b) => {
8124
- if (a.online !== b.online) return a.online ? -1 : 1;
8125
- return a.charName.localeCompare(b.charName);
8126
- }),
8127
- [players]
8128
- );
8129
- const playerByCitizen = useMemo(() => {
8130
- const m = /* @__PURE__ */ new Map();
8131
- for (const p of sortedPlayers) m.set(p.citizenId, p);
8132
- return m;
8133
- }, [sortedPlayers]);
8134
- const data = useMemo(() => {
8135
- const items = sortedPlayers.map((p) => ({
8136
- value: p.citizenId,
8137
- label: formatLabel(p)
8138
- }));
8139
- if (value && !items.some((i) => i.value === value)) {
8140
- items.unshift({ value, label: value });
8141
- }
8142
- return items;
8143
- }, [sortedPlayers, value]);
8144
- const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
8145
- const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
8146
- useEffect(() => {
8147
- if (!includeOffline) return;
8148
- if (selectedLabel && searchInput === selectedLabel) return;
8149
- const t3 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
8150
- return () => clearTimeout(t3);
8151
- }, [searchInput, includeOffline, selectedLabel]);
8152
- const renderOption = ({ option }) => {
8153
- const p = playerByCitizen.get(option.value);
8154
- if (!p) return option.label;
8155
- return /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
8156
- /* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
8157
- /* @__PURE__ */ jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
8158
- ] });
8159
- };
8160
- return /* @__PURE__ */ jsx(
8161
- Select,
8162
- {
8163
- ...rest,
8164
- data,
8165
- value: value ?? null,
8166
- onChange: (v) => {
8167
- if (!v) return onChange(null);
8168
- const player = playerByCitizen.get(v) ?? null;
8169
- onChange(player);
8170
- },
8171
- searchable: searchableProp ?? true,
8172
- searchValue: includeOffline ? searchInput : void 0,
8173
- onSearchChange: includeOffline ? setSearchInput : void 0,
8174
- placeholder,
8175
- nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
8176
- maxDropdownHeight: 300,
8177
- renderOption,
8178
- leftSection: selectedPlayer ? /* @__PURE__ */ jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
8179
- rightSectionWidth: "3.5vh",
8180
- rightSectionPointerEvents: "all",
8181
- rightSection: /* @__PURE__ */ jsx(
8182
- ActionIcon,
8183
- {
8184
- variant: "subtle",
8185
- size: "sm",
8186
- onClick: (e) => {
8187
- e.stopPropagation();
8188
- refresh();
8189
- },
8190
- "aria-label": refreshLabel,
8191
- title: refreshLabel,
8192
- style: { marginRight: "0.6vh" },
8193
- children: isFetching ? /* @__PURE__ */ jsx(Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsx(RefreshCw, { size: "1.4vh", color })
8194
- }
8195
- )
8196
- }
8197
- );
8198
- }
8199
- function StatusDot({ online, onlineLabel, offlineLabel }) {
8200
- return /* @__PURE__ */ jsx(
8201
- "span",
8202
- {
8203
- title: online ? onlineLabel : offlineLabel,
8204
- style: {
8205
- display: "inline-block",
8206
- width: "0.9vh",
8207
- height: "0.9vh",
8208
- borderRadius: "50%",
8209
- backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
8210
- boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
8211
- flexShrink: 0
8212
- }
8213
- }
8214
- );
8215
- }
8216
- function formatLabel(p) {
8217
- const parts = [p.charName || p.citizenId];
8218
- if (p.online) {
8219
- if (p.name) parts.push(p.name);
8220
- if (p.id != null) parts.push(`#${p.id}`);
8221
- }
8222
- return parts.join(" \xB7 ");
8223
- }
8224
8555
  function usePickDoor() {
8225
8556
  const begin = useAdminToolStore((s) => s.begin);
8226
8557
  return async () => {
@@ -8612,6 +8943,6 @@ var Vector4Schema = z.object({
8612
8943
  w: z.number()
8613
8944
  });
8614
8945
 
8615
- 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 };
8946
+ 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 };
8616
8947
  //# sourceMappingURL=index.js.map
8617
8948
  //# sourceMappingURL=index.js.map