dirk-cfx-react 1.1.87 → 1.1.89

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
@@ -1,16 +1,15 @@
1
- import { Flex, Text, Image as Image$1, createTheme, Box, Stack, Title as Title$1, Code, TextInput, Select, useMantineTheme, Tooltip, alpha, Progress, RingProgress, Portal, Button, NumberInput, MultiSelect, Loader, Switch, ActionIcon, ColorInput, Popover, MantineProvider, BackgroundImage, Group, JsonInput } from '@mantine/core';
1
+ import { Flex, Text, Image as Image$1, createTheme, alpha, Box, Stack, Title as Title$1, Code, TextInput, Select, useMantineTheme, Tooltip, Progress, RingProgress, Portal, Button, MantineProvider, NumberInput, MultiSelect, Loader, Switch, ActionIcon, ColorInput, Popover, BackgroundImage, Group, JsonInput } from '@mantine/core';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import React6, { createContext, useContext, useEffect, useRef, useState, useCallback, useMemo, useLayoutEffect } from 'react';
3
+ import React4, { createContext, memo, useRef, useEffect, useContext, useState, useCallback, useMemo, useLayoutEffect } from 'react';
4
4
  import { create, useStore, createStore } from 'zustand';
5
5
  import axios from 'axios';
6
6
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7
7
  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, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
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
9
  import clickSoundUrl from './click_sound-PNCRRTM4.mp3';
10
10
  import hoverSoundUrl from './hover_sound-NBUA222C.mp3';
11
11
  import { notifications } from '@mantine/notifications';
12
- import { QueryClient, QueryClientProvider, useInfiniteQuery } from '@tanstack/react-query';
13
- import { generateColors } from '@mantine/colors-generator';
12
+ import { QueryClient, QueryClientProvider, useQuery, keepPreviousData, useInfiniteQuery } from '@tanstack/react-query';
14
13
  import '@mantine/core/styles.css';
15
14
  import '@mantine/notifications/styles.css';
16
15
  import './styles/fonts.css';
@@ -21,6 +20,12 @@ import { library } from '@fortawesome/fontawesome-svg-core';
21
20
  import { fab } from '@fortawesome/free-brands-svg-icons';
22
21
  import { far } from '@fortawesome/free-regular-svg-icons';
23
22
  import { fas } from '@fortawesome/free-solid-svg-icons';
23
+ import { generateColors } from '@mantine/colors-generator';
24
+ import { createPortal } from 'react-dom';
25
+ import { CRS, tileLayer, latLngBounds, latLng } from 'leaflet';
26
+ import 'leaflet/dist/leaflet.css';
27
+ import { MapContainer, useMap } from 'react-leaflet';
28
+ import { Marker } from '@adamscybot/react-leaflet-component-marker';
24
29
  import { z } from 'zod';
25
30
 
26
31
  var __defProp = Object.defineProperty;
@@ -967,6 +972,18 @@ var BLIP_COLOR_DATA = BLIP_COLORS.map(([id, label2]) => ({
967
972
  }));
968
973
  var blipEntryMap = new Map(BLIP_ENTRIES.map(([id, name, ext]) => [String(id), { id, name, ext }]));
969
974
  var blipColorMap = new Map(BLIP_COLORS.map(([id, label2, hex]) => [String(id), { id, label: label2, hex }]));
975
+ function getBlipEntry(spriteId) {
976
+ if (spriteId == null) return void 0;
977
+ return blipEntryMap.get(String(spriteId));
978
+ }
979
+ function getBlipColor(colorId) {
980
+ if (colorId == null) return void 0;
981
+ return blipColorMap.get(String(colorId));
982
+ }
983
+ function blipUrlForSprite(spriteId) {
984
+ const entry = getBlipEntry(spriteId);
985
+ return entry ? blipUrl(entry.name, entry.ext) : null;
986
+ }
970
987
  var renderBlipOption = ({ option }) => {
971
988
  const entry = blipEntryMap.get(option.value);
972
989
  if (!entry) return option.label;
@@ -1244,11 +1261,11 @@ var colorNames = {
1244
1261
  Yellow: { r: 255, g: 255, b: 0 },
1245
1262
  YellowGreen: { r: 154, g: 205, b: 50 }
1246
1263
  };
1247
- function colorWithAlpha(color, alpha12) {
1264
+ function colorWithAlpha(color, alpha18) {
1248
1265
  const lowerCasedColor = color.toLowerCase();
1249
1266
  if (colorNames[lowerCasedColor]) {
1250
1267
  const rgb = colorNames[lowerCasedColor];
1251
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha12})`;
1268
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha18})`;
1252
1269
  }
1253
1270
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1254
1271
  const hex = color.slice(1);
@@ -1256,12 +1273,12 @@ function colorWithAlpha(color, alpha12) {
1256
1273
  const r = bigint >> 16 & 255;
1257
1274
  const g = bigint >> 8 & 255;
1258
1275
  const b = bigint & 255;
1259
- return `rgba(${r}, ${g}, ${b}, ${alpha12})`;
1276
+ return `rgba(${r}, ${g}, ${b}, ${alpha18})`;
1260
1277
  }
1261
1278
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1262
1279
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1263
1280
  if (result) {
1264
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha12})`;
1281
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha18})`;
1265
1282
  }
1266
1283
  }
1267
1284
  return color;
@@ -1423,6 +1440,7 @@ if (typeof window !== "undefined") {
1423
1440
  const msg = event.data;
1424
1441
  if (!msg || msg.action !== "UPDATE_DIRK_LIB_LOCALES") return;
1425
1442
  if (!msg.data || typeof msg.data !== "object") return;
1443
+ if (Object.keys(msg.data).length === 0) return;
1426
1444
  localeStore.setState({ locales: msg.data });
1427
1445
  });
1428
1446
  }
@@ -1949,15 +1967,21 @@ var useFrameworkGroups = create(() => ({
1949
1967
  gangs: [],
1950
1968
  loaded: false
1951
1969
  }));
1952
- registerInitialFetch("GET_FRAMEWORK_GROUPS", void 0).then((data) => {
1953
- useFrameworkGroups.setState({
1954
- jobs: Array.isArray(data?.jobs) ? data.jobs : [],
1955
- gangs: Array.isArray(data?.gangs) ? data.gangs : [],
1956
- loaded: true
1970
+ var frameworkGroupsRequested = false;
1971
+ function ensureFrameworkGroups() {
1972
+ if (frameworkGroupsRequested) return;
1973
+ frameworkGroupsRequested = true;
1974
+ fetchNui("GET_FRAMEWORK_GROUPS", void 0).then((data) => {
1975
+ useFrameworkGroups.setState({
1976
+ jobs: Array.isArray(data?.jobs) ? data.jobs : [],
1977
+ gangs: Array.isArray(data?.gangs) ? data.gangs : [],
1978
+ loaded: true
1979
+ });
1980
+ }).catch(() => {
1981
+ frameworkGroupsRequested = false;
1982
+ useFrameworkGroups.setState({ loaded: true });
1957
1983
  });
1958
- }).catch(() => {
1959
- useFrameworkGroups.setState({ loaded: true });
1960
- });
1984
+ }
1961
1985
  function selectAllGroups(state) {
1962
1986
  return [...state.jobs, ...state.gangs];
1963
1987
  }
@@ -2991,7 +3015,7 @@ function NavigationProvider({ children, defaultPage }) {
2991
3015
  }
2992
3016
  function NavBar(props) {
2993
3017
  const pageId = useNavigation((state) => state.pageId);
2994
- const store = useNavigationStore();
3018
+ const store2 = useNavigationStore();
2995
3019
  return /* @__PURE__ */ jsx(
2996
3020
  SegmentedControl,
2997
3021
  {
@@ -3000,7 +3024,7 @@ function NavBar(props) {
3000
3024
  value: pageId,
3001
3025
  items: props.items,
3002
3026
  onChange: (value) => {
3003
- store.setState({ pageId: value });
3027
+ store2.setState({ pageId: value });
3004
3028
  }
3005
3029
  }
3006
3030
  );
@@ -4307,29 +4331,29 @@ function FormProvider({
4307
4331
  return /* @__PURE__ */ jsx(FormContext.Provider, { value: storeRef.current, children });
4308
4332
  }
4309
4333
  function useForm() {
4310
- const store = useContext(FormContext);
4311
- if (!store) {
4334
+ const store2 = useContext(FormContext);
4335
+ if (!store2) {
4312
4336
  throw new Error("useForm must be used inside <FormProvider>");
4313
4337
  }
4314
- const state = useStore(store);
4338
+ const state = useStore(store2);
4315
4339
  const changedFields = useMemo(() => {
4316
4340
  return collectChangedPaths(state.values, state.initialValues);
4317
4341
  }, [state.values, state.initialValues]);
4318
4342
  return { ...state, changedFields, changedCount: changedFields.length };
4319
4343
  }
4320
4344
  function useFormField(path) {
4321
- const store = useContext(FormContext);
4322
- if (!store) {
4345
+ const store2 = useContext(FormContext);
4346
+ if (!store2) {
4323
4347
  throw new Error("useFormField must be used inside <FormProvider>");
4324
4348
  }
4325
- return useStore(store, (s) => getNested(s.values, path));
4349
+ return useStore(store2, (s) => getNested(s.values, path));
4326
4350
  }
4327
4351
  function useFormFields(...paths) {
4328
- const store = useContext(FormContext);
4329
- if (!store) {
4352
+ const store2 = useContext(FormContext);
4353
+ if (!store2) {
4330
4354
  throw new Error("useFormFields must be used inside <FormProvider>");
4331
4355
  }
4332
- return useStore(store, (s) => {
4356
+ return useStore(store2, (s) => {
4333
4357
  const result = {};
4334
4358
  for (const path of paths) {
4335
4359
  result[path] = getNested(s.values, path);
@@ -4338,18 +4362,18 @@ function useFormFields(...paths) {
4338
4362
  });
4339
4363
  }
4340
4364
  function useFormError(path) {
4341
- const store = useContext(FormContext);
4342
- if (!store) {
4365
+ const store2 = useContext(FormContext);
4366
+ if (!store2) {
4343
4367
  throw new Error("useFormError must be used inside <FormProvider>");
4344
4368
  }
4345
- return useStore(store, (s) => s.errors[path]);
4369
+ return useStore(store2, (s) => s.errors[path]);
4346
4370
  }
4347
4371
  function useFormErrors(...paths) {
4348
- const store = useContext(FormContext);
4349
- if (!store) {
4372
+ const store2 = useContext(FormContext);
4373
+ if (!store2) {
4350
4374
  throw new Error("useFormErrors must be used inside <FormProvider>");
4351
4375
  }
4352
- return useStore(store, (s) => {
4376
+ return useStore(store2, (s) => {
4353
4377
  const result = {};
4354
4378
  for (const path of paths) {
4355
4379
  result[path] = s.errors[path];
@@ -4358,12 +4382,223 @@ function useFormErrors(...paths) {
4358
4382
  });
4359
4383
  }
4360
4384
  function useFormActions() {
4361
- const store = useContext(FormContext);
4362
- if (!store) {
4385
+ const store2 = useContext(FormContext);
4386
+ if (!store2) {
4363
4387
  throw new Error("useFormActions must be used inside <FormProvider>");
4364
4388
  }
4365
- return store.getState();
4389
+ return store2.getState();
4390
+ }
4391
+ var store = /* @__PURE__ */ new Map();
4392
+ var listeners = /* @__PURE__ */ new Map();
4393
+ function notify(key) {
4394
+ const ls = listeners.get(key);
4395
+ if (!ls) return;
4396
+ ls.forEach((fn) => fn());
4397
+ }
4398
+ function useAdminState(key, initial) {
4399
+ const [, setTick] = useState(0);
4400
+ useEffect(() => {
4401
+ const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
4402
+ listeners.set(key, set);
4403
+ const handler = () => setTick((t3) => t3 + 1);
4404
+ set.add(handler);
4405
+ return () => {
4406
+ set.delete(handler);
4407
+ if (set.size === 0) listeners.delete(key);
4408
+ };
4409
+ }, [key]);
4410
+ const value = store.has(key) ? store.get(key) : initial;
4411
+ const setValue = (v) => {
4412
+ const next = typeof v === "function" ? v(value) : v;
4413
+ store.set(key, next);
4414
+ notify(key);
4415
+ };
4416
+ return [value, setValue];
4417
+ }
4418
+ function clearAdminState(key) {
4419
+ if (key) {
4420
+ store.delete(key);
4421
+ notify(key);
4422
+ } else {
4423
+ const keys = Array.from(store.keys());
4424
+ store.clear();
4425
+ keys.forEach(notify);
4426
+ }
4366
4427
  }
4428
+ var label = {
4429
+ fontSize: "var(--mantine-font-size-xs)",
4430
+ fontFamily: "Akrobat Bold",
4431
+ letterSpacing: "0.05em",
4432
+ textTransform: "uppercase"
4433
+ };
4434
+ var error = {
4435
+ fontSize: "var(--mantine-font-size-xs)",
4436
+ fontFamily: "Akrobat Regular"
4437
+ };
4438
+ var description = {
4439
+ fontSize: "var(--mantine-font-size-xs)"
4440
+ };
4441
+ var genericInputStyles = {
4442
+ styles: {
4443
+ label,
4444
+ error,
4445
+ description,
4446
+ input: {
4447
+ background: "rgba(255,255,255,0.04)",
4448
+ border: "0.1vh solid rgba(255,255,255,0.08)",
4449
+ color: "rgba(255,255,255,0.85)",
4450
+ minHeight: "4vh"
4451
+ }
4452
+ }
4453
+ };
4454
+ var theme = createTheme({
4455
+ primaryColor: "dirk",
4456
+ primaryShade: 9,
4457
+ defaultRadius: "xs",
4458
+ fontFamily: "Akrobat Regular, sans-serif",
4459
+ radius: {
4460
+ xxs: "0.3vh",
4461
+ xs: "0.5vh",
4462
+ sm: "0.75vh",
4463
+ md: "1vh",
4464
+ lg: "1.5vh",
4465
+ xl: "2vh",
4466
+ xxl: "3vh"
4467
+ },
4468
+ fontSizes: {
4469
+ xxs: "1.2vh",
4470
+ xs: "1.5vh",
4471
+ sm: "1.8vh",
4472
+ md: "2.2vh",
4473
+ lg: "2.8vh",
4474
+ xl: "3.3vh",
4475
+ xxl: "3.8vh"
4476
+ },
4477
+ lineHeights: {
4478
+ xxs: "1.4vh",
4479
+ xs: "1.8vh",
4480
+ sm: "2.2vh",
4481
+ md: "2.8vh",
4482
+ lg: "3.3vh",
4483
+ xl: "3.8vh"
4484
+ },
4485
+ spacing: {
4486
+ xxs: "0.5vh",
4487
+ xs: "0.75vh",
4488
+ sm: "1.5vh",
4489
+ md: "2vh",
4490
+ lg: "3vh",
4491
+ xl: "4vh",
4492
+ xxl: "5vh"
4493
+ },
4494
+ components: {
4495
+ Progress: {
4496
+ styles: {
4497
+ label: {
4498
+ fontFamily: "Akrobat Bold",
4499
+ letterSpacing: "0.05em",
4500
+ textTransform: "uppercase"
4501
+ },
4502
+ root: {
4503
+ backgroundColor: "rgba(77, 77, 77, 0.4)"
4504
+ }
4505
+ }
4506
+ },
4507
+ Input: genericInputStyles,
4508
+ TextInput: genericInputStyles,
4509
+ NumberInput: genericInputStyles,
4510
+ Select: genericInputStyles,
4511
+ MultiSelect: genericInputStyles,
4512
+ Textarea: genericInputStyles,
4513
+ ColorInput: genericInputStyles,
4514
+ DateInput: genericInputStyles,
4515
+ // Mantine's <Button> defaults to rem-based heights (xs ≈ 1.875rem)
4516
+ // which doesn't match this theme's vh-based input min-heights, so
4517
+ // `<Button size="xs">` rendered next to `<TextInput size="xs">` ends
4518
+ // up visibly shorter. Pin the button heights to the same vh values
4519
+ // the inputs use so xs-everything lines up out of the box.
4520
+ Button: {
4521
+ styles: {
4522
+ label: {
4523
+ fontFamily: "Akrobat Bold",
4524
+ letterSpacing: "0.05em",
4525
+ textTransform: "uppercase"
4526
+ },
4527
+ root: {
4528
+ // Mantine maps these to --button-height per size; setting them
4529
+ // directly here keeps native Button sizing logic intact.
4530
+ }
4531
+ },
4532
+ vars: (_theme, props) => {
4533
+ const heights = {
4534
+ xs: "4vh",
4535
+ sm: "4.5vh",
4536
+ md: "5vh",
4537
+ lg: "5.5vh",
4538
+ xl: "6vh"
4539
+ };
4540
+ const h = heights[props.size ?? "sm"] ?? "4.5vh";
4541
+ return {
4542
+ root: {
4543
+ "--button-height": h
4544
+ }
4545
+ };
4546
+ }
4547
+ },
4548
+ Pill: {
4549
+ styles: (theme2) => ({
4550
+ root: {
4551
+ display: "inline-flex",
4552
+ alignItems: "center",
4553
+ justifyContent: "space-between",
4554
+ backgroundColor: "rgba(76, 76, 76, 0.3)",
4555
+ height: "fit-content",
4556
+ textTransform: "uppercase",
4557
+ letterSpacing: "0.05em",
4558
+ fontFamily: "Akrobat Bold",
4559
+ fontSize: "1.25vh",
4560
+ borderRadius: theme2.defaultRadius,
4561
+ paddingBottom: "0.5vh",
4562
+ paddingTop: "0.5vh"
4563
+ }
4564
+ })
4565
+ },
4566
+ // Mantine's <Tooltip> defaults to a white card with black text — looks
4567
+ // jarring against every dirk consumer's dark configurator. Every script
4568
+ // used to hand-paste this dark style block per Tooltip; centralised here
4569
+ // so consumers get the right look automatically and never need to think
4570
+ // about it again.
4571
+ Tooltip: {
4572
+ styles: (theme2) => ({
4573
+ tooltip: {
4574
+ background: alpha(theme2.colors.dark[7], 0.95),
4575
+ border: "0.1vh solid rgba(255,255,255,0.1)",
4576
+ color: "rgba(255,255,255,0.75)",
4577
+ fontFamily: "Akrobat Bold",
4578
+ fontSize: "1.3vh",
4579
+ lineHeight: 1.3,
4580
+ padding: "0.6vh 0.8vh",
4581
+ letterSpacing: "0.03em"
4582
+ }
4583
+ })
4584
+ }
4585
+ },
4586
+ colors: {
4587
+ dirk: [
4588
+ "#ffffff",
4589
+ "#f3fce9",
4590
+ "#dbf5bd",
4591
+ "#c3ee91",
4592
+ "#ace765",
4593
+ "#94e039",
4594
+ "#7ac61f",
4595
+ "#5f9a18",
4596
+ "#29420a",
4597
+ "#446e11"
4598
+ ]
4599
+ }
4600
+ });
4601
+ var theme_default = theme;
4367
4602
  var useNuiEvent = (action, handler) => {
4368
4603
  const savedHandler = useRef(noop);
4369
4604
  useEffect(() => {
@@ -4382,13 +4617,35 @@ var useNuiEvent = (action, handler) => {
4382
4617
  return () => window.removeEventListener("message", eventListener);
4383
4618
  }, [action]);
4384
4619
  };
4620
+
4621
+ // src/utils/mergeMantineTheme.ts
4622
+ var isValidColorScale = (v) => Array.isArray(v) && v.length === 10 && v.every((shade) => typeof shade === "string");
4623
+ function mergeMantineThemeSafe(base, custom, override) {
4624
+ const colors = { ...base.colors };
4625
+ if (custom && isValidColorScale(custom)) {
4626
+ colors["custom"] = custom;
4627
+ } else if (!colors["custom"]) {
4628
+ const fallback = base.colors && base.colors.dirk;
4629
+ if (fallback && isValidColorScale(fallback)) {
4630
+ colors["custom"] = fallback;
4631
+ }
4632
+ }
4633
+ return {
4634
+ ...base,
4635
+ ...override,
4636
+ colors: {
4637
+ ...colors,
4638
+ ...override?.colors ?? {}
4639
+ }
4640
+ };
4641
+ }
4385
4642
  var _instance = null;
4386
4643
  function getScriptConfigInstance() {
4387
4644
  if (!_instance) throw new Error("[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel");
4388
4645
  return _instance;
4389
4646
  }
4390
4647
  function createScriptConfig(defaultValue) {
4391
- const store = create(() => defaultValue);
4648
+ const store2 = create(() => defaultValue);
4392
4649
  let clientVersion = 0;
4393
4650
  const useScriptConfigHooks = () => {
4394
4651
  useNuiEvent("UPDATE_SCRIPT_CONFIG", (data) => {
@@ -4397,7 +4654,7 @@ function createScriptConfig(defaultValue) {
4397
4654
  clientVersion = data.clientVersion;
4398
4655
  }
4399
4656
  if (data.config && typeof data.config === "object") {
4400
- store.setState((prev) => ({ ...prev, ...data.config }));
4657
+ store2.setState((prev) => ({ ...prev, ...data.config }));
4401
4658
  }
4402
4659
  });
4403
4660
  };
@@ -4405,7 +4662,7 @@ function createScriptConfig(defaultValue) {
4405
4662
  try {
4406
4663
  const response = await fetchNui("GET_FULL_SCRIPT_CONFIG");
4407
4664
  if (response?.success && response.data?.config) {
4408
- store.setState(() => response.data.config);
4665
+ store2.setState(() => response.data.config);
4409
4666
  if (typeof response.data.clientVersion === "number") {
4410
4667
  clientVersion = response.data.clientVersion;
4411
4668
  }
@@ -4416,7 +4673,7 @@ function createScriptConfig(defaultValue) {
4416
4673
  return null;
4417
4674
  };
4418
4675
  const updateScriptConfig = async (newConfig) => {
4419
- store.setState((prev) => ({ ...prev, ...newConfig }));
4676
+ store2.setState((prev) => ({ ...prev, ...newConfig }));
4420
4677
  const response = await fetchNui("UPDATE_SCRIPT_CONFIG", {
4421
4678
  data: newConfig,
4422
4679
  expectedVersion: clientVersion
@@ -4425,7 +4682,7 @@ function createScriptConfig(defaultValue) {
4425
4682
  clientVersion = response.meta.client_version;
4426
4683
  }
4427
4684
  if (response?.success === false && response?.meta?.latestData) {
4428
- store.setState((prev) => ({ ...prev, ...response.meta.latestData }));
4685
+ store2.setState((prev) => ({ ...prev, ...response.meta.latestData }));
4429
4686
  }
4430
4687
  return response;
4431
4688
  };
@@ -4437,27 +4694,250 @@ function createScriptConfig(defaultValue) {
4437
4694
  if (response?.success) {
4438
4695
  const fresh = await fetchScriptConfig();
4439
4696
  if (fresh) {
4440
- store.setState(() => fresh);
4697
+ store2.setState(() => fresh);
4441
4698
  }
4442
4699
  }
4443
4700
  return response;
4444
4701
  };
4445
4702
  _instance = {
4446
- store,
4703
+ store: store2,
4447
4704
  updateConfig: updateScriptConfig,
4448
4705
  resetConfig,
4449
4706
  getHistory: getScriptConfigHistory,
4450
4707
  fetchConfig: fetchScriptConfig
4451
4708
  };
4452
- return { store, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig };
4709
+ return { store: store2, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig };
4710
+ }
4711
+ var DirkErrorBoundary = class extends React4.Component {
4712
+ constructor() {
4713
+ super(...arguments);
4714
+ __publicField(this, "state", { error: null, stack: void 0 });
4715
+ }
4716
+ static getDerivedStateFromError(error2) {
4717
+ return { error: error2 };
4718
+ }
4719
+ componentDidCatch(error2, info) {
4720
+ console.group("\u{1F525} Dirk UI Crash");
4721
+ console.error("Error:", error2);
4722
+ console.error("Component Stack:", info.componentStack);
4723
+ console.groupEnd();
4724
+ }
4725
+ render() {
4726
+ if (!this.state.error) return this.props.children;
4727
+ return /* @__PURE__ */ jsx(
4728
+ Box,
4729
+ {
4730
+ style: {
4731
+ position: "fixed",
4732
+ inset: 0,
4733
+ width: "100vw",
4734
+ height: "100vh",
4735
+ background: "rgba(10, 10, 12, 0.92)",
4736
+ display: "flex",
4737
+ alignItems: "center",
4738
+ justifyContent: "center",
4739
+ padding: "2rem",
4740
+ zIndex: 999999
4741
+ },
4742
+ children: /* @__PURE__ */ jsx(
4743
+ Box,
4744
+ {
4745
+ maw: 900,
4746
+ w: "100%",
4747
+ p: "lg",
4748
+ style: {
4749
+ background: "rgba(20,20,24,0.75)",
4750
+ border: "1px solid rgba(255,255,255,0.08)",
4751
+ borderRadius: "10px",
4752
+ boxShadow: "0 10px 40px rgba(0,0,0,0.6)"
4753
+ },
4754
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
4755
+ /* @__PURE__ */ jsx(Title$1, { order: 2, c: "red.5", children: "Dirk UI Crash" }),
4756
+ /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "The interface encountered a fatal error and stopped rendering." }),
4757
+ /* @__PURE__ */ jsx(Code, { block: true, style: { maxHeight: 150, overflow: "auto" }, children: this.state.error?.message }),
4758
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Check console for full stack trace" })
4759
+ ] })
4760
+ }
4761
+ )
4762
+ }
4763
+ );
4764
+ }
4765
+ };
4766
+ var useAdminToolStore = create((set, get) => ({
4767
+ active: null,
4768
+ begin: (spec) => new Promise((resolve) => {
4769
+ const prev = get().active;
4770
+ if (prev) prev.resolve(null);
4771
+ set({ active: { ...spec, resolve } });
4772
+ }),
4773
+ resolveActive: (value) => {
4774
+ const cur = get().active;
4775
+ if (!cur) return;
4776
+ cur.resolve(value);
4777
+ set({ active: null });
4778
+ },
4779
+ cancelActive: () => {
4780
+ const cur = get().active;
4781
+ if (!cur) return;
4782
+ cur.resolve(null);
4783
+ set({ active: null });
4784
+ }
4785
+ }));
4786
+
4787
+ // src/components/AdminTools/AdminOverlays.tsx
4788
+ var listenerInstalled = false;
4789
+ function installNuiListener() {
4790
+ if (listenerInstalled || typeof window === "undefined") return;
4791
+ listenerInstalled = true;
4792
+ window.addEventListener("message", (e) => {
4793
+ const msg = e?.data;
4794
+ if (!msg || typeof msg !== "object" || typeof msg.action !== "string") return;
4795
+ const action = msg.action;
4796
+ const cur = useAdminToolStore.getState().active;
4797
+ if (!cur) return;
4798
+ if (action === `${cur.id}_RESULT`) {
4799
+ useAdminToolStore.getState().resolveActive(msg.data ?? null);
4800
+ } else if (action === `${cur.id}_CANCELLED`) {
4801
+ useAdminToolStore.getState().cancelActive();
4802
+ }
4803
+ });
4804
+ }
4805
+ var BODY_HIDE_STYLE_ID = "dirk-instruction-panel-style";
4806
+ var BODY_HIDE_ATTR = "data-dirk-instruction-active";
4807
+ var OVERLAY_ATTR = "data-dirk-instruction-overlay";
4808
+ function ensureBodyHideStyle() {
4809
+ if (typeof document === "undefined") return;
4810
+ if (document.getElementById(BODY_HIDE_STYLE_ID)) return;
4811
+ const el = document.createElement("style");
4812
+ el.id = BODY_HIDE_STYLE_ID;
4813
+ el.textContent = `
4814
+ body[${BODY_HIDE_ATTR}] > *:not([${OVERLAY_ATTR}]) {
4815
+ visibility: hidden !important;
4816
+ opacity: 0 !important;
4817
+ pointer-events: none !important;
4818
+ }
4819
+ `;
4820
+ document.head.appendChild(el);
4821
+ }
4822
+ function AdminOverlays() {
4823
+ const active = useAdminToolStore((s) => s.active);
4824
+ useEffect(() => {
4825
+ installNuiListener();
4826
+ }, []);
4827
+ useEffect(() => {
4828
+ if (!active || typeof document === "undefined") return;
4829
+ ensureBodyHideStyle();
4830
+ document.body.setAttribute(BODY_HIDE_ATTR, "");
4831
+ return () => {
4832
+ document.body.removeAttribute(BODY_HIDE_ATTR);
4833
+ };
4834
+ }, [active]);
4835
+ return null;
4453
4836
  }
4454
- var configPanelQueryClient = new QueryClient({
4837
+ library.add(fas, far, fab);
4838
+ var dirkQueryClient = new QueryClient({
4455
4839
  defaultOptions: { queries: { staleTime: 3e4, gcTime: 5 * 6e4 } }
4456
4840
  });
4457
- function NavItemButton({
4458
- icon: Icon,
4459
- label: label2,
4460
- active,
4841
+ function DirkProvider({ children, overideResourceName, themeOverride }) {
4842
+ const {
4843
+ primaryColor,
4844
+ primaryShade,
4845
+ customTheme,
4846
+ game
4847
+ } = useSettings();
4848
+ localeStore((s) => s.locales);
4849
+ const [scTheme, setScTheme] = useState(null);
4850
+ useLayoutEffect(() => {
4851
+ useSettings.setState({
4852
+ overideResourceName
4853
+ });
4854
+ }, [overideResourceName]);
4855
+ useEffect(() => {
4856
+ fetchNui("NUI_READY").catch(() => {
4857
+ });
4858
+ Promise.all([
4859
+ fetchNui("GET_SETTINGS"),
4860
+ fetchNui("GET_RESOURCE_VERSION", void 0, { version: "dev" })
4861
+ ]).then(([data, resourceInfo]) => {
4862
+ useSettings.setState({
4863
+ ...data,
4864
+ resourceVersion: resourceInfo?.version || "dev"
4865
+ });
4866
+ }).catch((err) => {
4867
+ console.error("Failed to fetch initial settings within dirk-cfx-react:", err);
4868
+ });
4869
+ }, []);
4870
+ useNuiEvent("UPDATE_DIRK_LIB_SETTINGS", (data) => {
4871
+ if (!data || typeof data !== "object") return;
4872
+ useSettings.setState(data);
4873
+ });
4874
+ useNuiEvent(
4875
+ "UPDATE_SCRIPT_CONFIG",
4876
+ (data) => {
4877
+ if (!data || !data.config || typeof data.config !== "object") return;
4878
+ try {
4879
+ const inst = getScriptConfigInstance();
4880
+ inst.store.setState((prev) => ({ ...prev, ...data.config }));
4881
+ } catch {
4882
+ }
4883
+ }
4884
+ );
4885
+ useEffect(() => {
4886
+ let unsubscribe;
4887
+ try {
4888
+ const inst = getScriptConfigInstance();
4889
+ setScTheme(inst.store.getState()?.theme ?? null);
4890
+ const subscribable = inst.store;
4891
+ if (typeof subscribable.subscribe === "function") {
4892
+ unsubscribe = subscribable.subscribe((s) => {
4893
+ setScTheme(s?.theme ?? null);
4894
+ });
4895
+ }
4896
+ inst.fetchConfig?.().then((full) => {
4897
+ if (full && typeof full === "object") {
4898
+ setScTheme(full.theme ?? null);
4899
+ }
4900
+ }).catch(() => {
4901
+ });
4902
+ } catch {
4903
+ }
4904
+ return () => {
4905
+ unsubscribe?.();
4906
+ };
4907
+ }, []);
4908
+ const overrideActive = scTheme?.useOverride === true;
4909
+ const effectivePrimaryColor = overrideActive ? scTheme.primaryColor ?? primaryColor : primaryColor;
4910
+ const effectivePrimaryShade = overrideActive ? scTheme.primaryShade ?? primaryShade : primaryShade;
4911
+ const effectiveCustomTheme = overrideActive ? scTheme.customTheme ?? customTheme : customTheme;
4912
+ const mergedTheme = useMemo(
4913
+ () => mergeMantineThemeSafe(
4914
+ { ...theme_default, primaryColor: effectivePrimaryColor, primaryShade: effectivePrimaryShade },
4915
+ effectiveCustomTheme,
4916
+ themeOverride
4917
+ ),
4918
+ [effectivePrimaryColor, effectivePrimaryShade, effectiveCustomTheme, themeOverride]
4919
+ );
4920
+ useEffect(() => {
4921
+ document.body.style.fontFamily = game === "rdr3" ? '"Red Dead", sans-serif' : '"Akrobat Regular", sans-serif';
4922
+ }, [game]);
4923
+ const content = isEnvBrowser() ? /* @__PURE__ */ jsx(
4924
+ BackgroundImage,
4925
+ {
4926
+ w: "100vw",
4927
+ h: "100vh",
4928
+ src: game === "fivem" ? "https://i.ytimg.com/vi/TOxuNbXrO28/maxresdefault.jpg" : "https://raw.githubusercontent.com/Jump-On-Studios/RedM-jo_libs/refs/heads/main/source-repositories/Menu/public/assets/images/background_dev.jpg",
4929
+ children
4930
+ }
4931
+ ) : children;
4932
+ return /* @__PURE__ */ jsx(QueryClientProvider, { client: dirkQueryClient, children: /* @__PURE__ */ jsx(MantineProvider, { theme: mergedTheme, defaultColorScheme: "dark", children: /* @__PURE__ */ jsxs(DirkErrorBoundary, { children: [
4933
+ content,
4934
+ /* @__PURE__ */ jsx(AdminOverlays, {})
4935
+ ] }) }) });
4936
+ }
4937
+ function NavItemButton({
4938
+ icon: Icon,
4939
+ label: label2,
4940
+ active,
4461
4941
  onClick
4462
4942
  }) {
4463
4943
  const theme2 = useMantineTheme();
@@ -4782,7 +5262,7 @@ function ConfigPanelInner({
4782
5262
  const theme2 = useMantineTheme();
4783
5263
  const color = theme2.colors[theme2.primaryColor][5];
4784
5264
  const version = useSettings((s) => s.resourceVersion);
4785
- const [activeTab, setActiveTab] = useState(navItems[0]?.id ?? "");
5265
+ const [activeTab, setActiveTab] = useAdminState("__dirkConfigPanel:activeTab", navItems[0]?.id ?? "");
4786
5266
  const firstMountRef = useRef(true);
4787
5267
  const [jsonOpen, setJsonOpen] = useState(false);
4788
5268
  const [historyOpen, setHistoryOpen] = useState(false);
@@ -4825,8 +5305,8 @@ function ConfigPanelInner({
4825
5305
  setResetOpen(false);
4826
5306
  const result = await resetConfig();
4827
5307
  if (result?.success) {
4828
- const { store } = getScriptConfigInstance();
4829
- form.reinitialize(cloneConfig(store.getState()));
5308
+ const { store: store2 } = getScriptConfigInstance();
5309
+ form.reinitialize(cloneConfig(store2.getState()));
4830
5310
  notifications.show({
4831
5311
  color: "green",
4832
5312
  title: locale("ConfigResetSuccessTitle"),
@@ -5054,13 +5534,13 @@ function ServerOnlyFetcher() {
5054
5534
  var defaultOnClose = () => fetchNui("CLOSE_ADMIN_SECTION");
5055
5535
  function ConfigPanel(props) {
5056
5536
  const { open, onClose = defaultOnClose } = props;
5057
- const { store, updateConfig } = getScriptConfigInstance();
5537
+ const { store: store2, updateConfig } = getScriptConfigInstance();
5058
5538
  const [isSaving, setIsSaving] = useState(false);
5059
5539
  if (!open) return null;
5060
- return /* @__PURE__ */ jsx(QueryClientProvider, { client: configPanelQueryClient, children: /* @__PURE__ */ jsxs(
5540
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(
5061
5541
  FormProvider,
5062
5542
  {
5063
- initialValues: cloneConfig(store.getState()),
5543
+ initialValues: cloneConfig(store2.getState()),
5064
5544
  onSubmit: async (form) => {
5065
5545
  if (isSaving) return;
5066
5546
  setIsSaving(true);
@@ -5068,7 +5548,7 @@ function ConfigPanel(props) {
5068
5548
  const result = await updateConfig(form.values);
5069
5549
  if (result?.success) {
5070
5550
  form.reinitialize(cloneConfig(form.values));
5071
- configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
5551
+ dirkQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
5072
5552
  useMissingItemsAudit.getState().refresh();
5073
5553
  notifications.show({
5074
5554
  color: "green",
@@ -5078,7 +5558,7 @@ function ConfigPanel(props) {
5078
5558
  });
5079
5559
  return;
5080
5560
  }
5081
- form.reinitialize(cloneConfig(store.getState()));
5561
+ form.reinitialize(cloneConfig(store2.getState()));
5082
5562
  const err = result?._error || "Unknown";
5083
5563
  console.warn(`[ConfigPanel] config save failed: ${err}`);
5084
5564
  const titleKey = err === "NoPermission" ? "ConfigSaveNoPermissionTitle" : err === "VersionConflict" ? "ConfigSaveVersionConflictTitle" : err === "NotReady" ? "ConfigSaveNotReadyTitle" : "ConfigSaveFailedTitle";
@@ -5575,6 +6055,9 @@ function GroupName(props) {
5575
6055
  const ctx = useContext(GroupSelectContext);
5576
6056
  const jobs = useFrameworkGroups((s) => s.jobs);
5577
6057
  const gangs = useFrameworkGroups((s) => s.gangs);
6058
+ useEffect(() => {
6059
+ ensureFrameworkGroups();
6060
+ }, []);
5578
6061
  const inCompound = ctx !== null;
5579
6062
  const currentValue = inCompound ? ctx.value.name : props.value;
5580
6063
  const filterType = inCompound ? ctx.type : props.type;
@@ -5614,6 +6097,9 @@ function GroupRank(props) {
5614
6097
  }
5615
6098
  const jobs = useFrameworkGroups((s) => s.jobs);
5616
6099
  const gangs = useFrameworkGroups((s) => s.gangs);
6100
+ useEffect(() => {
6101
+ ensureFrameworkGroups();
6102
+ }, []);
5617
6103
  const all = [...jobs, ...gangs];
5618
6104
  const selectedGroup = all.find((g) => g.name === ctx.value.name) ?? null;
5619
6105
  const grades = selectedGroup?.grades ?? [];
@@ -7259,349 +7745,831 @@ function AccountSelect(props) {
7259
7745
  !hideFrameworkHint && /* @__PURE__ */ jsx(FrameworkHint, { framework })
7260
7746
  ] });
7261
7747
  }
7262
- function useTornEdges() {
7263
- const game = useSettings((state) => state.game);
7264
- return game === "rdr3" ? "torn-edge-wrapper" : "";
7748
+ var BODY_HIDE_STYLE_ID2 = "dirk-instruction-panel-style";
7749
+ var BODY_HIDE_ATTR2 = "data-dirk-instruction-active";
7750
+ var OVERLAY_ATTR2 = "data-dirk-instruction-overlay";
7751
+ function ensureBodyHideStyle2() {
7752
+ if (document.getElementById(BODY_HIDE_STYLE_ID2)) return;
7753
+ const el = document.createElement("style");
7754
+ el.id = BODY_HIDE_STYLE_ID2;
7755
+ el.textContent = `
7756
+ body[${BODY_HIDE_ATTR2}] > *:not([${OVERLAY_ATTR2}]) {
7757
+ visibility: hidden !important;
7758
+ opacity: 0 !important;
7759
+ pointer-events: none !important;
7760
+ }
7761
+ `;
7762
+ document.head.appendChild(el);
7265
7763
  }
7266
- function TornEdgeSVGFilter() {
7764
+ function InstructionPanel({
7765
+ visible,
7766
+ title,
7767
+ hint,
7768
+ keys,
7769
+ icon: Icon = MapPin,
7770
+ hideRestOfAdmin = true
7771
+ }) {
7772
+ const theme2 = useMantineTheme();
7773
+ const pc = theme2.colors[theme2.primaryColor];
7774
+ useEffect(() => {
7775
+ if (!visible || !hideRestOfAdmin) return;
7776
+ ensureBodyHideStyle2();
7777
+ document.body.setAttribute(BODY_HIDE_ATTR2, "");
7778
+ return () => {
7779
+ document.body.removeAttribute(BODY_HIDE_ATTR2);
7780
+ };
7781
+ }, [visible, hideRestOfAdmin]);
7782
+ if (!visible) return null;
7783
+ return createPortal(
7784
+ /* @__PURE__ */ jsx(
7785
+ "div",
7786
+ {
7787
+ ...{ [OVERLAY_ATTR2]: "" },
7788
+ style: { position: "fixed", inset: 0, pointerEvents: "none", zIndex: 1e4 },
7789
+ children: /* @__PURE__ */ jsx(AnimatePresence, { children: /* @__PURE__ */ jsx(
7790
+ motion.div,
7791
+ {
7792
+ initial: { opacity: 0, y: 12, scale: 0.92 },
7793
+ animate: { opacity: 1, y: 0, scale: 1 },
7794
+ exit: { opacity: 0, y: 12, scale: 0.92 },
7795
+ transition: { duration: 0.25, ease: "easeInOut" },
7796
+ style: { position: "absolute", bottom: "3vh", right: "3vh", userSelect: "none" },
7797
+ children: /* @__PURE__ */ jsxs(
7798
+ Flex,
7799
+ {
7800
+ direction: "column",
7801
+ gap: "0.8vh",
7802
+ style: {
7803
+ background: alpha(theme2.colors.dark[9], 0.55),
7804
+ border: "0.1vh solid rgba(255,255,255,0.07)",
7805
+ borderRadius: theme2.radius.sm,
7806
+ boxShadow: "0 0.74vh 2.96vh rgba(0,0,0,0.5)",
7807
+ padding: "1.4vh 1.6vh",
7808
+ minWidth: "22vh",
7809
+ maxWidth: "28vh"
7810
+ },
7811
+ children: [
7812
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "0.6vh", children: [
7813
+ /* @__PURE__ */ jsx(Icon, { size: "1.6vh", color: pc[6], strokeWidth: 2.5 }),
7814
+ /* @__PURE__ */ jsx(
7815
+ Text,
7816
+ {
7817
+ style: {
7818
+ fontFamily: "Akrobat Bold, sans-serif",
7819
+ fontSize: "1.7vh",
7820
+ fontWeight: 700,
7821
+ letterSpacing: "0.14em",
7822
+ textTransform: "uppercase",
7823
+ color: pc[6],
7824
+ textShadow: `0 0 0.8vh ${alpha(pc[7], 0.5)}, 0 0 1.6vh ${alpha(pc[9], 0.3)}`
7825
+ },
7826
+ children: title
7827
+ }
7828
+ )
7829
+ ] }),
7830
+ (hint || keys && keys.length > 0) && /* @__PURE__ */ jsx("div", { style: { height: "0.1vh", background: "rgba(255,255,255,0.06)", margin: "0.1vh 0" } }),
7831
+ hint && /* @__PURE__ */ jsx(
7832
+ Text,
7833
+ {
7834
+ style: {
7835
+ fontFamily: "Akrobat Bold, sans-serif",
7836
+ fontSize: "1.05vh",
7837
+ color: "rgba(255,255,255,0.45)",
7838
+ letterSpacing: "0.06em",
7839
+ textTransform: "uppercase",
7840
+ lineHeight: 1.4
7841
+ },
7842
+ children: hint
7843
+ }
7844
+ ),
7845
+ keys && keys.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
7846
+ hint && /* @__PURE__ */ jsx("div", { style: { height: "0.1vh", background: "rgba(255,255,255,0.06)", margin: "0.1vh 0" } }),
7847
+ /* @__PURE__ */ jsx(Flex, { direction: "column", gap: "0.5vh", children: keys.map((k, i) => /* @__PURE__ */ jsx(InstructionKeyRow, { keyLabel: k.key, action: k.action }, i)) })
7848
+ ] })
7849
+ ]
7850
+ }
7851
+ )
7852
+ },
7853
+ "instruction-card"
7854
+ ) })
7855
+ }
7856
+ ),
7857
+ document.body
7858
+ );
7859
+ }
7860
+ function renderKeyContent(keyLabel) {
7861
+ const normalized = keyLabel.trim().toUpperCase();
7862
+ if (normalized === "LMB") return /* @__PURE__ */ jsx(MouseIcon, { side: "left" });
7863
+ if (normalized === "RMB") return /* @__PURE__ */ jsx(MouseIcon, { side: "right" });
7864
+ if (normalized === "BACKSPACE" || keyLabel === "\u232B") return /* @__PURE__ */ jsx(BackspaceIcon, {});
7267
7865
  return /* @__PURE__ */ jsx(
7268
- "svg",
7866
+ Text,
7269
7867
  {
7270
- style: { position: "absolute", width: 0, height: 0, pointerEvents: "none" },
7271
- "aria-hidden": "true",
7272
- children: /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("filter", { id: "torn-edge-filter", x: "-50%", y: "-50%", width: "200%", height: "200%", children: [
7273
- /* @__PURE__ */ jsx(
7274
- "feTurbulence",
7275
- {
7276
- type: "fractalNoise",
7277
- baseFrequency: "0.018 0.022",
7278
- numOctaves: "5",
7279
- seed: "9",
7280
- result: "noise1"
7281
- }
7282
- ),
7283
- /* @__PURE__ */ jsx(
7284
- "feTurbulence",
7285
- {
7286
- type: "fractalNoise",
7287
- baseFrequency: "0.08 0.12",
7288
- numOctaves: "2",
7289
- seed: "3",
7290
- result: "noise2"
7291
- }
7292
- ),
7293
- /* @__PURE__ */ jsx("feBlend", { in: "noise1", in2: "noise2", mode: "multiply", result: "combinedNoise" }),
7294
- /* @__PURE__ */ jsx(
7295
- "feDisplacementMap",
7296
- {
7297
- in: "SourceGraphic",
7298
- in2: "combinedNoise",
7299
- scale: "52",
7300
- xChannelSelector: "R",
7301
- yChannelSelector: "G",
7302
- result: "displaced"
7303
- }
7304
- ),
7305
- /* @__PURE__ */ jsx("feGaussianBlur", { stdDeviation: "0.8", in: "displaced", result: "blurred" }),
7306
- /* @__PURE__ */ jsx("feComponentTransfer", { in: "blurred", result: "alphaFade", children: /* @__PURE__ */ jsx("feFuncA", { type: "gamma", amplitude: "1", exponent: "1.3", offset: "-0.05" }) }),
7307
- /* @__PURE__ */ jsx("feMorphology", { operator: "erode", radius: "0.4", in: "alphaFade", result: "eroded" }),
7308
- /* @__PURE__ */ jsx("feMerge", { children: /* @__PURE__ */ jsx("feMergeNode", { in: "eroded" }) })
7309
- ] }) })
7868
+ style: {
7869
+ fontFamily: "Akrobat Bold, sans-serif",
7870
+ fontSize: "1.2vh",
7871
+ color: "rgba(255,255,255,0.85)",
7872
+ lineHeight: 1,
7873
+ padding: "0 0.3vh"
7874
+ },
7875
+ children: keyLabel
7310
7876
  }
7311
7877
  );
7312
7878
  }
7313
- var label = {
7314
- fontSize: "var(--mantine-font-size-xs)",
7315
- fontFamily: "Akrobat Bold",
7316
- letterSpacing: "0.05em",
7317
- textTransform: "uppercase"
7318
- };
7319
- var error = {
7320
- fontSize: "var(--mantine-font-size-xs)",
7321
- fontFamily: "Akrobat Regular"
7322
- };
7323
- var description = {
7324
- fontSize: "var(--mantine-font-size-xs)"
7325
- };
7326
- var genericInputStyles = {
7327
- styles: {
7328
- label,
7329
- error,
7330
- description,
7331
- input: {
7332
- background: "rgba(255,255,255,0.04)",
7333
- border: "0.1vh solid rgba(255,255,255,0.08)",
7334
- color: "rgba(255,255,255,0.85)",
7335
- minHeight: "4vh"
7336
- }
7337
- }
7338
- };
7339
- var theme = createTheme({
7340
- primaryColor: "dirk",
7341
- primaryShade: 9,
7342
- defaultRadius: "xs",
7343
- fontFamily: "Akrobat Regular, sans-serif",
7344
- radius: {
7345
- xxs: "0.3vh",
7346
- xs: "0.5vh",
7347
- sm: "0.75vh",
7348
- md: "1vh",
7349
- lg: "1.5vh",
7350
- xl: "2vh",
7351
- xxl: "3vh"
7352
- },
7353
- fontSizes: {
7354
- xxs: "1.2vh",
7355
- xs: "1.5vh",
7356
- sm: "1.8vh",
7357
- md: "2.2vh",
7358
- lg: "2.8vh",
7359
- xl: "3.3vh",
7360
- xxl: "3.8vh"
7361
- },
7362
- lineHeights: {
7363
- xxs: "1.4vh",
7364
- xs: "1.8vh",
7365
- sm: "2.2vh",
7366
- md: "2.8vh",
7367
- lg: "3.3vh",
7368
- xl: "3.8vh"
7369
- },
7370
- spacing: {
7371
- xxs: "0.5vh",
7372
- xs: "0.75vh",
7373
- sm: "1.5vh",
7374
- md: "2vh",
7375
- lg: "3vh",
7376
- xl: "4vh",
7377
- xxl: "5vh"
7378
- },
7379
- components: {
7380
- Progress: {
7381
- styles: {
7382
- label: {
7383
- fontFamily: "Akrobat Bold",
7384
- letterSpacing: "0.05em",
7879
+ function InstructionKeyRow({ keyLabel, action }) {
7880
+ const normalized = keyLabel.trim().toUpperCase();
7881
+ const isIconKey = normalized === "LMB" || normalized === "RMB" || normalized === "BACKSPACE" || keyLabel === "\u232B";
7882
+ const minWidth = isIconKey ? "2.6vh" : "2.4vh";
7883
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "0.6vh", children: [
7884
+ /* @__PURE__ */ jsx(
7885
+ "div",
7886
+ {
7887
+ style: {
7888
+ minWidth,
7889
+ height: "2.4vh",
7890
+ padding: "0 0.4vh",
7891
+ borderRadius: "0.3vh",
7892
+ border: "0.15vh solid rgba(255,255,255,0.35)",
7893
+ background: "rgba(255,255,255,0.06)",
7894
+ display: "flex",
7895
+ alignItems: "center",
7896
+ justifyContent: "center",
7897
+ opacity: 0.85,
7898
+ filter: "drop-shadow(0 0 0.3vh rgba(0,0,0,0.5))",
7899
+ flexShrink: 0,
7900
+ boxSizing: "border-box"
7901
+ },
7902
+ children: renderKeyContent(keyLabel)
7903
+ }
7904
+ ),
7905
+ /* @__PURE__ */ jsx(
7906
+ Text,
7907
+ {
7908
+ style: {
7909
+ fontFamily: "Akrobat Bold, sans-serif",
7910
+ fontSize: "1.05vh",
7911
+ color: "rgba(255,255,255,0.45)",
7912
+ letterSpacing: "0.06em",
7385
7913
  textTransform: "uppercase"
7386
7914
  },
7387
- root: {
7388
- backgroundColor: "rgba(77, 77, 77, 0.4)"
7389
- }
7915
+ children: action
7390
7916
  }
7391
- },
7392
- Input: genericInputStyles,
7393
- TextInput: genericInputStyles,
7394
- NumberInput: genericInputStyles,
7395
- Select: genericInputStyles,
7396
- MultiSelect: genericInputStyles,
7397
- Textarea: genericInputStyles,
7398
- ColorInput: genericInputStyles,
7399
- DateInput: genericInputStyles,
7400
- Pill: {
7401
- styles: (theme2) => ({
7402
- root: {
7403
- display: "inline-flex",
7404
- alignItems: "center",
7405
- justifyContent: "space-between",
7406
- backgroundColor: "rgba(76, 76, 76, 0.3)",
7407
- height: "fit-content",
7408
- textTransform: "uppercase",
7409
- letterSpacing: "0.05em",
7410
- fontFamily: "Akrobat Bold",
7411
- fontSize: "1.25vh",
7412
- borderRadius: theme2.defaultRadius,
7413
- paddingBottom: "0.5vh",
7414
- paddingTop: "0.5vh"
7415
- }
7416
- })
7917
+ )
7918
+ ] });
7919
+ }
7920
+ function MouseIcon({ side }) {
7921
+ const stroke = "rgba(255,255,255,0.85)";
7922
+ const fillActive = "rgba(255,255,255,0.85)";
7923
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 22", width: "1.4vh", height: "1.9vh", fill: "none", stroke, strokeWidth: "1.4", strokeLinejoin: "round", children: [
7924
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "14", height: "20", rx: "6" }),
7925
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "1", x2: "8", y2: "9" }),
7926
+ /* @__PURE__ */ jsx(
7927
+ "path",
7928
+ {
7929
+ d: side === "left" ? "M 7.4 1.6 L 2 1.6 A 5 5 0 0 0 1 6 L 1 9 L 7.4 9 Z" : "M 8.6 1.6 L 14 1.6 A 5 5 0 0 1 15 6 L 15 9 L 8.6 9 Z",
7930
+ fill: fillActive,
7931
+ stroke: "none"
7932
+ }
7933
+ )
7934
+ ] });
7935
+ }
7936
+ function BackspaceIcon() {
7937
+ const stroke = "rgba(255,255,255,0.85)";
7938
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 22 16", width: "1.7vh", height: "1.3vh", fill: "none", stroke, strokeWidth: "1.4", strokeLinejoin: "round", strokeLinecap: "round", children: [
7939
+ /* @__PURE__ */ jsx("path", { d: "M 21 2 L 8 2 L 2 8 L 8 14 L 21 14 Z" }),
7940
+ /* @__PURE__ */ jsx("line", { x1: "11", y1: "6", x2: "16", y2: "11" }),
7941
+ /* @__PURE__ */ jsx("line", { x1: "16", y1: "6", x2: "11", y2: "11" })
7942
+ ] });
7943
+ }
7944
+ function WorldPositionPicker({ value, onChange, compact, setOnly, gotoOnly }) {
7945
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
7946
+ !gotoOnly && /* @__PURE__ */ jsx(WorldPositionSetButton2, { value, onChange, compact }),
7947
+ !setOnly && /* @__PURE__ */ jsx(WorldPositionGotoButton2, { value, compact })
7948
+ ] });
7949
+ }
7950
+ function WorldPositionSetButton2({
7951
+ value,
7952
+ onChange,
7953
+ compact
7954
+ }) {
7955
+ const theme2 = useMantineTheme();
7956
+ const color = theme2.colors[theme2.primaryColor][5];
7957
+ const begin = useAdminToolStore((s) => s.begin);
7958
+ const onClick = async () => {
7959
+ const instructions = {
7960
+ title: locale("PickPositionTitle") || "Pick Position",
7961
+ hint: locale("PickPositionHint") || "Walk to where you want this set",
7962
+ keys: [
7963
+ { key: "E", action: locale("Set") || "Set" },
7964
+ { key: "\u232B", action: locale("Cancel") || "Cancel" }
7965
+ ]
7966
+ };
7967
+ const pendingResult = begin({ id: "capturePosition", ...instructions });
7968
+ fetchNui("ADMIN_TOOL_BEGIN", { id: "capturePosition", instructions }).catch(() => {
7969
+ useAdminToolStore.getState().cancelActive();
7970
+ });
7971
+ const result = await pendingResult;
7972
+ if (result) onChange(result);
7973
+ };
7974
+ return /* @__PURE__ */ jsx(Tooltip, { label: locale("SetWorldTooltip"), position: "top", withArrow: true, withinPortal: true, zIndex: 2e3, children: /* @__PURE__ */ jsxs(
7975
+ motion.button,
7976
+ {
7977
+ onClick,
7978
+ whileHover: { background: alpha(color, 0.18) },
7979
+ whileTap: { scale: 0.95 },
7980
+ style: {
7981
+ background: alpha(color, 0.1),
7982
+ border: `0.1vh solid ${alpha(color, 0.35)}`,
7983
+ borderRadius: theme2.radius.xs,
7984
+ padding: compact ? "0.25vh 0.6vh" : "0.5vh 0.8vh",
7985
+ cursor: "pointer",
7986
+ display: "flex",
7987
+ alignItems: "center",
7988
+ gap: compact ? "0.3vh" : "0.4vh"
7989
+ },
7990
+ children: [
7991
+ /* @__PURE__ */ jsx(Crosshair, { size: compact ? "1.1vh" : "1.3vh", color }),
7992
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: locale("Set") })
7993
+ ]
7417
7994
  }
7418
- },
7419
- colors: {
7420
- dirk: [
7421
- "#ffffff",
7422
- "#f3fce9",
7423
- "#dbf5bd",
7424
- "#c3ee91",
7425
- "#ace765",
7426
- "#94e039",
7427
- "#7ac61f",
7428
- "#5f9a18",
7429
- "#29420a",
7430
- "#446e11"
7431
- ]
7432
- }
7433
- });
7434
- var theme_default = theme;
7435
-
7436
- // src/utils/mergeMantineTheme.ts
7437
- var isValidColorScale = (v) => Array.isArray(v) && v.length === 10 && v.every((shade) => typeof shade === "string");
7438
- function mergeMantineThemeSafe(base, custom, override) {
7439
- const colors = { ...base.colors };
7440
- if (custom && isValidColorScale(custom)) {
7441
- colors["custom"] = custom;
7442
- } else if (!colors["custom"]) {
7443
- const fallback = base.colors && base.colors.dirk;
7444
- if (fallback && isValidColorScale(fallback)) {
7445
- colors["custom"] = fallback;
7995
+ ) });
7996
+ }
7997
+ function WorldPositionGotoButton2({
7998
+ value,
7999
+ compact
8000
+ }) {
8001
+ const theme2 = useMantineTheme();
8002
+ const color = theme2.colors[theme2.primaryColor][5];
8003
+ const onClick = () => {
8004
+ fetchNui("ADMIN_TOOL_INVOKE", { id: "gotoCoord", value }).catch(() => {
8005
+ });
8006
+ };
8007
+ return /* @__PURE__ */ jsx(Tooltip, { label: locale("GotoWorldTooltip"), position: "top", withArrow: true, withinPortal: true, zIndex: 2e3, children: /* @__PURE__ */ jsxs(
8008
+ motion.button,
8009
+ {
8010
+ onClick,
8011
+ whileHover: { background: alpha(color, 0.18) },
8012
+ whileTap: { scale: 0.95 },
8013
+ style: {
8014
+ background: alpha(color, 0.1),
8015
+ border: `0.1vh solid ${alpha(color, 0.35)}`,
8016
+ borderRadius: theme2.radius.xs,
8017
+ padding: compact ? "0.25vh 0.6vh" : "0.5vh 0.8vh",
8018
+ cursor: "pointer",
8019
+ display: "flex",
8020
+ alignItems: "center",
8021
+ gap: compact ? "0.3vh" : "0.4vh"
8022
+ },
8023
+ children: [
8024
+ /* @__PURE__ */ jsx(MapPin, { size: compact ? "1.1vh" : "1.3vh", color }),
8025
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: locale("Goto") })
8026
+ ]
7446
8027
  }
7447
- }
8028
+ ) });
8029
+ }
8030
+ function usePlayers(opts = {}) {
8031
+ const {
8032
+ includeOffline = false,
8033
+ search = "",
8034
+ limit = 50,
8035
+ staleTimeMs,
8036
+ refetchIntervalMs
8037
+ } = opts;
8038
+ const query = useQuery({
8039
+ queryKey: includeOffline ? ["dirk:players", "search", search.trim().toLowerCase(), limit] : ["dirk:players", "online"],
8040
+ queryFn: async () => {
8041
+ const toolId = includeOffline ? "searchPlayers" : "getOnlinePlayers";
8042
+ const payload = includeOffline ? { id: toolId, value: { search: search.trim(), limit } } : { id: toolId };
8043
+ const result = await fetchNui(
8044
+ "ADMIN_TOOL_QUERY",
8045
+ payload,
8046
+ // Browser-dev fallback. Returns a couple of mock players so the
8047
+ // dev shell isn't blank.
8048
+ includeOffline ? [
8049
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true },
8050
+ { id: null, citizenId: "DEF67890", name: "", charName: "Jane Offline", online: false }
8051
+ ] : [
8052
+ { id: 1, citizenId: "ABC12345", name: "Dev User", charName: "John Doe", online: true }
8053
+ ]
8054
+ );
8055
+ return Array.isArray(result) ? result : [];
8056
+ },
8057
+ staleTime: staleTimeMs ?? (includeOffline ? 3e4 : 5e3),
8058
+ gcTime: 5 * 6e4,
8059
+ refetchInterval: refetchIntervalMs ?? false,
8060
+ refetchOnWindowFocus: false,
8061
+ placeholderData: includeOffline ? keepPreviousData : void 0
8062
+ });
7448
8063
  return {
7449
- ...base,
7450
- ...override,
7451
- colors: {
7452
- ...colors,
7453
- ...override?.colors ?? {}
8064
+ players: query.data ?? [],
8065
+ isLoading: query.isLoading,
8066
+ isFetching: query.isFetching,
8067
+ error: query.error ?? null,
8068
+ refresh: () => query.refetch()
8069
+ };
8070
+ }
8071
+ var DEBOUNCE_MS = 300;
8072
+ var ONLINE_COLOR = "#3FA83F";
8073
+ var OFFLINE_COLOR = "#E54141";
8074
+ function PlayerSelect({
8075
+ value,
8076
+ onChange,
8077
+ includeOffline = false,
8078
+ limit = 50,
8079
+ searchable: searchableProp,
8080
+ placeholder,
8081
+ nothingFoundMessage: nothingFoundMessageProp,
8082
+ loadingLabel = "Loading\u2026",
8083
+ onlineLabel = "Online",
8084
+ offlineLabel = "Offline",
8085
+ refreshLabel = "Refresh player list",
8086
+ ...rest
8087
+ }) {
8088
+ const theme2 = useMantineTheme();
8089
+ const color = theme2.colors[theme2.primaryColor][5];
8090
+ const [searchInput, setSearchInput] = useState("");
8091
+ const [debouncedSearch, setDebouncedSearch] = useState("");
8092
+ const { players, isFetching, refresh } = usePlayers({
8093
+ includeOffline,
8094
+ search: includeOffline ? debouncedSearch : void 0,
8095
+ limit
8096
+ });
8097
+ const sortedPlayers = useMemo(
8098
+ () => [...players].sort((a, b) => {
8099
+ if (a.online !== b.online) return a.online ? -1 : 1;
8100
+ return a.charName.localeCompare(b.charName);
8101
+ }),
8102
+ [players]
8103
+ );
8104
+ const playerByCitizen = useMemo(() => {
8105
+ const m = /* @__PURE__ */ new Map();
8106
+ for (const p of sortedPlayers) m.set(p.citizenId, p);
8107
+ return m;
8108
+ }, [sortedPlayers]);
8109
+ const data = useMemo(() => {
8110
+ const items = sortedPlayers.map((p) => ({
8111
+ value: p.citizenId,
8112
+ label: formatLabel(p)
8113
+ }));
8114
+ if (value && !items.some((i) => i.value === value)) {
8115
+ items.unshift({ value, label: value });
7454
8116
  }
8117
+ return items;
8118
+ }, [sortedPlayers, value]);
8119
+ const selectedPlayer = value ? playerByCitizen.get(value) ?? null : null;
8120
+ const selectedLabel = selectedPlayer ? formatLabel(selectedPlayer) : null;
8121
+ useEffect(() => {
8122
+ if (!includeOffline) return;
8123
+ if (selectedLabel && searchInput === selectedLabel) return;
8124
+ const t3 = setTimeout(() => setDebouncedSearch(searchInput), DEBOUNCE_MS);
8125
+ return () => clearTimeout(t3);
8126
+ }, [searchInput, includeOffline, selectedLabel]);
8127
+ const renderOption = ({ option }) => {
8128
+ const p = playerByCitizen.get(option.value);
8129
+ if (!p) return option.label;
8130
+ return /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", gap: "xs", style: { width: "100%" }, children: [
8131
+ /* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1 }, children: formatLabel(p) }),
8132
+ /* @__PURE__ */ jsx(StatusDot, { online: p.online, onlineLabel, offlineLabel })
8133
+ ] });
7455
8134
  };
8135
+ return /* @__PURE__ */ jsx(
8136
+ Select,
8137
+ {
8138
+ ...rest,
8139
+ data,
8140
+ value: value ?? null,
8141
+ onChange: (v) => {
8142
+ if (!v) return onChange(null);
8143
+ const player = playerByCitizen.get(v) ?? null;
8144
+ onChange(player);
8145
+ },
8146
+ searchable: searchableProp ?? true,
8147
+ searchValue: includeOffline ? searchInput : void 0,
8148
+ onSearchChange: includeOffline ? setSearchInput : void 0,
8149
+ placeholder,
8150
+ nothingFoundMessage: isFetching ? loadingLabel : nothingFoundMessageProp ?? "No players found",
8151
+ maxDropdownHeight: 300,
8152
+ renderOption,
8153
+ leftSection: selectedPlayer ? /* @__PURE__ */ jsx(StatusDot, { online: selectedPlayer.online, onlineLabel, offlineLabel }) : void 0,
8154
+ rightSectionWidth: "3.5vh",
8155
+ rightSectionPointerEvents: "all",
8156
+ rightSection: /* @__PURE__ */ jsx(
8157
+ ActionIcon,
8158
+ {
8159
+ variant: "subtle",
8160
+ size: "sm",
8161
+ onClick: (e) => {
8162
+ e.stopPropagation();
8163
+ refresh();
8164
+ },
8165
+ "aria-label": refreshLabel,
8166
+ title: refreshLabel,
8167
+ style: { marginRight: "0.6vh" },
8168
+ children: isFetching ? /* @__PURE__ */ jsx(Loader, { size: "1.2vh", color }) : /* @__PURE__ */ jsx(RefreshCw, { size: "1.4vh", color })
8169
+ }
8170
+ )
8171
+ }
8172
+ );
7456
8173
  }
7457
- var DirkErrorBoundary = class extends React6.Component {
7458
- constructor() {
7459
- super(...arguments);
7460
- __publicField(this, "state", { error: null, stack: void 0 });
7461
- }
7462
- static getDerivedStateFromError(error2) {
7463
- return { error: error2 };
7464
- }
7465
- componentDidCatch(error2, info) {
7466
- console.group("\u{1F525} Dirk UI Crash");
7467
- console.error("Error:", error2);
7468
- console.error("Component Stack:", info.componentStack);
7469
- console.groupEnd();
7470
- }
7471
- render() {
7472
- if (!this.state.error) return this.props.children;
7473
- return /* @__PURE__ */ jsx(
7474
- Box,
7475
- {
7476
- style: {
7477
- position: "fixed",
7478
- inset: 0,
7479
- width: "100vw",
7480
- height: "100vh",
7481
- background: "rgba(10, 10, 12, 0.92)",
7482
- display: "flex",
7483
- alignItems: "center",
7484
- justifyContent: "center",
7485
- padding: "2rem",
7486
- zIndex: 999999
7487
- },
7488
- children: /* @__PURE__ */ jsx(
7489
- Box,
7490
- {
7491
- maw: 900,
7492
- w: "100%",
7493
- p: "lg",
7494
- style: {
7495
- background: "rgba(20,20,24,0.75)",
7496
- border: "1px solid rgba(255,255,255,0.08)",
7497
- borderRadius: "10px",
7498
- boxShadow: "0 10px 40px rgba(0,0,0,0.6)"
7499
- },
7500
- children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
7501
- /* @__PURE__ */ jsx(Title$1, { order: 2, c: "red.5", children: "Dirk UI Crash" }),
7502
- /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "The interface encountered a fatal error and stopped rendering." }),
7503
- /* @__PURE__ */ jsx(Code, { block: true, style: { maxHeight: 150, overflow: "auto" }, children: this.state.error?.message }),
7504
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Check console for full stack trace" })
7505
- ] })
7506
- }
7507
- )
8174
+ function StatusDot({ online, onlineLabel, offlineLabel }) {
8175
+ return /* @__PURE__ */ jsx(
8176
+ "span",
8177
+ {
8178
+ title: online ? onlineLabel : offlineLabel,
8179
+ style: {
8180
+ display: "inline-block",
8181
+ width: "0.9vh",
8182
+ height: "0.9vh",
8183
+ borderRadius: "50%",
8184
+ backgroundColor: online ? ONLINE_COLOR : OFFLINE_COLOR,
8185
+ boxShadow: `0 0 0.4vh ${online ? ONLINE_COLOR : OFFLINE_COLOR}`,
8186
+ flexShrink: 0
7508
8187
  }
7509
- );
8188
+ }
8189
+ );
8190
+ }
8191
+ function formatLabel(p) {
8192
+ const parts = [p.charName || p.citizenId];
8193
+ if (p.online) {
8194
+ if (p.name) parts.push(p.name);
8195
+ if (p.id != null) parts.push(`#${p.id}`);
7510
8196
  }
7511
- };
7512
- library.add(fas, far, fab);
7513
- function DirkProvider({ children, overideResourceName, themeOverride }) {
7514
- const {
7515
- primaryColor,
7516
- primaryShade,
7517
- customTheme,
7518
- game
7519
- } = useSettings();
7520
- localeStore((s) => s.locales);
7521
- const [scTheme, setScTheme] = useState(null);
7522
- useLayoutEffect(() => {
7523
- useSettings.setState({
7524
- overideResourceName
7525
- });
7526
- }, [overideResourceName]);
7527
- useEffect(() => {
7528
- fetchNui("NUI_READY").catch(() => {
8197
+ return parts.join(" \xB7 ");
8198
+ }
8199
+ function usePickDoor() {
8200
+ const begin = useAdminToolStore((s) => s.begin);
8201
+ return async () => {
8202
+ const pending = begin({
8203
+ id: "pickDoor",
8204
+ title: locale("PickDoorTitle"),
8205
+ hint: locale("PickDoorHint"),
8206
+ keys: [
8207
+ { key: "LMB", action: locale("Toggle") },
8208
+ { key: "E", action: locale("Confirm") },
8209
+ { key: "BACKSPACE", action: locale("Cancel") }
8210
+ ]
7529
8211
  });
7530
- Promise.all([
7531
- fetchNui("GET_SETTINGS"),
7532
- fetchNui("GET_RESOURCE_VERSION", void 0, { version: "dev" })
7533
- ]).then(([data, resourceInfo]) => {
7534
- useSettings.setState({
7535
- ...data,
7536
- resourceVersion: resourceInfo?.version || "dev"
7537
- });
7538
- }).catch((err) => {
7539
- console.error("Failed to fetch initial settings within dirk-cfx-react:", err);
8212
+ fetchNui("ADMIN_TOOL_BEGIN", { id: "pickDoor" }).catch(() => {
8213
+ useAdminToolStore.getState().cancelActive();
7540
8214
  });
7541
- }, []);
7542
- useNuiEvent("UPDATE_DIRK_LIB_SETTINGS", (data) => {
7543
- if (!data || typeof data !== "object") return;
7544
- useSettings.setState(data);
7545
- });
7546
- useNuiEvent(
7547
- "UPDATE_SCRIPT_CONFIG",
7548
- (data) => {
7549
- if (!data || !data.config || typeof data.config !== "object") return;
7550
- try {
7551
- const inst = getScriptConfigInstance();
7552
- inst.store.setState((prev) => ({ ...prev, ...data.config }));
7553
- } catch {
7554
- }
8215
+ return await pending;
8216
+ };
8217
+ }
8218
+ function DoorPickerButton({
8219
+ onPick,
8220
+ compact,
8221
+ label: label2,
8222
+ tooltip
8223
+ }) {
8224
+ const theme2 = useMantineTheme();
8225
+ const color = theme2.colors[theme2.primaryColor][5];
8226
+ const pickDoor = usePickDoor();
8227
+ const onClick = async () => {
8228
+ const picked = await pickDoor();
8229
+ if (picked && picked.doors.length > 0) onPick(picked);
8230
+ };
8231
+ return /* @__PURE__ */ jsx(
8232
+ Tooltip,
8233
+ {
8234
+ label: tooltip ?? locale("PickDoorTooltip"),
8235
+ position: "top",
8236
+ withArrow: true,
8237
+ withinPortal: true,
8238
+ zIndex: 2e3,
8239
+ children: /* @__PURE__ */ jsxs(
8240
+ motion.button,
8241
+ {
8242
+ onClick,
8243
+ whileHover: { background: alpha(color, 0.18) },
8244
+ whileTap: { scale: 0.95 },
8245
+ style: {
8246
+ background: alpha(color, 0.1),
8247
+ border: `0.1vh solid ${alpha(color, 0.35)}`,
8248
+ borderRadius: theme2.radius.xs,
8249
+ padding: compact ? "0.25vh 0.6vh" : "0.5vh 0.8vh",
8250
+ cursor: "pointer",
8251
+ display: "flex",
8252
+ alignItems: "center",
8253
+ gap: compact ? "0.3vh" : "0.4vh"
8254
+ },
8255
+ children: [
8256
+ /* @__PURE__ */ jsx(DoorOpen, { size: compact ? "1.1vh" : "1.3vh", color }),
8257
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: label2 ?? locale("PickDoor") })
8258
+ ]
8259
+ }
8260
+ )
8261
+ }
8262
+ );
8263
+ }
8264
+ var MapImpl = memo(({
8265
+ children,
8266
+ initialZoom = 2,
8267
+ initialCenter = gameToMap(0, 0),
8268
+ mapStyle = "game"
8269
+ }) => {
8270
+ const mapRef = useRef(null);
8271
+ return /* @__PURE__ */ jsxs(
8272
+ MapContainer,
8273
+ {
8274
+ maxBoundsViscosity: 1,
8275
+ preferCanvas: true,
8276
+ zoom: Math.round(initialZoom),
8277
+ zoomSnap: 1,
8278
+ zoomDelta: 1,
8279
+ zoomControl: false,
8280
+ crs: CRS.Simple,
8281
+ style: {
8282
+ height: "100%",
8283
+ width: "100%",
8284
+ overflow: "hidden",
8285
+ outline: "none !important",
8286
+ border: "none !important",
8287
+ boxShadow: "none !important",
8288
+ backgroundColor: "#384950"
8289
+ },
8290
+ center: initialCenter,
8291
+ attributionControl: false,
8292
+ doubleClickZoom: false,
8293
+ inertia: false,
8294
+ zoomAnimation: false,
8295
+ ref: mapRef,
8296
+ maxBounds: [[-250, -250], [250, 250]],
8297
+ children: [
8298
+ /* @__PURE__ */ jsx(MapLayer, { mapLayer: mapStyle }),
8299
+ children
8300
+ ]
7555
8301
  }
7556
8302
  );
8303
+ });
8304
+ MapImpl.displayName = "DirkMap";
8305
+ var Map2 = MapImpl;
8306
+ var MapLayer = ({ mapLayer }) => {
8307
+ const map = useMap();
8308
+ const layerRef = useRef(null);
7557
8309
  useEffect(() => {
7558
- let unsubscribe;
7559
- try {
7560
- const inst = getScriptConfigInstance();
7561
- setScTheme(inst.store.getState()?.theme ?? null);
7562
- const subscribable = inst.store;
7563
- if (typeof subscribable.subscribe === "function") {
7564
- unsubscribe = subscribable.subscribe((s) => {
7565
- setScTheme(s?.theme ?? null);
7566
- });
7567
- }
7568
- inst.fetchConfig?.().then((full) => {
7569
- if (full && typeof full === "object") {
7570
- setScTheme(full.theme ?? null);
7571
- }
7572
- }).catch(() => {
7573
- });
7574
- } catch {
8310
+ if (layerRef.current) {
8311
+ map.removeLayer(layerRef.current);
7575
8312
  }
8313
+ const layer = tileLayer(
8314
+ `https://s.rsg.sc/sc/images/games/GTAV/map/${mapLayer}/{z}/{x}/{y}.jpg`,
8315
+ {
8316
+ maxZoom: 6,
8317
+ minZoom: 4,
8318
+ bounds: latLngBounds(latLng(0, 128), latLng(-192, 0)),
8319
+ tileSize: 256,
8320
+ updateWhenZooming: false,
8321
+ keepBuffer: 2,
8322
+ opacity: 0.75
8323
+ }
8324
+ );
8325
+ layer.addTo(map);
8326
+ layerRef.current = layer;
7576
8327
  return () => {
7577
- unsubscribe?.();
8328
+ if (layerRef.current) {
8329
+ map.removeLayer(layerRef.current);
8330
+ }
7578
8331
  };
7579
- }, []);
7580
- const overrideActive = scTheme?.useOverride === true;
7581
- const effectivePrimaryColor = overrideActive ? scTheme.primaryColor ?? primaryColor : primaryColor;
7582
- const effectivePrimaryShade = overrideActive ? scTheme.primaryShade ?? primaryShade : primaryShade;
7583
- const effectiveCustomTheme = overrideActive ? scTheme.customTheme ?? customTheme : customTheme;
7584
- const mergedTheme = useMemo(
7585
- () => mergeMantineThemeSafe(
7586
- { ...theme_default, primaryColor: effectivePrimaryColor, primaryShade: effectivePrimaryShade },
7587
- effectiveCustomTheme,
7588
- themeOverride
7589
- ),
7590
- [effectivePrimaryColor, effectivePrimaryShade, effectiveCustomTheme, themeOverride]
8332
+ }, [mapLayer, map]);
8333
+ return null;
8334
+ };
8335
+ function ZoomControls() {
8336
+ const theme2 = useMantineTheme();
8337
+ const map = useMap();
8338
+ const buttons = [
8339
+ { Icon: Plus, fn: () => map.zoomIn() },
8340
+ { Icon: Minus, fn: () => map.zoomOut() }
8341
+ ];
8342
+ return /* @__PURE__ */ jsx(
8343
+ motion.div,
8344
+ {
8345
+ style: {
8346
+ position: "absolute",
8347
+ right: "2vh",
8348
+ top: "2vh",
8349
+ display: "flex",
8350
+ flexDirection: "column",
8351
+ zIndex: 999999,
8352
+ boxShadow: `0 0 1vh ${alpha(theme2.colors.dark[9], 0.85)}`,
8353
+ background: alpha(theme2.colors.dark[9], 0.85),
8354
+ borderRadius: theme2.radius.xs
8355
+ },
8356
+ initial: { opacity: 0, y: -20 },
8357
+ animate: { opacity: 1, y: 0 },
8358
+ exit: { opacity: 0, y: -20 },
8359
+ children: buttons.map(({ Icon, fn }, i) => /* @__PURE__ */ jsx(
8360
+ motion.div,
8361
+ {
8362
+ whileHover: { scale: 1.1, filter: "brightness(1.5)" },
8363
+ onClick: fn,
8364
+ style: {
8365
+ padding: theme2.spacing.xs,
8366
+ cursor: "pointer",
8367
+ display: "flex"
8368
+ },
8369
+ children: /* @__PURE__ */ jsx(Icon, { size: 34, color: theme2.colors.gray[5] })
8370
+ },
8371
+ i
8372
+ ))
8373
+ }
7591
8374
  );
7592
- useEffect(() => {
7593
- document.body.style.fontFamily = game === "rdr3" ? '"Red Dead", sans-serif' : '"Akrobat Regular", sans-serif';
7594
- }, [game]);
7595
- const content = isEnvBrowser() ? /* @__PURE__ */ jsx(
7596
- BackgroundImage,
8375
+ }
8376
+ var DEFAULT_SPRITE = 162;
8377
+ var DEFAULT_COLOR = 5;
8378
+ function BlipMarker({
8379
+ position,
8380
+ sprite,
8381
+ color,
8382
+ scale = 1,
8383
+ onClick,
8384
+ selected,
8385
+ disabled,
8386
+ fallbackSprite = DEFAULT_SPRITE,
8387
+ fallbackColor = DEFAULT_COLOR
8388
+ }) {
8389
+ const [hovered, setHovered] = useState(false);
8390
+ const mapCoords = useMemo(() => gameToMap(position.x, position.y), [position.x, position.y]);
8391
+ const effectiveSprite = sprite ?? fallbackSprite;
8392
+ const effectiveColor = color ?? fallbackColor;
8393
+ const url = blipUrlForSprite(effectiveSprite);
8394
+ const colorHex = getBlipColor(effectiveColor)?.hex ?? "#ffffff";
8395
+ const handleClick = (e) => {
8396
+ e.originalEvent?.stopPropagation?.();
8397
+ if (disabled) return;
8398
+ onClick?.();
8399
+ };
8400
+ const baseSize = 1.8 * scale;
8401
+ const size = `${baseSize}vh`;
8402
+ const ringSize = `${baseSize * 1.6}vh`;
8403
+ return /* @__PURE__ */ jsx(
8404
+ Marker,
7597
8405
  {
7598
- w: "100vw",
7599
- h: "100vh",
7600
- src: game === "fivem" ? "https://i.ytimg.com/vi/TOxuNbXrO28/maxresdefault.jpg" : "https://raw.githubusercontent.com/Jump-On-Studios/RedM-jo_libs/refs/heads/main/source-repositories/Menu/public/assets/images/background_dev.jpg",
7601
- children
8406
+ position: mapCoords,
8407
+ eventHandlers: onClick ? { click: handleClick } : void 0,
8408
+ icon: /* @__PURE__ */ jsxs(
8409
+ motion.div,
8410
+ {
8411
+ onHoverStart: () => setHovered(true),
8412
+ onHoverEnd: () => setHovered(false),
8413
+ style: {
8414
+ position: "relative",
8415
+ display: "flex",
8416
+ alignItems: "center",
8417
+ justifyContent: "center",
8418
+ cursor: disabled ? "not-allowed" : onClick ? "pointer" : "default",
8419
+ opacity: disabled ? 0.35 : 1,
8420
+ width: size,
8421
+ height: size
8422
+ },
8423
+ animate: { scale: hovered && !disabled ? 1.2 : selected ? 1.15 : 1 },
8424
+ transition: { duration: 0.15, ease: "easeOut" },
8425
+ children: [
8426
+ (selected || hovered) && !disabled && /* @__PURE__ */ jsx(
8427
+ motion.div,
8428
+ {
8429
+ style: {
8430
+ position: "absolute",
8431
+ width: ringSize,
8432
+ height: ringSize,
8433
+ borderRadius: "50%",
8434
+ border: `0.18vh solid ${alpha(colorHex, 0.85)}`,
8435
+ boxShadow: `0 0 1vh ${alpha(colorHex, 0.55)}`
8436
+ },
8437
+ initial: { opacity: 0, scale: 0.8 },
8438
+ animate: { opacity: 1, scale: 1 }
8439
+ }
8440
+ ),
8441
+ url && /* @__PURE__ */ jsx(
8442
+ "div",
8443
+ {
8444
+ style: {
8445
+ width: size,
8446
+ height: size,
8447
+ backgroundColor: colorHex,
8448
+ WebkitMaskImage: `url(${url})`,
8449
+ maskImage: `url(${url})`,
8450
+ WebkitMaskRepeat: "no-repeat",
8451
+ maskRepeat: "no-repeat",
8452
+ WebkitMaskPosition: "center",
8453
+ maskPosition: "center",
8454
+ WebkitMaskSize: "contain",
8455
+ maskSize: "contain",
8456
+ filter: [
8457
+ "drop-shadow(0.12vh 0 0 #000)",
8458
+ "drop-shadow(-0.12vh 0 0 #000)",
8459
+ "drop-shadow(0 0.12vh 0 #000)",
8460
+ "drop-shadow(0 -0.12vh 0 #000)",
8461
+ `drop-shadow(0 0 0.4vh ${alpha("#000", 0.7)})`
8462
+ ].join(" "),
8463
+ pointerEvents: "none",
8464
+ userSelect: "none"
8465
+ }
8466
+ }
8467
+ )
8468
+ ]
8469
+ }
8470
+ )
7602
8471
  }
7603
- ) : children;
7604
- return /* @__PURE__ */ jsx(MantineProvider, { theme: mergedTheme, defaultColorScheme: "dark", children: /* @__PURE__ */ jsx(DirkErrorBoundary, { children: content }) });
8472
+ );
8473
+ }
8474
+ function useTornEdges() {
8475
+ const game = useSettings((state) => state.game);
8476
+ return game === "rdr3" ? "torn-edge-wrapper" : "";
8477
+ }
8478
+ function TornEdgeSVGFilter() {
8479
+ return /* @__PURE__ */ jsx(
8480
+ "svg",
8481
+ {
8482
+ style: { position: "absolute", width: 0, height: 0, pointerEvents: "none" },
8483
+ "aria-hidden": "true",
8484
+ children: /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("filter", { id: "torn-edge-filter", x: "-50%", y: "-50%", width: "200%", height: "200%", children: [
8485
+ /* @__PURE__ */ jsx(
8486
+ "feTurbulence",
8487
+ {
8488
+ type: "fractalNoise",
8489
+ baseFrequency: "0.018 0.022",
8490
+ numOctaves: "5",
8491
+ seed: "9",
8492
+ result: "noise1"
8493
+ }
8494
+ ),
8495
+ /* @__PURE__ */ jsx(
8496
+ "feTurbulence",
8497
+ {
8498
+ type: "fractalNoise",
8499
+ baseFrequency: "0.08 0.12",
8500
+ numOctaves: "2",
8501
+ seed: "3",
8502
+ result: "noise2"
8503
+ }
8504
+ ),
8505
+ /* @__PURE__ */ jsx("feBlend", { in: "noise1", in2: "noise2", mode: "multiply", result: "combinedNoise" }),
8506
+ /* @__PURE__ */ jsx(
8507
+ "feDisplacementMap",
8508
+ {
8509
+ in: "SourceGraphic",
8510
+ in2: "combinedNoise",
8511
+ scale: "52",
8512
+ xChannelSelector: "R",
8513
+ yChannelSelector: "G",
8514
+ result: "displaced"
8515
+ }
8516
+ ),
8517
+ /* @__PURE__ */ jsx("feGaussianBlur", { stdDeviation: "0.8", in: "displaced", result: "blurred" }),
8518
+ /* @__PURE__ */ jsx("feComponentTransfer", { in: "blurred", result: "alphaFade", children: /* @__PURE__ */ jsx("feFuncA", { type: "gamma", amplitude: "1", exponent: "1.3", offset: "-0.05" }) }),
8519
+ /* @__PURE__ */ jsx("feMorphology", { operator: "erode", radius: "0.4", in: "alphaFade", result: "eroded" }),
8520
+ /* @__PURE__ */ jsx("feMerge", { children: /* @__PURE__ */ jsx("feMergeNode", { in: "eroded" }) })
8521
+ ] }) })
8522
+ }
8523
+ );
8524
+ }
8525
+ var moduleCache = /* @__PURE__ */ new Map();
8526
+ function useValidModels(names) {
8527
+ const cacheKey = useMemo(() => {
8528
+ const unique = Array.from(new Set(names.filter((n) => typeof n === "string" && n.length > 0)));
8529
+ unique.sort();
8530
+ return unique.join("|");
8531
+ }, [names]);
8532
+ const [version, setVersion] = useState(0);
8533
+ const inflight = useRef(/* @__PURE__ */ new Set());
8534
+ useEffect(() => {
8535
+ if (!cacheKey) return;
8536
+ const all = cacheKey.split("|").filter(Boolean);
8537
+ const missing = all.filter((n) => !moduleCache.has(n) && !inflight.current.has(n));
8538
+ if (missing.length === 0) return;
8539
+ missing.forEach((n) => inflight.current.add(n));
8540
+ let cancelled = false;
8541
+ fetchNui(
8542
+ "ADMIN_TOOL_QUERY",
8543
+ { id: "validateModels", value: missing },
8544
+ // Fallback when running outside FiveM (browser dev) — assume valid so the
8545
+ // dev shell doesn't grey out every row.
8546
+ Object.fromEntries(missing.map((n) => [n, true]))
8547
+ ).then((result) => {
8548
+ if (cancelled) return;
8549
+ const map = result && typeof result === "object" ? result : {};
8550
+ missing.forEach((n) => {
8551
+ moduleCache.set(n, !!map[n]);
8552
+ inflight.current.delete(n);
8553
+ });
8554
+ setVersion((v) => v + 1);
8555
+ }).catch(() => {
8556
+ if (cancelled) return;
8557
+ missing.forEach((n) => {
8558
+ inflight.current.delete(n);
8559
+ });
8560
+ });
8561
+ return () => {
8562
+ cancelled = true;
8563
+ };
8564
+ }, [cacheKey]);
8565
+ return useMemo(() => {
8566
+ const out = /* @__PURE__ */ new Set();
8567
+ if (!cacheKey) return out;
8568
+ for (const n of cacheKey.split("|")) {
8569
+ if (n && moduleCache.get(n) === true) out.add(n);
8570
+ }
8571
+ return out;
8572
+ }, [cacheKey, version]);
7605
8573
  }
7606
8574
  var Vector2Schema = z.object({
7607
8575
  x: z.number(),
@@ -7619,6 +8587,6 @@ var Vector4Schema = z.object({
7619
8587
  w: z.number()
7620
8588
  });
7621
8589
 
7622
- export { AccountSelect, AdminPageTitle, AnimPostFxSelect, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, DirkProvider, DiscordRoleSelect, FiveMKeyBindInput, FloatingParticles, FormProvider, GTA_CONTROLS, GTA_CONTROL_GROUP_ORDER, GroupName, GroupRank, GroupSelect, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, InfoBox, InputContainer, LevelBanner, LevelPanel, MissingItemsBanner, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PositionPicker, PromptModal, ScenarioSelect, SegmentedControl, SegmentedProgress, SelectItem, SwitchPanel, TestBed, ThemeOverrideSection, Title, TornEdgeSVGFilter, Vector2Schema, Vector3Schema, Vector4DeleteButton, Vector4Display, Vector4Schema, WorldPositionGotoButton, WorldPositionSetButton, colorWithAlpha, copyToClipboard, createFormStore, createScriptConfig, createSkill, extractDefaults, fetchLuaTable, fetchNui, formatGtaControl, gameToMap, getGtaControl, getImageShape, getItemImageUrl, getScriptConfigInstance, initialFetches, internalEvent, isEnvBrowser, isProfanity, latPr100, locale, localeStore, mapCenter, mapToGame, noop, numberToRoman, openLink, registerInitialFetch, registerInitialLuaTableFetch, runFetches, selectAllGroups, splitFAString, updatePresignedURL, uploadImage, useAudio, useAutoFetcher, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useFrameworkGroups, useItems, useItemsList, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore, useNuiEvent, useProfanityStore, useSettings, useTornEdges };
8590
+ export { AccountSelect, AdminPageTitle, AnimPostFxSelect, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BlipMarker, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, DirkProvider, DiscordRoleSelect, DoorPickerButton, FiveMKeyBindInput, FloatingParticles, FormProvider, GTA_CONTROLS, GTA_CONTROL_GROUP_ORDER, GroupName, GroupRank, GroupSelect, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, InfoBox, InputContainer, InstructionPanel, LevelBanner, LevelPanel, Map2 as Map, MapLayer, MissingItemsBanner, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PlayerSelect, PositionPicker, PromptModal, ScenarioSelect, SegmentedControl, SegmentedProgress, SelectItem, SwitchPanel, TestBed, ThemeOverrideSection, Title, TornEdgeSVGFilter, Vector2Schema, Vector3Schema, Vector4DeleteButton, Vector4Display, Vector4Schema, WorldPositionGotoButton, WorldPositionPicker, WorldPositionSetButton, ZoomControls, blipUrl, blipUrlForSprite, clearAdminState, colorWithAlpha, copyToClipboard, createFormStore, createScriptConfig, createSkill, dirkQueryClient, ensureFrameworkGroups, extractDefaults, fetchLuaTable, fetchNui, formatGtaControl, gameToMap, getBlipColor, getBlipEntry, getGtaControl, getImageShape, getItemImageUrl, getScriptConfigInstance, initialFetches, internalEvent, isEnvBrowser, isProfanity, latPr100, locale, localeStore, mapCenter, mapToGame, noop, numberToRoman, openLink, registerInitialFetch, registerInitialLuaTableFetch, runFetches, selectAllGroups, splitFAString, updatePresignedURL, uploadImage, useAdminState, useAdminToolStore, useAudio, useAutoFetcher, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useFrameworkGroups, useItems, useItemsList, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore, useNuiEvent, usePickDoor, usePlayers, useProfanityStore, useSettings, useTornEdges, useValidModels };
7623
8591
  //# sourceMappingURL=index.js.map
7624
8592
  //# sourceMappingURL=index.js.map