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.cjs CHANGED
@@ -4,6 +4,7 @@ var core = require('@mantine/core');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var React4 = require('react');
6
6
  var zustand = require('zustand');
7
+ var zod = require('zod');
7
8
  var axios = require('axios');
8
9
  var reactFontawesome = require('@fortawesome/react-fontawesome');
9
10
  var framerMotion = require('framer-motion');
@@ -28,7 +29,6 @@ var leaflet = require('leaflet');
28
29
  require('leaflet/dist/leaflet.css');
29
30
  var reactLeaflet = require('react-leaflet');
30
31
  var reactLeafletComponentMarker = require('@adamscybot/react-leaflet-component-marker');
31
- var zod = require('zod');
32
32
 
33
33
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
34
34
 
@@ -1321,6 +1321,32 @@ var openLink = (url) => {
1321
1321
  window.invokeNative("openLink", url);
1322
1322
  }
1323
1323
  };
1324
+ var DEFAULT_PALETTE = [
1325
+ "#f0f4ff",
1326
+ "#d9e3ff",
1327
+ "#bfcfff",
1328
+ "#a6bbff",
1329
+ "#8ca7ff",
1330
+ "#7393ff",
1331
+ "#5a7fff",
1332
+ "#406bff",
1333
+ "#2547ff",
1334
+ "#0b33ff"
1335
+ ];
1336
+ var ThemeOverrideSchema = zod.z.object({
1337
+ useOverride: zod.z.boolean(),
1338
+ primaryColor: zod.z.string(),
1339
+ primaryShade: zod.z.number(),
1340
+ customTheme: zod.z.array(zod.z.string())
1341
+ });
1342
+ var defaultThemeOverride = {
1343
+ useOverride: false,
1344
+ primaryColor: "dirk",
1345
+ primaryShade: 5,
1346
+ customTheme: DEFAULT_PALETTE
1347
+ };
1348
+
1349
+ // src/utils/useSettings.ts
1324
1350
  var useSettings = zustand.create(() => ({
1325
1351
  currency: "$",
1326
1352
  game: "fivem",
@@ -1328,18 +1354,7 @@ var useSettings = zustand.create(() => ({
1328
1354
  primaryShade: 9,
1329
1355
  itemImgPath: "",
1330
1356
  resourceVersion: "dev",
1331
- customTheme: [
1332
- "#f0f4ff",
1333
- "#d9e3ff",
1334
- "#bfcfff",
1335
- "#a6bbff",
1336
- "#8ca7ff",
1337
- "#7393ff",
1338
- "#5a7fff",
1339
- "#406bff",
1340
- "#2547ff",
1341
- "#0b33ff"
1342
- ]
1357
+ customTheme: DEFAULT_PALETTE
1343
1358
  }));
1344
1359
 
1345
1360
  // src/utils/fetchNui.ts
@@ -3751,11 +3766,11 @@ function ConfirmModal({
3751
3766
  placeholder: confirmText,
3752
3767
  value: typed,
3753
3768
  onChange: (e) => setTyped(e.currentTarget.value),
3754
- styles: (t3) => ({
3769
+ styles: (t4) => ({
3755
3770
  input: {
3756
- backgroundColor: core.alpha(t3.colors.dark[7], 0.5),
3771
+ backgroundColor: core.alpha(t4.colors.dark[7], 0.5),
3757
3772
  border: `0.1vh solid ${core.alpha(
3758
- typed === confirmText ? "#ef4444" : t3.colors.dark[5],
3773
+ typed === confirmText ? "#ef4444" : t4.colors.dark[5],
3759
3774
  0.5
3760
3775
  )}`,
3761
3776
  color: "rgba(255,255,255,0.85)",
@@ -4433,7 +4448,7 @@ function useAdminState(key, initial) {
4433
4448
  React4.useEffect(() => {
4434
4449
  const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
4435
4450
  listeners.set(key, set);
4436
- const handler = () => setTick((t3) => t3 + 1);
4451
+ const handler = () => setTick((t4) => t4 + 1);
4437
4452
  set.add(handler);
4438
4453
  return () => {
4439
4454
  set.delete(handler);
@@ -6613,24 +6628,6 @@ var MANTINE_COLOR_OPTIONS = [
6613
6628
  "yellow",
6614
6629
  "orange"
6615
6630
  ].map((value) => ({ value, label: value }));
6616
- var DEFAULT_PALETTE = [
6617
- "#f0f4ff",
6618
- "#d9e3ff",
6619
- "#bfcfff",
6620
- "#a6bbff",
6621
- "#8ca7ff",
6622
- "#7393ff",
6623
- "#5a7fff",
6624
- "#406bff",
6625
- "#2547ff",
6626
- "#0b33ff"
6627
- ];
6628
- var DEFAULT_VALUE = {
6629
- useOverride: false,
6630
- primaryColor: "dirk",
6631
- primaryShade: 5,
6632
- customTheme: DEFAULT_PALETTE
6633
- };
6634
6631
  function GroupLabel({ label: label2 }) {
6635
6632
  return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", mt: "xxs", children: [
6636
6633
  /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
@@ -6645,10 +6642,10 @@ function ThemeOverrideSection({
6645
6642
  const color = mantineTheme.colors[mantineTheme.primaryColor][5];
6646
6643
  const raw = useFormField(schemaKey);
6647
6644
  const value = {
6648
- useOverride: raw?.useOverride ?? DEFAULT_VALUE.useOverride,
6649
- primaryColor: raw?.primaryColor ?? DEFAULT_VALUE.primaryColor,
6650
- primaryShade: raw?.primaryShade ?? DEFAULT_VALUE.primaryShade,
6651
- customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : DEFAULT_VALUE.customTheme
6645
+ useOverride: raw?.useOverride ?? defaultThemeOverride.useOverride,
6646
+ primaryColor: raw?.primaryColor ?? defaultThemeOverride.primaryColor,
6647
+ primaryShade: raw?.primaryShade ?? defaultThemeOverride.primaryShade,
6648
+ customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : defaultThemeOverride.customTheme
6652
6649
  };
6653
6650
  const { setValue } = useFormActions();
6654
6651
  const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
@@ -6855,6 +6852,403 @@ function SwatchTile({
6855
6852
  ) })
6856
6853
  ] });
6857
6854
  }
6855
+ function usePlayers(opts = {}) {
6856
+ const {
6857
+ includeOffline = false,
6858
+ search = "",
6859
+ limit = 50,
6860
+ staleTimeMs,
6861
+ refetchIntervalMs
6862
+ } = opts;
6863
+ const query = reactQuery.useQuery({
6864
+ queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
6865
+ queryFn: async () => {
6866
+ const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
6867
+ const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
6868
+ const result = await fetchNui(
6869
+ "ADMIN_TOOL_QUERY",
6870
+ payload,
6871
+ // Browser-dev fallback. Returns a couple of mock players so the
6872
+ // dev shell isn't blank.
6873
+ includeOffline ? [
6874
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
6875
+ { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
6876
+ ] : [
6877
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
6878
+ ]
6879
+ );
6880
+ return Array.isArray(result) ? result : [];
6881
+ },
6882
+ staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
6883
+ gcTime: 5 * 6e4,
6884
+ refetchInterval: refetchIntervalMs ?? false,
6885
+ refetchOnWindowFocus: false,
6886
+ placeholderData: includeOffline ? reactQuery.keepPreviousData : void 0
6887
+ });
6888
+ return {
6889
+ players: query.data ?? [],
6890
+ isLoading: query.isLoading,
6891
+ isFetching: query.isFetching,
6892
+ error: query.error ?? null,
6893
+ refresh: () => query.refetch()
6894
+ };
6895
+ }
6896
+ var DEBOUNCE_MS = 300;
6897
+ var ONLINE_COLOR = "#3FA83F";
6898
+ var OFFLINE_COLOR = "#E54141";
6899
+ function PlayerSelect({
6900
+ value,
6901
+ onChange,
6902
+ includeOffline = false,
6903
+ limit = 50,
6904
+ searchable: searchableProp,
6905
+ placeholder,
6906
+ nothingFoundMessage: nothingFoundMessageProp,
6907
+ loadingLabel = "Loading\u2026",
6908
+ onlineLabel = "Online",
6909
+ offlineLabel = "Offline",
6910
+ refreshLabel = "Refresh player list",
6911
+ ...rest
6912
+ }) {
6913
+ const theme2 = core.useMantineTheme();
6914
+ const color = theme2.colors[theme2.primaryColor][5];
6915
+ const [searchInput, setSearchInput] = React4.useState("");
6916
+ const [debouncedSearch, setDebouncedSearch] = React4.useState("");
6917
+ const { players, isFetching, refresh } = usePlayers({
6918
+ includeOffline,
6919
+ search: includeOffline ? debouncedSearch : void 0,
6920
+ limit
6921
+ });
6922
+ const sortedPlayers = React4.useMemo(
6923
+ () => [...players].sort((a, b) => {
6924
+ if (a.online !== b.online) return a.online ? -1 : 1;
6925
+ return a.charName.localeCompare(b.charName);
6926
+ }),
6927
+ [players]
6928
+ );
6929
+ const playerByCitizen = React4.useMemo(() => {
6930
+ const m = /* @__PURE__ */ new Map();
6931
+ for (const p of sortedPlayers) m.set(p.citizenId, p);
6932
+ return m;
6933
+ }, [sortedPlayers]);
6934
+ const data = React4.useMemo(() => {
6935
+ const items = sortedPlayers.map((p) => ({
6936
+ value: p.citizenId,
6937
+ label: formatLabel(p)
6938
+ }));
6939
+ if (value && !items.some((i) => i.value === value)) {
6940
+ items.unshift({ value, label: value });
6941
+ }
6942
+ return items;
6943
+ }, [sortedPlayers, value]);
6944
+ const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
6945
+ const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
6946
+ React4.useEffect(() => {
6947
+ if (!includeOffline) return;
6948
+ if (selectedLabel && searchInput === selectedLabel) return;
6949
+ const t4 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
6950
+ return () => clearTimeout(t4);
6951
+ }, [searchInput, includeOffline, selectedLabel]);
6952
+ const renderOption = ({ option }) => {
6953
+ const p = playerByCitizen.get(option.value);
6954
+ if (!p) return option.label;
6955
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
6956
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
6957
+ /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
6958
+ ] });
6959
+ };
6960
+ return /* @__PURE__ */ jsxRuntime.jsx(
6961
+ core.Select,
6962
+ {
6963
+ ...rest,
6964
+ data,
6965
+ value: value ?? null,
6966
+ onChange: (v) => {
6967
+ if (!v) return onChange(null);
6968
+ const player = playerByCitizen.get(v) ?? null;
6969
+ onChange(player);
6970
+ },
6971
+ searchable: searchableProp ?? true,
6972
+ searchValue: includeOffline ? searchInput : void 0,
6973
+ onSearchChange: includeOffline ? setSearchInput : void 0,
6974
+ placeholder,
6975
+ nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
6976
+ maxDropdownHeight: 300,
6977
+ renderOption,
6978
+ leftSection: selectedPlayer ? /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
6979
+ rightSectionWidth: "3.5vh",
6980
+ rightSectionPointerEvents: "all",
6981
+ rightSection: /* @__PURE__ */ jsxRuntime.jsx(
6982
+ core.ActionIcon,
6983
+ {
6984
+ variant: "subtle",
6985
+ size: "sm",
6986
+ onClick: (e) => {
6987
+ e.stopPropagation();
6988
+ refresh();
6989
+ },
6990
+ "aria-label": refreshLabel,
6991
+ title: refreshLabel,
6992
+ style: { marginRight: "0.6vh" },
6993
+ children: isFetching ? /* @__PURE__ */ jsxRuntime.jsx(core.Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.4vh", color })
6994
+ }
6995
+ )
6996
+ }
6997
+ );
6998
+ }
6999
+ function StatusDot({ online, onlineLabel, offlineLabel }) {
7000
+ return /* @__PURE__ */ jsxRuntime.jsx(
7001
+ "span",
7002
+ {
7003
+ title: online ? onlineLabel : offlineLabel,
7004
+ style: {
7005
+ display: "inline-block",
7006
+ width: "0.9vh",
7007
+ height: "0.9vh",
7008
+ borderRadius: "50%",
7009
+ backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
7010
+ boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
7011
+ flexShrink: 0
7012
+ }
7013
+ }
7014
+ );
7015
+ }
7016
+ function formatLabel(p) {
7017
+ const parts = [p.charName || p.citizenId];
7018
+ if (p.online) {
7019
+ if (p.name) parts.push(p.name);
7020
+ if (p.id != null) parts.push(`#${p.id}`);
7021
+ }
7022
+ return parts.join(" \xB7 ");
7023
+ }
7024
+ var t = (key, fallback) => {
7025
+ const v = locale(key);
7026
+ return v === key ? fallback : v;
7027
+ };
7028
+ var DEFAULT_VALUE = {
7029
+ groups: [],
7030
+ identifiers: []
7031
+ };
7032
+ function SectionLabel({ icon: Icon, label: label2 }) {
7033
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", mt: "xs", children: [
7034
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: "1.4vh", color: "rgba(255,255,255,0.3)" }),
7035
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label2 }),
7036
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, height: "0.05vh", background: "rgba(255,255,255,0.06)" } })
7037
+ ] });
7038
+ }
7039
+ function EmptyHint({ text }) {
7040
+ return /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.25)", ta: "center", py: "xs", children: text });
7041
+ }
7042
+ function AccessOverrideSection({
7043
+ schemaKey = "access",
7044
+ title,
7045
+ description: description2,
7046
+ groupType,
7047
+ includeOffline = true
7048
+ }) {
7049
+ const mantineTheme = core.useMantineTheme();
7050
+ const color = mantineTheme.colors[mantineTheme.primaryColor][5];
7051
+ const raw = useFormField(schemaKey);
7052
+ const value = {
7053
+ groups: Array.isArray(raw?.groups) ? raw.groups : DEFAULT_VALUE.groups,
7054
+ identifiers: Array.isArray(raw?.identifiers) ? raw.identifiers : DEFAULT_VALUE.identifiers
7055
+ };
7056
+ const { setValue } = useFormActions();
7057
+ const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
7058
+ const [addingGroup, setAddingGroup] = React4.useState(false);
7059
+ const [addingPlayer, setAddingPlayer] = React4.useState(false);
7060
+ const commitGroup = (name) => {
7061
+ if (!name) return;
7062
+ set("groups", [...value.groups, name]);
7063
+ setAddingGroup(false);
7064
+ };
7065
+ const setGroup = (index, name) => {
7066
+ const next = [...value.groups];
7067
+ next[index] = name;
7068
+ set("groups", next.filter((g) => g !== ""));
7069
+ };
7070
+ const removeGroup = (index) => set("groups", value.groups.filter((_, i) => i !== index));
7071
+ const commitIdentifier = (id) => {
7072
+ if (!id) return;
7073
+ set("identifiers", [...value.identifiers, id]);
7074
+ setAddingPlayer(false);
7075
+ };
7076
+ const setIdentifier = (index, id) => {
7077
+ const next = [...value.identifiers];
7078
+ next[index] = id;
7079
+ set("identifiers", next.filter((x) => x !== ""));
7080
+ };
7081
+ const removeIdentifier = (index) => set("identifiers", value.identifiers.filter((_, i) => i !== index));
7082
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7083
+ core.Flex,
7084
+ {
7085
+ direction: "column",
7086
+ gap: "xs",
7087
+ p: "sm",
7088
+ style: { flex: 1, minHeight: 0, overflowY: "auto" },
7089
+ children: [
7090
+ /* @__PURE__ */ jsxRuntime.jsx(
7091
+ AdminPageTitle,
7092
+ {
7093
+ icon: lucideReact.ShieldCheck,
7094
+ title: title || t("Access", "Access"),
7095
+ color
7096
+ }
7097
+ ),
7098
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.4)", children: description2 || t(
7099
+ "AccessOverrideDesc",
7100
+ "Grant access to this resource's admin panel to specific groups and players, in addition to your global dirk_lib admins. Enforcement is server-side."
7101
+ ) }),
7102
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { icon: lucideReact.Users, label: t("AccessGroups", "Groups") }),
7103
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "xxs", children: [
7104
+ value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsxRuntime.jsx(EmptyHint, { text: t("AccessNoGroups", "No groups added.") }),
7105
+ value.groups.map((name, i) => /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7106
+ /* @__PURE__ */ jsxRuntime.jsx(
7107
+ GroupName,
7108
+ {
7109
+ value: name,
7110
+ onChange: (v) => setGroup(i, v),
7111
+ type: groupType,
7112
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7113
+ style: { flex: 1 }
7114
+ }
7115
+ ),
7116
+ /* @__PURE__ */ jsxRuntime.jsx(
7117
+ core.ActionIcon,
7118
+ {
7119
+ variant: "subtle",
7120
+ color: "red",
7121
+ size: "md",
7122
+ onClick: () => removeGroup(i),
7123
+ title: t("Remove", "Remove"),
7124
+ "aria-label": t("Remove", "Remove"),
7125
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7126
+ }
7127
+ )
7128
+ ] }, i)),
7129
+ addingGroup && /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7130
+ /* @__PURE__ */ jsxRuntime.jsx(
7131
+ GroupName,
7132
+ {
7133
+ value: "",
7134
+ onChange: (v) => commitGroup(v),
7135
+ type: groupType,
7136
+ placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7137
+ style: { flex: 1 }
7138
+ }
7139
+ ),
7140
+ /* @__PURE__ */ jsxRuntime.jsx(
7141
+ core.ActionIcon,
7142
+ {
7143
+ variant: "subtle",
7144
+ color: "red",
7145
+ size: "md",
7146
+ onClick: () => setAddingGroup(false),
7147
+ title: t("Remove", "Remove"),
7148
+ "aria-label": t("Remove", "Remove"),
7149
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7150
+ }
7151
+ )
7152
+ ] })
7153
+ ] }),
7154
+ !addingGroup && /* @__PURE__ */ jsxRuntime.jsx(AddRowButton, { label: t("AccessAddGroup", "Add group"), onClick: () => setAddingGroup(true) }),
7155
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { icon: lucideReact.UserRound, label: t("AccessPlayers", "Players") }),
7156
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "xxs", children: [
7157
+ value.identifiers.length === 0 && !addingPlayer && /* @__PURE__ */ jsxRuntime.jsx(EmptyHint, { text: t("AccessNoPlayers", "No players added.") }),
7158
+ value.identifiers.map((id, i) => /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7159
+ /* @__PURE__ */ jsxRuntime.jsx(
7160
+ PlayerSelect,
7161
+ {
7162
+ value: id || null,
7163
+ onChange: (p) => setIdentifier(i, p?.citizenId ?? ""),
7164
+ includeOffline,
7165
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7166
+ style: { flex: 1 },
7167
+ size: "xs"
7168
+ }
7169
+ ),
7170
+ /* @__PURE__ */ jsxRuntime.jsx(
7171
+ core.ActionIcon,
7172
+ {
7173
+ variant: "subtle",
7174
+ color: "red",
7175
+ size: "md",
7176
+ onClick: () => removeIdentifier(i),
7177
+ title: t("Remove", "Remove"),
7178
+ "aria-label": t("Remove", "Remove"),
7179
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7180
+ }
7181
+ )
7182
+ ] }, i)),
7183
+ addingPlayer && /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7184
+ /* @__PURE__ */ jsxRuntime.jsx(
7185
+ PlayerSelect,
7186
+ {
7187
+ value: null,
7188
+ onChange: (p) => commitIdentifier(p?.citizenId ?? ""),
7189
+ includeOffline,
7190
+ placeholder: t("AccessPickPlayer", "Pick a player\u2026"),
7191
+ style: { flex: 1 },
7192
+ size: "xs"
7193
+ }
7194
+ ),
7195
+ /* @__PURE__ */ jsxRuntime.jsx(
7196
+ core.ActionIcon,
7197
+ {
7198
+ variant: "subtle",
7199
+ color: "red",
7200
+ size: "md",
7201
+ onClick: () => setAddingPlayer(false),
7202
+ title: t("Remove", "Remove"),
7203
+ "aria-label": t("Remove", "Remove"),
7204
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7205
+ }
7206
+ )
7207
+ ] })
7208
+ ] }),
7209
+ !addingPlayer && /* @__PURE__ */ jsxRuntime.jsx(AddRowButton, { label: t("AccessAddPlayer", "Add player"), onClick: () => setAddingPlayer(true) })
7210
+ ]
7211
+ }
7212
+ );
7213
+ }
7214
+ function AddRowButton({ label: label2, onClick }) {
7215
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7216
+ core.Flex,
7217
+ {
7218
+ align: "center",
7219
+ justify: "center",
7220
+ gap: "xs",
7221
+ onClick,
7222
+ role: "button",
7223
+ tabIndex: 0,
7224
+ onKeyDown: (e) => {
7225
+ if (e.key === "Enter" || e.key === " ") {
7226
+ e.preventDefault();
7227
+ onClick();
7228
+ }
7229
+ },
7230
+ py: "xs",
7231
+ style: {
7232
+ cursor: "pointer",
7233
+ border: "0.1vh dashed rgba(255,255,255,0.12)",
7234
+ borderRadius: "0.4vh",
7235
+ transition: "background 0.15s, border-color 0.15s"
7236
+ },
7237
+ onMouseEnter: (e) => {
7238
+ e.currentTarget.style.background = "rgba(255,255,255,0.03)";
7239
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.2)";
7240
+ },
7241
+ onMouseLeave: (e) => {
7242
+ e.currentTarget.style.background = "transparent";
7243
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.12)";
7244
+ },
7245
+ children: [
7246
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: "1.4vh", color: "rgba(255,255,255,0.4)" }),
7247
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "rgba(255,255,255,0.4)", children: label2 })
7248
+ ]
7249
+ }
7250
+ );
7251
+ }
6858
7252
 
6859
7253
  // src/utils/gtaAnimPostFx.ts
6860
7254
  var GTA_ANIM_POST_FX_GROUP_ORDER = [
@@ -7444,7 +7838,7 @@ function ScenarioSelect({
7444
7838
  }
7445
7839
  );
7446
7840
  }
7447
- var t = (key, fallback) => {
7841
+ var t2 = (key, fallback) => {
7448
7842
  const v = locale(key);
7449
7843
  return v === key ? fallback : v;
7450
7844
  };
@@ -7512,7 +7906,7 @@ function DiscordRoleSelect(props) {
7512
7906
  return map;
7513
7907
  }, [roles]);
7514
7908
  if (errorCode === "NotConfigured") {
7515
- const notConfiguredPlaceholder = t(
7909
+ const notConfiguredPlaceholder = t2(
7516
7910
  "DiscordNotConfigured",
7517
7911
  "Not configured \u2014 set in /dirk_lib"
7518
7912
  );
@@ -7580,9 +7974,9 @@ function DiscordRoleSelect(props) {
7580
7974
  e.stopPropagation();
7581
7975
  refresh();
7582
7976
  },
7583
- title: t("Refresh", "Refresh"),
7977
+ title: t2("Refresh", "Refresh"),
7584
7978
  loading,
7585
- "aria-label": t("Refresh", "Refresh"),
7979
+ "aria-label": t2("Refresh", "Refresh"),
7586
7980
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.2vh" })
7587
7981
  }
7588
7982
  );
@@ -7592,7 +7986,7 @@ function DiscordRoleSelect(props) {
7592
7986
  {
7593
7987
  label: label2,
7594
7988
  size,
7595
- placeholder: placeholder || t("PickRoles", "Pick roles..."),
7989
+ placeholder: placeholder || t2("PickRoles", "Pick roles..."),
7596
7990
  data,
7597
7991
  value: props.value || [],
7598
7992
  onChange: (ids) => props.onChange(ids),
@@ -7604,7 +7998,7 @@ function DiscordRoleSelect(props) {
7604
7998
  rightSectionPointerEvents: "all",
7605
7999
  style,
7606
8000
  styles,
7607
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
8001
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7608
8002
  }
7609
8003
  );
7610
8004
  }
@@ -7613,7 +8007,7 @@ function DiscordRoleSelect(props) {
7613
8007
  {
7614
8008
  label: label2,
7615
8009
  size,
7616
- placeholder: placeholder || t("PickRole", "Pick a role..."),
8010
+ placeholder: placeholder || t2("PickRole", "Pick a role..."),
7617
8011
  data,
7618
8012
  value: props.value || null,
7619
8013
  onChange: (id) => props.onChange(id),
@@ -7625,11 +8019,11 @@ function DiscordRoleSelect(props) {
7625
8019
  rightSectionPointerEvents: "all",
7626
8020
  style,
7627
8021
  styles,
7628
- nothingFoundMessage: errorCode ? `${t("Error", "Error")} (${errorCode})` : t("NoRoles", "No roles found")
8022
+ nothingFoundMessage: errorCode ? `${t2("Error", "Error")} (${errorCode})` : t2("NoRoles", "No roles found")
7629
8023
  }
7630
8024
  );
7631
8025
  }
7632
- var t2 = (key, fallback) => {
8026
+ var t3 = (key, fallback) => {
7633
8027
  const v = locale(key);
7634
8028
  return v === key ? fallback : v;
7635
8029
  };
@@ -7650,7 +8044,7 @@ function FrameworkHint({ framework }) {
7650
8044
  const theme2 = core.useMantineTheme();
7651
8045
  if (!framework) return null;
7652
8046
  const hintKey = `AccountFrameworkHint_${framework}`;
7653
- const text = t2(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
8047
+ const text = t3(hintKey, FRAMEWORK_HINTS[framework] || FRAMEWORK_HINTS.unknown);
7654
8048
  return /* @__PURE__ */ jsxRuntime.jsxs(
7655
8049
  core.Flex,
7656
8050
  {
@@ -7734,9 +8128,9 @@ function AccountSelect(props) {
7734
8128
  e.stopPropagation();
7735
8129
  refresh();
7736
8130
  },
7737
- title: t2("Refresh", "Refresh"),
8131
+ title: t3("Refresh", "Refresh"),
7738
8132
  loading,
7739
- "aria-label": t2("Refresh", "Refresh"),
8133
+ "aria-label": t3("Refresh", "Refresh"),
7740
8134
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.2vh" })
7741
8135
  }
7742
8136
  );
@@ -7750,14 +8144,14 @@ function AccountSelect(props) {
7750
8144
  rightSection: refreshButton,
7751
8145
  rightSectionPointerEvents: "all",
7752
8146
  style,
7753
- nothingFoundMessage: t2("NoAccounts", "No accounts available")
8147
+ nothingFoundMessage: t3("NoAccounts", "No accounts available")
7754
8148
  };
7755
8149
  return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "0.3vh", style, children: [
7756
8150
  props.multi ? /* @__PURE__ */ jsxRuntime.jsx(
7757
8151
  core.MultiSelect,
7758
8152
  {
7759
8153
  ...sharedSelectProps,
7760
- placeholder: placeholder || t2("PickAccounts", "Pick accounts (or type custom)..."),
8154
+ placeholder: placeholder || t3("PickAccounts", "Pick accounts (or type custom)..."),
7761
8155
  value: props.value || [],
7762
8156
  onChange: (names) => props.onChange(names),
7763
8157
  renderOption,
@@ -7769,7 +8163,7 @@ function AccountSelect(props) {
7769
8163
  core.Select,
7770
8164
  {
7771
8165
  ...sharedSelectProps,
7772
- placeholder: placeholder || t2("PickAccount", "Pick an account..."),
8166
+ placeholder: placeholder || t3("PickAccount", "Pick an account..."),
7773
8167
  value: props.value || null,
7774
8168
  onChange: (name) => props.onChange(name),
7775
8169
  renderOption,
@@ -8061,175 +8455,6 @@ function WorldPositionGotoButton2({
8061
8455
  }
8062
8456
  ) });
8063
8457
  }
8064
- function usePlayers(opts = {}) {
8065
- const {
8066
- includeOffline = false,
8067
- search = "",
8068
- limit = 50,
8069
- staleTimeMs,
8070
- refetchIntervalMs
8071
- } = opts;
8072
- const query = reactQuery.useQuery({
8073
- queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
8074
- queryFn: async () => {
8075
- const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
8076
- const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
8077
- const result = await fetchNui(
8078
- "ADMIN_TOOL_QUERY",
8079
- payload,
8080
- // Browser-dev fallback. Returns a couple of mock players so the
8081
- // dev shell isn't blank.
8082
- includeOffline ? [
8083
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
8084
- { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
8085
- ] : [
8086
- { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
8087
- ]
8088
- );
8089
- return Array.isArray(result) ? result : [];
8090
- },
8091
- staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
8092
- gcTime: 5 * 6e4,
8093
- refetchInterval: refetchIntervalMs ?? false,
8094
- refetchOnWindowFocus: false,
8095
- placeholderData: includeOffline ? reactQuery.keepPreviousData : void 0
8096
- });
8097
- return {
8098
- players: query.data ?? [],
8099
- isLoading: query.isLoading,
8100
- isFetching: query.isFetching,
8101
- error: query.error ?? null,
8102
- refresh: () => query.refetch()
8103
- };
8104
- }
8105
- var DEBOUNCE_MS = 300;
8106
- var ONLINE_COLOR = "#3FA83F";
8107
- var OFFLINE_COLOR = "#E54141";
8108
- function PlayerSelect({
8109
- value,
8110
- onChange,
8111
- includeOffline = false,
8112
- limit = 50,
8113
- searchable: searchableProp,
8114
- placeholder,
8115
- nothingFoundMessage: nothingFoundMessageProp,
8116
- loadingLabel = "Loading\u2026",
8117
- onlineLabel = "Online",
8118
- offlineLabel = "Offline",
8119
- refreshLabel = "Refresh player list",
8120
- ...rest
8121
- }) {
8122
- const theme2 = core.useMantineTheme();
8123
- const color = theme2.colors[theme2.primaryColor][5];
8124
- const [searchInput, setSearchInput] = React4.useState("");
8125
- const [debouncedSearch, setDebouncedSearch] = React4.useState("");
8126
- const { players, isFetching, refresh } = usePlayers({
8127
- includeOffline,
8128
- search: includeOffline ? debouncedSearch : void 0,
8129
- limit
8130
- });
8131
- const sortedPlayers = React4.useMemo(
8132
- () => [...players].sort((a, b) => {
8133
- if (a.online !== b.online) return a.online ? -1 : 1;
8134
- return a.charName.localeCompare(b.charName);
8135
- }),
8136
- [players]
8137
- );
8138
- const playerByCitizen = React4.useMemo(() => {
8139
- const m = /* @__PURE__ */ new Map();
8140
- for (const p of sortedPlayers) m.set(p.citizenId, p);
8141
- return m;
8142
- }, [sortedPlayers]);
8143
- const data = React4.useMemo(() => {
8144
- const items = sortedPlayers.map((p) => ({
8145
- value: p.citizenId,
8146
- label: formatLabel(p)
8147
- }));
8148
- if (value && !items.some((i) => i.value === value)) {
8149
- items.unshift({ value, label: value });
8150
- }
8151
- return items;
8152
- }, [sortedPlayers, value]);
8153
- const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
8154
- const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
8155
- React4.useEffect(() => {
8156
- if (!includeOffline) return;
8157
- if (selectedLabel && searchInput === selectedLabel) return;
8158
- const t3 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
8159
- return () => clearTimeout(t3);
8160
- }, [searchInput, includeOffline, selectedLabel]);
8161
- const renderOption = ({ option }) => {
8162
- const p = playerByCitizen.get(option.value);
8163
- if (!p) return option.label;
8164
- return /* @__PURE__ */ jsxRuntime.jsxs(core.Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
8165
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
8166
- /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
8167
- ] });
8168
- };
8169
- return /* @__PURE__ */ jsxRuntime.jsx(
8170
- core.Select,
8171
- {
8172
- ...rest,
8173
- data,
8174
- value: value ?? null,
8175
- onChange: (v) => {
8176
- if (!v) return onChange(null);
8177
- const player = playerByCitizen.get(v) ?? null;
8178
- onChange(player);
8179
- },
8180
- searchable: searchableProp ?? true,
8181
- searchValue: includeOffline ? searchInput : void 0,
8182
- onSearchChange: includeOffline ? setSearchInput : void 0,
8183
- placeholder,
8184
- nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
8185
- maxDropdownHeight: 300,
8186
- renderOption,
8187
- leftSection: selectedPlayer ? /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
8188
- rightSectionWidth: "3.5vh",
8189
- rightSectionPointerEvents: "all",
8190
- rightSection: /* @__PURE__ */ jsxRuntime.jsx(
8191
- core.ActionIcon,
8192
- {
8193
- variant: "subtle",
8194
- size: "sm",
8195
- onClick: (e) => {
8196
- e.stopPropagation();
8197
- refresh();
8198
- },
8199
- "aria-label": refreshLabel,
8200
- title: refreshLabel,
8201
- style: { marginRight: "0.6vh" },
8202
- children: isFetching ? /* @__PURE__ */ jsxRuntime.jsx(core.Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.4vh", color })
8203
- }
8204
- )
8205
- }
8206
- );
8207
- }
8208
- function StatusDot({ online, onlineLabel, offlineLabel }) {
8209
- return /* @__PURE__ */ jsxRuntime.jsx(
8210
- "span",
8211
- {
8212
- title: online ? onlineLabel : offlineLabel,
8213
- style: {
8214
- display: "inline-block",
8215
- width: "0.9vh",
8216
- height: "0.9vh",
8217
- borderRadius: "50%",
8218
- backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
8219
- boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
8220
- flexShrink: 0
8221
- }
8222
- }
8223
- );
8224
- }
8225
- function formatLabel(p) {
8226
- const parts = [p.charName || p.citizenId];
8227
- if (p.online) {
8228
- if (p.name) parts.push(p.name);
8229
- if (p.id != null) parts.push(`#${p.id}`);
8230
- }
8231
- return parts.join(" \xB7 ");
8232
- }
8233
8458
  function usePickDoor() {
8234
8459
  const begin = useAdminToolStore((s) => s.begin);
8235
8460
  return async () => {
@@ -8621,6 +8846,7 @@ var Vector4Schema = zod.z.object({
8621
8846
  w: zod.z.number()
8622
8847
  });
8623
8848
 
8849
+ exports.AccessOverrideSection = AccessOverrideSection;
8624
8850
  exports.AccountSelect = AccountSelect;
8625
8851
  exports.AdminPageTitle = AdminPageTitle;
8626
8852
  exports.AnimPostFxSelect = AnimPostFxSelect;
@@ -8635,6 +8861,7 @@ exports.ConfirmModal = ConfirmModal;
8635
8861
  exports.ControlMultiSelect = ControlMultiSelect;
8636
8862
  exports.ControlSelect = ControlSelect;
8637
8863
  exports.Counter = Counter;
8864
+ exports.DEFAULT_PALETTE = DEFAULT_PALETTE;
8638
8865
  exports.DirkProvider = DirkProvider;
8639
8866
  exports.DiscordRoleSelect = DiscordRoleSelect;
8640
8867
  exports.DoorPickerButton = DoorPickerButton;
@@ -8675,6 +8902,7 @@ exports.SegmentedProgress = SegmentedProgress;
8675
8902
  exports.SelectItem = SelectItem;
8676
8903
  exports.SwitchPanel = SwitchPanel;
8677
8904
  exports.TestBed = TestBed;
8905
+ exports.ThemeOverrideSchema = ThemeOverrideSchema;
8678
8906
  exports.ThemeOverrideSection = ThemeOverrideSection;
8679
8907
  exports.Title = Title;
8680
8908
  exports.TornEdgeSVGFilter = TornEdgeSVGFilter;
@@ -8695,6 +8923,7 @@ exports.copyToClipboard = copyToClipboard;
8695
8923
  exports.createFormStore = createFormStore;
8696
8924
  exports.createScriptConfig = createScriptConfig;
8697
8925
  exports.createSkill = createSkill;
8926
+ exports.defaultThemeOverride = defaultThemeOverride;
8698
8927
  exports.dirkQueryClient = dirkQueryClient;
8699
8928
  exports.ensureFrameworkGroups = ensureFrameworkGroups;
8700
8929
  exports.extractDefaults = extractDefaults;