dirk-cfx-react 1.1.90 → 1.1.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,10 +2,11 @@ import { Flex, Text, Image as Image$1, createTheme, alpha, Box, Stack, Title as
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import React4, { createContext, memo, useRef, useEffect, useContext, useState, useCallback, useMemo, useLayoutEffect } from 'react';
4
4
  import { create, useStore, createStore } from 'zustand';
5
+ import { z } from 'zod';
5
6
  import axios from 'axios';
6
7
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7
8
  import { motion, AnimatePresence, useMotionValue } from 'framer-motion';
8
- import { Info, X, AlertTriangle, Trash2, RefreshCw, ChevronDown, Check, Copy, MapPin, Crosshair, EyeOff, Eye, RotateCcw, FlaskConical, ChevronUp, Palette, DoorOpen, Plus, Minus, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
9
+ import { Info, X, AlertTriangle, Trash2, RefreshCw, ChevronDown, Check, Copy, MapPin, Crosshair, EyeOff, Eye, RotateCcw, FlaskConical, ChevronUp, Palette, ShieldCheck, Users, UserRound, Plus, DoorOpen, Minus, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
9
10
  import clickSoundUrl from './click_sound-PNCRRTM4.mp3';
10
11
  import hoverSoundUrl from './hover_sound-NBUA222C.mp3';
11
12
  import { notifications } from '@mantine/notifications';
@@ -26,7 +27,6 @@ import { CRS, tileLayer, latLngBounds, latLng } from 'leaflet';
26
27
  import 'leaflet/dist/leaflet.css';
27
28
  import { MapContainer, useMap } from 'react-leaflet';
28
29
  import { Marker } from '@adamscybot/react-leaflet-component-marker';
29
- import { z } from 'zod';
30
30
 
31
31
  var __defProp = Object.defineProperty;
32
32
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -1312,6 +1312,32 @@ var openLink = (url) => {
1312
1312
  window.invokeNative("openLink", url);
1313
1313
  }
1314
1314
  };
1315
+ var DEFAULT_PALETTE = [
1316
+ "#f0f4ff",
1317
+ "#d9e3ff",
1318
+ "#bfcfff",
1319
+ "#a6bbff",
1320
+ "#8ca7ff",
1321
+ "#7393ff",
1322
+ "#5a7fff",
1323
+ "#406bff",
1324
+ "#2547ff",
1325
+ "#0b33ff"
1326
+ ];
1327
+ var ThemeOverrideSchema = z.object({
1328
+ useOverride: z.boolean(),
1329
+ primaryColor: z.string(),
1330
+ primaryShade: z.number(),
1331
+ customTheme: z.array(z.string())
1332
+ });
1333
+ var defaultThemeOverride = {
1334
+ useOverride: false,
1335
+ primaryColor: "dirk",
1336
+ primaryShade: 5,
1337
+ customTheme: DEFAULT_PALETTE
1338
+ };
1339
+
1340
+ // src/utils/useSettings.ts
1315
1341
  var useSettings = create(() => ({
1316
1342
  currency: "$",
1317
1343
  game: "fivem",
@@ -1319,18 +1345,7 @@ var useSettings = create(() => ({
1319
1345
  primaryShade: 9,
1320
1346
  itemImgPath: "",
1321
1347
  resourceVersion: "dev",
1322
- customTheme: [
1323
- "#f0f4ff",
1324
- "#d9e3ff",
1325
- "#bfcfff",
1326
- "#a6bbff",
1327
- "#8ca7ff",
1328
- "#7393ff",
1329
- "#5a7fff",
1330
- "#406bff",
1331
- "#2547ff",
1332
- "#0b33ff"
1333
- ]
1348
+ customTheme: DEFAULT_PALETTE
1334
1349
  }));
1335
1350
 
1336
1351
  // src/utils/fetchNui.ts
@@ -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);
@@ -6604,24 +6619,6 @@ var MANTINE_COLOR_OPTIONS = [
6604
6619
  "yellow",
6605
6620
  "orange"
6606
6621
  ].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
6622
  function GroupLabel({ label: label2 }) {
6626
6623
  return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", mt: "xxs", children: [
6627
6624
  /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
@@ -6636,10 +6633,10 @@ function ThemeOverrideSection({
6636
6633
  const color = mantineTheme.colors[mantineTheme.primaryColor][5];
6637
6634
  const raw = useFormField(schemaKey);
6638
6635
  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
6636
+ useOverride: raw?.useOverride ?? defaultThemeOverride.useOverride,
6637
+ primaryColor: raw?.primaryColor ?? defaultThemeOverride.primaryColor,
6638
+ primaryShade: raw?.primaryShade ?? defaultThemeOverride.primaryShade,
6639
+ customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : defaultThemeOverride.customTheme
6643
6640
  };
6644
6641
  const { setValue } = useFormActions();
6645
6642
  const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
@@ -6846,6 +6843,403 @@ function SwatchTile({
6846
6843
  ) })
6847
6844
  ] });
6848
6845
  }
6846
+ function usePlayers(opts = {}) {
6847
+ const {
6848
+ includeOffline = false,
6849
+ search = "",
6850
+ limit = 50,
6851
+ staleTimeMs,
6852
+ refetchIntervalMs
6853
+ } = opts;
6854
+ const query = useQuery({
6855
+ queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
6856
+ queryFn: async () => {
6857
+ const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
6858
+ const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
6859
+ const result = await fetchNui(
6860
+ "ADMIN_TOOL_QUERY",
6861
+ payload,
6862
+ // Browser-dev fallback. Returns a couple of mock players so the
6863
+ // dev shell isn't blank.
6864
+ includeOffline ? [
6865
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
6866
+ { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
6867
+ ] : [
6868
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
6869
+ ]
6870
+ );
6871
+ return Array.isArray(result) ? result : [];
6872
+ },
6873
+ staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
6874
+ gcTime: 5 * 6e4,
6875
+ refetchInterval: refetchIntervalMs ?? false,
6876
+ refetchOnWindowFocus: false,
6877
+ placeholderData: includeOffline ? keepPreviousData : void 0
6878
+ });
6879
+ return {
6880
+ players: query.data ?? [],
6881
+ isLoading: query.isLoading,
6882
+ isFetching: query.isFetching,
6883
+ error: query.error ?? null,
6884
+ refresh: () => query.refetch()
6885
+ };
6886
+ }
6887
+ var DEBOUNCE_MS = 300;
6888
+ var ONLINE_COLOR = "#3FA83F";
6889
+ var OFFLINE_COLOR = "#E54141";
6890
+ function PlayerSelect({
6891
+ value,
6892
+ onChange,
6893
+ includeOffline = false,
6894
+ limit = 50,
6895
+ searchable: searchableProp,
6896
+ placeholder,
6897
+ nothingFoundMessage: nothingFoundMessageProp,
6898
+ loadingLabel = "Loading\u2026",
6899
+ onlineLabel = "Online",
6900
+ offlineLabel = "Offline",
6901
+ refreshLabel = "Refresh player list",
6902
+ ...rest
6903
+ }) {
6904
+ const theme2 = useMantineTheme();
6905
+ const color = theme2.colors[theme2.primaryColor][5];
6906
+ const [searchInput, setSearchInput] = useState("");
6907
+ const [debouncedSearch, setDebouncedSearch] = useState("");
6908
+ const { players, isFetching, refresh } = usePlayers({
6909
+ includeOffline,
6910
+ search: includeOffline ? debouncedSearch : void 0,
6911
+ limit
6912
+ });
6913
+ const sortedPlayers = useMemo(
6914
+ () => [...players].sort((a, b) => {
6915
+ if (a.online !== b.online) return a.online ? -1 : 1;
6916
+ return a.charName.localeCompare(b.charName);
6917
+ }),
6918
+ [players]
6919
+ );
6920
+ const playerByCitizen = useMemo(() => {
6921
+ const m = /* @__PURE__ */ new Map();
6922
+ for (const p of sortedPlayers) m.set(p.citizenId, p);
6923
+ return m;
6924
+ }, [sortedPlayers]);
6925
+ const data = useMemo(() => {
6926
+ const items = sortedPlayers.map((p) => ({
6927
+ value: p.citizenId,
6928
+ label: formatLabel(p)
6929
+ }));
6930
+ if (value && !items.some((i) => i.value === value)) {
6931
+ items.unshift({ value, label: value });
6932
+ }
6933
+ return items;
6934
+ }, [sortedPlayers, value]);
6935
+ const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
6936
+ const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
6937
+ useEffect(() => {
6938
+ if (!includeOffline) return;
6939
+ if (selectedLabel && searchInput === selectedLabel) return;
6940
+ const t4 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
6941
+ return () => clearTimeout(t4);
6942
+ }, [searchInput, includeOffline, selectedLabel]);
6943
+ const renderOption = ({ option }) => {
6944
+ const p = playerByCitizen.get(option.value);
6945
+ if (!p) return option.label;
6946
+ return /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
6947
+ /* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
6948
+ /* @__PURE__ */ jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
6949
+ ] });
6950
+ };
6951
+ return /* @__PURE__ */ jsx(
6952
+ Select,
6953
+ {
6954
+ ...rest,
6955
+ data,
6956
+ value: value ?? null,
6957
+ onChange: (v) => {
6958
+ if (!v) return onChange(null);
6959
+ const player = playerByCitizen.get(v) ?? null;
6960
+ onChange(player);
6961
+ },
6962
+ searchable: searchableProp ?? true,
6963
+ searchValue: includeOffline ? searchInput : void 0,
6964
+ onSearchChange: includeOffline ? setSearchInput : void 0,
6965
+ placeholder,
6966
+ nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
6967
+ maxDropdownHeight: 300,
6968
+ renderOption,
6969
+ leftSection: selectedPlayer ? /* @__PURE__ */ jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
6970
+ rightSectionWidth: "3.5vh",
6971
+ rightSectionPointerEvents: "all",
6972
+ rightSection: /* @__PURE__ */ jsx(
6973
+ ActionIcon,
6974
+ {
6975
+ variant: "subtle",
6976
+ size: "sm",
6977
+ onClick: (e) => {
6978
+ e.stopPropagation();
6979
+ refresh();
6980
+ },
6981
+ "aria-label": refreshLabel,
6982
+ title: refreshLabel,
6983
+ style: { marginRight: "0.6vh" },
6984
+ children: isFetching ? /* @__PURE__ */ jsx(Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsx(RefreshCw, { size: "1.4vh", color })
6985
+ }
6986
+ )
6987
+ }
6988
+ );
6989
+ }
6990
+ function StatusDot({ online, onlineLabel, offlineLabel }) {
6991
+ return /* @__PURE__ */ jsx(
6992
+ "span",
6993
+ {
6994
+ title: online ? onlineLabel : offlineLabel,
6995
+ style: {
6996
+ display: "inline-block",
6997
+ width: "0.9vh",
6998
+ height: "0.9vh",
6999
+ borderRadius: "50%",
7000
+ backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
7001
+ boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
7002
+ flexShrink: 0
7003
+ }
7004
+ }
7005
+ );
7006
+ }
7007
+ function formatLabel(p) {
7008
+ const parts = [p.charName || p.citizenId];
7009
+ if (p.online) {
7010
+ if (p.name) parts.push(p.name);
7011
+ if (p.id != null) parts.push(`#${p.id}`);
7012
+ }
7013
+ return parts.join(" \xB7 ");
7014
+ }
7015
+ var t = (key, fallback) => {
7016
+ const v = locale(key);
7017
+ return v === key ? fallback : v;
7018
+ };
7019
+ var DEFAULT_VALUE = {
7020
+ groups: [],
7021
+ identifiers: []
7022
+ };
7023
+ function SectionLabel({ icon: Icon, label: label2 }) {
7024
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", mt: "xs", children: [
7025
+ /* @__PURE__ */ jsx(Icon, { size: "1.4vh", color: "rgba(255,255,255,0.3)" }),
7026
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
7027
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: "0.05vh", background: "rgba(255,255,255,0.06)" } })
7028
+ ] });
7029
+ }
7030
+ function EmptyHint({ text }) {
7031
+ return /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.25)", ta: "center", py: "xs", children: text });
7032
+ }
7033
+ function AccessOverrideSection({
7034
+ schemaKey = "access",
7035
+ title,
7036
+ description: description2,
7037
+ groupType,
7038
+ includeOffline = true
7039
+ }) {
7040
+ const mantineTheme = useMantineTheme();
7041
+ const color = mantineTheme.colors[mantineTheme.primaryColor][5];
7042
+ const raw = useFormField(schemaKey);
7043
+ const value = {
7044
+ groups: Array.isArray(raw?.groups) ? raw.groups : DEFAULT_VALUE.groups,
7045
+ identifiers: Array.isArray(raw?.identifiers) ? raw.identifiers : DEFAULT_VALUE.identifiers
7046
+ };
7047
+ const { setValue } = useFormActions();
7048
+ const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
7049
+ const [addingGroup, setAddingGroup] = useState(false);
7050
+ const [addingPlayer, setAddingPlayer] = useState(false);
7051
+ const commitGroup = (name) => {
7052
+ if (!name) return;
7053
+ set("groups", [...value.groups, name]);
7054
+ setAddingGroup(false);
7055
+ };
7056
+ const setGroup = (index, name) => {
7057
+ const next = [...value.groups];
7058
+ next[index] = name;
7059
+ set("groups", next.filter((g) => g !== ""));
7060
+ };
7061
+ const removeGroup = (index) => set("groups", value.groups.filter((_, i) => i !== index));
7062
+ const commitIdentifier = (id) => {
7063
+ if (!id) return;
7064
+ set("identifiers", [...value.identifiers, id]);
7065
+ setAddingPlayer(false);
7066
+ };
7067
+ const setIdentifier = (index, id) => {
7068
+ const next = [...value.identifiers];
7069
+ next[index] = id;
7070
+ set("identifiers", next.filter((x) => x !== ""));
7071
+ };
7072
+ const removeIdentifier = (index) => set("identifiers", value.identifiers.filter((_, i) => i !== index));
7073
+ return /* @__PURE__ */ jsxs(
7074
+ Flex,
7075
+ {
7076
+ direction: "column",
7077
+ gap: "xs",
7078
+ p: "sm",
7079
+ style: { flex: 1, minHeight: 0, overflowY: "auto" },
7080
+ children: [
7081
+ /* @__PURE__ */ jsx(
7082
+ AdminPageTitle,
7083
+ {
7084
+ icon: ShieldCheck,
7085
+ title: title || t("Access", "Access"),
7086
+ color
7087
+ }
7088
+ ),
7089
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.4)", children: description2 || t(
7090
+ "AccessOverrideDesc",
7091
+ "Grant access to this resource's admin panel to specific groups and players, in addition to your global dirk_lib admins. Enforcement is server-side."
7092
+ ) }),
7093
+ /* @__PURE__ */ jsx(SectionLabel, { icon: Users, label: t("AccessGroups", "Groups") }),
7094
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "xxs", children: [
7095
+ value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsx(EmptyHint, { text: t("AccessNoGroups", "No groups added.") }),
7096
+ value.groups.map((name, i) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7097
+ /* @__PURE__ */ jsx(
7098
+ GroupName,
7099
+ {
7100
+ value: name,
7101
+ onChange: (v) => setGroup(i, v),
7102
+ type: groupType,
7103
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7104
+ style: { flex: 1 }
7105
+ }
7106
+ ),
7107
+ /* @__PURE__ */ jsx(
7108
+ ActionIcon,
7109
+ {
7110
+ variant: "subtle",
7111
+ color: "red",
7112
+ size: "md",
7113
+ onClick: () => removeGroup(i),
7114
+ title: t("Remove", "Remove"),
7115
+ "aria-label": t("Remove", "Remove"),
7116
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7117
+ }
7118
+ )
7119
+ ] }, i)),
7120
+ addingGroup && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7121
+ /* @__PURE__ */ jsx(
7122
+ GroupName,
7123
+ {
7124
+ value: "",
7125
+ onChange: (v) => commitGroup(v),
7126
+ type: groupType,
7127
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7128
+ style: { flex: 1 }
7129
+ }
7130
+ ),
7131
+ /* @__PURE__ */ jsx(
7132
+ ActionIcon,
7133
+ {
7134
+ variant: "subtle",
7135
+ color: "red",
7136
+ size: "md",
7137
+ onClick: () => setAddingGroup(false),
7138
+ title: t("Remove", "Remove"),
7139
+ "aria-label": t("Remove", "Remove"),
7140
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7141
+ }
7142
+ )
7143
+ ] })
7144
+ ] }),
7145
+ !addingGroup && /* @__PURE__ */ jsx(AddRowButton, { label: t("AccessAddGroup", "Add group"), onClick: () => setAddingGroup(true) }),
7146
+ /* @__PURE__ */ jsx(SectionLabel, { icon: UserRound, label: t("AccessPlayers", "Players") }),
7147
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "xxs", children: [
7148
+ value.identifiers.length === 0 && !addingPlayer && /* @__PURE__ */ jsx(EmptyHint, { text: t("AccessNoPlayers", "No players added.") }),
7149
+ value.identifiers.map((id, i) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7150
+ /* @__PURE__ */ jsx(
7151
+ PlayerSelect,
7152
+ {
7153
+ value: id || null,
7154
+ onChange: (p) => setIdentifier(i, p?.citizenId ?? ""),
7155
+ includeOffline,
7156
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7157
+ style: { flex: 1 },
7158
+ size: "xs"
7159
+ }
7160
+ ),
7161
+ /* @__PURE__ */ jsx(
7162
+ ActionIcon,
7163
+ {
7164
+ variant: "subtle",
7165
+ color: "red",
7166
+ size: "md",
7167
+ onClick: () => removeIdentifier(i),
7168
+ title: t("Remove", "Remove"),
7169
+ "aria-label": t("Remove", "Remove"),
7170
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7171
+ }
7172
+ )
7173
+ ] }, i)),
7174
+ addingPlayer && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7175
+ /* @__PURE__ */ jsx(
7176
+ PlayerSelect,
7177
+ {
7178
+ value: null,
7179
+ onChange: (p) => commitIdentifier(p?.citizenId ?? ""),
7180
+ includeOffline,
7181
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7182
+ style: { flex: 1 },
7183
+ size: "xs"
7184
+ }
7185
+ ),
7186
+ /* @__PURE__ */ jsx(
7187
+ ActionIcon,
7188
+ {
7189
+ variant: "subtle",
7190
+ color: "red",
7191
+ size: "md",
7192
+ onClick: () => setAddingPlayer(false),
7193
+ title: t("Remove", "Remove"),
7194
+ "aria-label": t("Remove", "Remove"),
7195
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7196
+ }
7197
+ )
7198
+ ] })
7199
+ ] }),
7200
+ !addingPlayer && /* @__PURE__ */ jsx(AddRowButton, { label: t("AccessAddPlayer", "Add player"), onClick: () => setAddingPlayer(true) })
7201
+ ]
7202
+ }
7203
+ );
7204
+ }
7205
+ function AddRowButton({ label: label2, onClick }) {
7206
+ return /* @__PURE__ */ jsxs(
7207
+ Flex,
7208
+ {
7209
+ align: "center",
7210
+ justify: "center",
7211
+ gap: "xs",
7212
+ onClick,
7213
+ role: "button",
7214
+ tabIndex: 0,
7215
+ onKeyDown: (e) => {
7216
+ if (e.key === "Enter" || e.key === " ") {
7217
+ e.preventDefault();
7218
+ onClick();
7219
+ }
7220
+ },
7221
+ py: "xs",
7222
+ style: {
7223
+ cursor: "pointer",
7224
+ border: "0.1vh dashed rgba(255,255,255,0.12)",
7225
+ borderRadius: "0.4vh",
7226
+ transition: "background 0.15s, border-color 0.15s"
7227
+ },
7228
+ onMouseEnter: (e) => {
7229
+ e.currentTarget.style.background = "rgba(255,255,255,0.03)";
7230
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.2)";
7231
+ },
7232
+ onMouseLeave: (e) => {
7233
+ e.currentTarget.style.background = "transparent";
7234
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.12)";
7235
+ },
7236
+ children: [
7237
+ /* @__PURE__ */ jsx(Plus, { size: "1.4vh", color: "rgba(255,255,255,0.4)" }),
7238
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "rgba(255,255,255,0.4)", children: label2 })
7239
+ ]
7240
+ }
7241
+ );
7242
+ }
6849
7243
 
6850
7244
  // src/utils/gtaAnimPostFx.ts
6851
7245
  var GTA_ANIM_POST_FX_GROUP_ORDER = [
@@ -7435,7 +7829,7 @@ function ScenarioSelect({
7435
7829
  }
7436
7830
  );
7437
7831
  }
7438
- var t = (key, fallback) => {
7832
+ var t2 = (key, fallback) => {
7439
7833
  const v = locale(key);
7440
7834
  return v === key ? fallback : v;
7441
7835
  };
@@ -7503,7 +7897,7 @@ function DiscordRoleSelect(props) {
7503
7897
  return map;
7504
7898
  }, [roles]);
7505
7899
  if (errorCode === "NotConfigured") {
7506
- const notConfiguredPlaceholder = t(
7900
+ const notConfiguredPlaceholder = t2(
7507
7901
  "DiscordNotConfigured",
7508
7902
  "Not configured \u2014 set in /dirk_lib"
7509
7903
  );
@@ -7571,9 +7965,9 @@ function DiscordRoleSelect(props) {
7571
7965
  e.stopPropagation();
7572
7966
  refresh();
7573
7967
  },
7574
- title: t("Refresh", "Refresh"),
7968
+ title: t2("Refresh", "Refresh"),
7575
7969
  loading,
7576
- "aria-label": t("Refresh", "Refresh"),
7970
+ "aria-label": t2("Refresh", "Refresh"),
7577
7971
  children: /* @__PURE__ */ jsx(RefreshCw, { size: "1.2vh" })
7578
7972
  }
7579
7973
  );
@@ -7583,7 +7977,7 @@ function DiscordRoleSelect(props) {
7583
7977
  {
7584
7978
  label: label2,
7585
7979
  size,
7586
- placeholder: placeholder || t("PickRoles", "Pick roles..."),
7980
+ placeholder: placeholder || t2("PickRoles", "Pick roles..."),
7587
7981
  data,
7588
7982
  value: props.value || [],
7589
7983
  onChange: (ids) => props.onChange(ids),
@@ -7595,7 +7989,7 @@ function DiscordRoleSelect(props) {
7595
7989
  rightSectionPointerEvents: "all",
7596
7990
  style,
7597
7991
  styles,
7598
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
7992
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7599
7993
  }
7600
7994
  );
7601
7995
  }
@@ -7604,7 +7998,7 @@ function DiscordRoleSelect(props) {
7604
7998
  {
7605
7999
  label: label2,
7606
8000
  size,
7607
- placeholder: placeholder || t("PickRole", "Pick a role..."),
8001
+ placeholder: placeholder || t2("PickRole", "Pick a role..."),
7608
8002
  data,
7609
8003
  value: props.value || null,
7610
8004
  onChange: (id) => props.onChange(id),
@@ -7616,11 +8010,11 @@ function DiscordRoleSelect(props) {
7616
8010
  rightSectionPointerEvents: "all",
7617
8011
  style,
7618
8012
  styles,
7619
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
8013
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7620
8014
  }
7621
8015
  );
7622
8016
  }
7623
- var t2 = (key, fallback) => {
8017
+ var t3 = (key, fallback) => {
7624
8018
  const v = locale(key);
7625
8019
  return v === key ? fallback : v;
7626
8020
  };
@@ -7641,7 +8035,7 @@ function FrameworkHint({ framework }) {
7641
8035
  const theme2 = useMantineTheme();
7642
8036
  if (!framework) return null;
7643
8037
  const hintKey = `AccountFrameworkHint_${framework}`;
7644
- const text = t2(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
8038
+ const text = t3(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
7645
8039
  return /* @__PURE__ */ jsxs(
7646
8040
  Flex,
7647
8041
  {
@@ -7725,9 +8119,9 @@ function AccountSelect(props) {
7725
8119
  e.stopPropagation();
7726
8120
  refresh();
7727
8121
  },
7728
- title: t2("Refresh", "Refresh"),
8122
+ title: t3("Refresh", "Refresh"),
7729
8123
  loading,
7730
- "aria-label": t2("Refresh", "Refresh"),
8124
+ "aria-label": t3("Refresh", "Refresh"),
7731
8125
  children: /* @__PURE__ */ jsx(RefreshCw, { size: "1.2vh" })
7732
8126
  }
7733
8127
  );
@@ -7741,14 +8135,14 @@ function AccountSelect(props) {
7741
8135
  rightSection: refreshButton,
7742
8136
  rightSectionPointerEvents: "all",
7743
8137
  style,
7744
- nothingFoundMessage: t2("NoAccounts", "No accounts available")
8138
+ nothingFoundMessage: t3("NoAccounts", "No accounts available")
7745
8139
  };
7746
8140
  return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "0.3vh", style, children: [
7747
8141
  props.multi ? /* @__PURE__ */ jsx(
7748
8142
  MultiSelect,
7749
8143
  {
7750
8144
  ...sharedSelectProps,
7751
- placeholder: placeholder || t2("PickAccounts", "Pick accounts (or type custom)..."),
8145
+ placeholder: placeholder || t3("PickAccounts", "Pick accounts (or type custom)..."),
7752
8146
  value: props.value || [],
7753
8147
  onChange: (names) => props.onChange(names),
7754
8148
  renderOption,
@@ -7760,7 +8154,7 @@ function AccountSelect(props) {
7760
8154
  Select,
7761
8155
  {
7762
8156
  ...sharedSelectProps,
7763
- placeholder: placeholder || t2("PickAccount", "Pick an account..."),
8157
+ placeholder: placeholder || t3("PickAccount", "Pick an account..."),
7764
8158
  value: props.value || null,
7765
8159
  onChange: (name) => props.onChange(name),
7766
8160
  renderOption,
@@ -8052,175 +8446,6 @@ function WorldPositionGotoButton2({
8052
8446
  }
8053
8447
  ) });
8054
8448
  }
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
8449
  function usePickDoor() {
8225
8450
  const begin = useAdminToolStore((s) => s.begin);
8226
8451
  return async () => {
@@ -8612,6 +8837,6 @@ var Vector4Schema = z.object({
8612
8837
  w: z.number()
8613
8838
  });
8614
8839
 
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 };
8840
+ export { AccessOverrideSection, AccountSelect, AdminPageTitle, AnimPostFxSelect, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BlipMarker, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, DEFAULT_PALETTE, DirkProvider, DiscordRoleSelect, DoorPickerButton, FiveMKeyBindInput, FloatingParticles, FormProvider, GTA_CONTROLS, GTA_CONTROL_GROUP_ORDER, GroupName, GroupRank, GroupSelect, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, InfoBox, InputContainer, InstructionPanel, LevelBanner, LevelPanel, Map2 as Map, MapLayer, MissingItemsBanner, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PlayerSelect, PositionPicker, PromptModal, ScenarioSelect, SegmentedControl, SegmentedProgress, SelectItem, SwitchPanel, TestBed, ThemeOverrideSchema, ThemeOverrideSection, Title, TornEdgeSVGFilter, Vector2Schema, Vector3Schema, Vector4DeleteButton, Vector4Display, Vector4Schema, WorldPositionGotoButton, WorldPositionPicker, WorldPositionSetButton, ZoomControls, blipUrl, blipUrlForSprite, clearAdminState, colorWithAlpha, copyToClipboard, createFormStore, createScriptConfig, createSkill, defaultThemeOverride, dirkQueryClient, ensureFrameworkGroups, extractDefaults, fetchLuaTable, fetchNui, formatGtaControl, gameToMap, getBlipColor, getBlipEntry, getGtaControl, getImageShape, getItemImageUrl, getScriptConfigInstance, initialFetches, internalEvent, isEnvBrowser, isProfanity, latPr100, locale, localeStore, mapCenter, mapToGame, noop, numberToRoman, openLink, registerInitialFetch, registerInitialLuaTableFetch, runFetches, selectAllGroups, splitFAString, updatePresignedURL, uploadImage, useAdminState, useAdminToolStore, useAudio, useAutoFetcher, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useFrameworkGroups, useItems, useItemsList, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore, useNuiEvent, usePickDoor, usePlayers, useProfanityStore, useSettings, useTornEdges, useValidModels };
8616
8841
  //# sourceMappingURL=index.js.map
8617
8842
  //# sourceMappingURL=index.js.map