dirk-cfx-react 1.1.87 → 1.1.90

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  var core = require('@mantine/core');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
- var React6 = require('react');
5
+ var React4 = require('react');
6
6
  var zustand = require('zustand');
7
7
  var axios = require('axios');
8
8
  var reactFontawesome = require('@fortawesome/react-fontawesome');
@@ -12,7 +12,6 @@ var clickSoundUrl = require('./click_sound-PNCRRTM4.mp3');
12
12
  var hoverSoundUrl = require('./hover_sound-NBUA222C.mp3');
13
13
  var notifications = require('@mantine/notifications');
14
14
  var reactQuery = require('@tanstack/react-query');
15
- var colorsGenerator = require('@mantine/colors-generator');
16
15
  require('@mantine/core/styles.css');
17
16
  require('@mantine/notifications/styles.css');
18
17
  require('./styles/fonts.css');
@@ -23,11 +22,17 @@ var fontawesomeSvgCore = require('@fortawesome/fontawesome-svg-core');
23
22
  var freeBrandsSvgIcons = require('@fortawesome/free-brands-svg-icons');
24
23
  var freeRegularSvgIcons = require('@fortawesome/free-regular-svg-icons');
25
24
  var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons');
25
+ var colorsGenerator = require('@mantine/colors-generator');
26
+ var reactDom = require('react-dom');
27
+ var leaflet = require('leaflet');
28
+ require('leaflet/dist/leaflet.css');
29
+ var reactLeaflet = require('react-leaflet');
30
+ var reactLeafletComponentMarker = require('@adamscybot/react-leaflet-component-marker');
26
31
  var zod = require('zod');
27
32
 
28
33
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
29
34
 
30
- var React6__default = /*#__PURE__*/_interopDefault(React6);
35
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
31
36
  var axios__default = /*#__PURE__*/_interopDefault(axios);
32
37
  var clickSoundUrl__default = /*#__PURE__*/_interopDefault(clickSoundUrl);
33
38
  var hoverSoundUrl__default = /*#__PURE__*/_interopDefault(hoverSoundUrl);
@@ -976,6 +981,18 @@ var BLIP_COLOR_DATA = BLIP_COLORS.map(([id, label2]) => ({
976
981
  }));
977
982
  var blipEntryMap = new Map(BLIP_ENTRIES.map(([id, name, ext]) => [String(id), { id, name, ext }]));
978
983
  var blipColorMap = new Map(BLIP_COLORS.map(([id, label2, hex]) => [String(id), { id, label: label2, hex }]));
984
+ function getBlipEntry(spriteId) {
985
+ if (spriteId == null) return void 0;
986
+ return blipEntryMap.get(String(spriteId));
987
+ }
988
+ function getBlipColor(colorId) {
989
+ if (colorId == null) return void 0;
990
+ return blipColorMap.get(String(colorId));
991
+ }
992
+ function blipUrlForSprite(spriteId) {
993
+ const entry = getBlipEntry(spriteId);
994
+ return entry ? blipUrl(entry.name, entry.ext) : null;
995
+ }
979
996
  var renderBlipOption = ({ option }) => {
980
997
  const entry = blipEntryMap.get(option.value);
981
998
  if (!entry) return option.label;
@@ -1253,11 +1270,11 @@ var colorNames = {
1253
1270
  Yellow: { r: 255, g: 255, b: 0 },
1254
1271
  YellowGreen: { r: 154, g: 205, b: 50 }
1255
1272
  };
1256
- function colorWithAlpha(color, alpha12) {
1273
+ function colorWithAlpha(color, alpha18) {
1257
1274
  const lowerCasedColor = color.toLowerCase();
1258
1275
  if (colorNames[lowerCasedColor]) {
1259
1276
  const rgb = colorNames[lowerCasedColor];
1260
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha12})`;
1277
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha18})`;
1261
1278
  }
1262
1279
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1263
1280
  const hex = color.slice(1);
@@ -1265,12 +1282,12 @@ function colorWithAlpha(color, alpha12) {
1265
1282
  const r = bigint >> 16 & 255;
1266
1283
  const g = bigint >> 8 & 255;
1267
1284
  const b = bigint & 255;
1268
- return `rgba(${r}, ${g}, ${b}, ${alpha12})`;
1285
+ return `rgba(${r}, ${g}, ${b}, ${alpha18})`;
1269
1286
  }
1270
1287
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1271
1288
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1272
1289
  if (result) {
1273
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha12})`;
1290
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha18})`;
1274
1291
  }
1275
1292
  }
1276
1293
  return color;
@@ -1369,7 +1386,7 @@ async function runFetches() {
1369
1386
  );
1370
1387
  }
1371
1388
  var useAutoFetcher = () => {
1372
- React6.useEffect(() => {
1389
+ React4.useEffect(() => {
1373
1390
  if (isEnvBrowser()) return;
1374
1391
  runFetches().catch(() => {
1375
1392
  });
@@ -1406,11 +1423,35 @@ function reportMissingLocale(key) {
1406
1423
  fetchNui("REPORT_MISSING_LOCALE", { key }).catch(() => {
1407
1424
  });
1408
1425
  }
1426
+ var PACKAGE_DEFAULTS = {
1427
+ "OccupantsDesc": "Here you can view and manage the occupants of your traphouse. These occupants can be used mainly for selling drugs to the NPCs surrounding your traphouse. However they have other uses to so be careful who you add as an occupant.",
1428
+ "cfgpanel_discard": "Discard",
1429
+ "cfgpanel_manual_edit": "Manual Edit",
1430
+ "cfgpanel_reset_defaults": "Reset Defaults",
1431
+ "cfgpanel_version": "Version",
1432
+ "cfgpanel_back_title": "Back to script list",
1433
+ "cfgpanel_undo": "Undo",
1434
+ "cfgpanel_redo": "Redo",
1435
+ "cfgpanel_save": "Save",
1436
+ "cfgpanel_saving": "Saving...",
1437
+ "cfgpanel_history": "History",
1438
+ "cfgpanel_reset_title": "Reset to Defaults",
1439
+ "cfgpanel_reset_desc": "This will permanently reset ALL config back to the defaults. Every value you have configured will be overwritten. This cannot be undone.",
1440
+ "cfgpanel_reset_confirm": "Reset Config",
1441
+ "cfgpanel_discard_title": "Discard Unsaved Changes?",
1442
+ "cfgpanel_discard_desc_back": "You have unsaved changes. Going back now will discard them.",
1443
+ "cfgpanel_discard_desc_close": "You have unsaved changes. Closing now will discard them.",
1444
+ "cfgpanel_discard_confirm_back": "Go Back Without Saving",
1445
+ "cfgpanel_discard_confirm_close": "Close Without Saving",
1446
+ "cfgpanel_json_title": "Config JSON",
1447
+ "cfgpanel_cancel": "Cancel",
1448
+ "cfgpanel_apply": "Apply",
1449
+ "cfgpanel_history_title": "Config History",
1450
+ "cfgpanel_close": "Close"
1451
+ };
1409
1452
  var localeStore = zustand.create((set, get) => {
1410
1453
  return {
1411
- locales: {
1412
- "OccupantsDesc": "Here you can view and manage the occupants of your traphouse. These occupants can be used mainly for selling drugs to the NPCs surrounding your traphouse. However they have other uses to so be careful who you add as an occupant."
1413
- },
1454
+ locales: { ...PACKAGE_DEFAULTS },
1414
1455
  locale: (key, ...args) => {
1415
1456
  const exists = get().locales[key];
1416
1457
  if (!exists) reportMissingLocale(key);
@@ -1424,7 +1465,7 @@ var localeStore = zustand.create((set, get) => {
1424
1465
  });
1425
1466
  var locale = localeStore.getState().locale;
1426
1467
  registerInitialFetch("GET_LOCALES", void 0).then((data) => {
1427
- localeStore.setState({ locales: data });
1468
+ localeStore.setState({ locales: { ...PACKAGE_DEFAULTS, ...data } });
1428
1469
  }).catch(() => {
1429
1470
  });
1430
1471
  if (typeof window !== "undefined") {
@@ -1432,7 +1473,8 @@ if (typeof window !== "undefined") {
1432
1473
  const msg = event.data;
1433
1474
  if (!msg || msg.action !== "UPDATE_DIRK_LIB_LOCALES") return;
1434
1475
  if (!msg.data || typeof msg.data !== "object") return;
1435
- localeStore.setState({ locales: msg.data });
1476
+ if (Object.keys(msg.data).length === 0) return;
1477
+ localeStore.setState({ locales: { ...PACKAGE_DEFAULTS, ...msg.data } });
1436
1478
  });
1437
1479
  }
1438
1480
 
@@ -1865,7 +1907,7 @@ function createSkill(defaultSettings) {
1865
1907
  }));
1866
1908
  const useSkill = (xp) => {
1867
1909
  const { settings, levelMap } = useStore4();
1868
- return React6.useMemo(() => {
1910
+ return React4.useMemo(() => {
1869
1911
  const currentLevel = getLevelFromXP(xp, levelMap, settings);
1870
1912
  const nextLevel = Math.min(currentLevel + 1, settings.maxLevel);
1871
1913
  const currentLevelXP = levelMap[currentLevel.toString()] || 0;
@@ -1958,15 +2000,21 @@ var useFrameworkGroups = zustand.create(() => ({
1958
2000
  gangs: [],
1959
2001
  loaded: false
1960
2002
  }));
1961
- registerInitialFetch("GET_FRAMEWORK_GROUPS", void 0).then((data) => {
1962
- useFrameworkGroups.setState({
1963
- jobs: Array.isArray(data?.jobs) ? data.jobs : [],
1964
- gangs: Array.isArray(data?.gangs) ? data.gangs : [],
1965
- loaded: true
2003
+ var frameworkGroupsRequested = false;
2004
+ function ensureFrameworkGroups() {
2005
+ if (frameworkGroupsRequested) return;
2006
+ frameworkGroupsRequested = true;
2007
+ fetchNui("GET_FRAMEWORK_GROUPS", void 0).then((data) => {
2008
+ useFrameworkGroups.setState({
2009
+ jobs: Array.isArray(data?.jobs) ? data.jobs : [],
2010
+ gangs: Array.isArray(data?.gangs) ? data.gangs : [],
2011
+ loaded: true
2012
+ });
2013
+ }).catch(() => {
2014
+ frameworkGroupsRequested = false;
2015
+ useFrameworkGroups.setState({ loaded: true });
1966
2016
  });
1967
- }).catch(() => {
1968
- useFrameworkGroups.setState({ loaded: true });
1969
- });
2017
+ }
1970
2018
  function selectAllGroups(state) {
1971
2019
  return [...state.jobs, ...state.gangs];
1972
2020
  }
@@ -2455,8 +2503,8 @@ var FloatingParticles = ({
2455
2503
  mouseRepelStrength = 50,
2456
2504
  backgroundColor = "transparent"
2457
2505
  }) => {
2458
- const containerRef = React6.useRef(null);
2459
- const [particles, setParticles] = React6.useState([]);
2506
+ const containerRef = React4.useRef(null);
2507
+ const [particles, setParticles] = React4.useState([]);
2460
2508
  const mouseX = framerMotion.useMotionValue(0);
2461
2509
  const mouseY = framerMotion.useMotionValue(0);
2462
2510
  const durationMap = {
@@ -2469,7 +2517,7 @@ var FloatingParticles = ({
2469
2517
  const x = Math.sin(seed) * 1e4;
2470
2518
  return x - Math.floor(x);
2471
2519
  };
2472
- React6.useEffect(() => {
2520
+ React4.useEffect(() => {
2473
2521
  if (!containerRef.current) return;
2474
2522
  const bounds = containerRef.current.getBoundingClientRect();
2475
2523
  const newParticles = [...Array(particleCount)].map((_, i) => {
@@ -2490,7 +2538,7 @@ var FloatingParticles = ({
2490
2538
  });
2491
2539
  setParticles(newParticles);
2492
2540
  }, [particleCount, icons.length, duration.base, duration.variance]);
2493
- React6.useEffect(() => {
2541
+ React4.useEffect(() => {
2494
2542
  if (!containerRef.current) return;
2495
2543
  const handleMouseMove = (e) => {
2496
2544
  const bounds = containerRef.current.getBoundingClientRect();
@@ -2539,7 +2587,7 @@ var FloatingParticles = ({
2539
2587
  container.removeEventListener("mouseleave", handleMouseLeave);
2540
2588
  };
2541
2589
  }, [mouseX, mouseY, mouseRepelDistance, mouseRepelStrength]);
2542
- React6.useEffect(() => {
2590
+ React4.useEffect(() => {
2543
2591
  const handleResize = () => {
2544
2592
  if (!containerRef.current) return;
2545
2593
  const bounds = containerRef.current.getBoundingClientRect();
@@ -2975,23 +3023,23 @@ function Segment(props) {
2975
3023
  }
2976
3024
  );
2977
3025
  }
2978
- var NavigationContext = React6.createContext(null);
3026
+ var NavigationContext = React4.createContext(null);
2979
3027
  function useNavigation(selector) {
2980
- const navigation = React6.useContext(NavigationContext);
3028
+ const navigation = React4.useContext(NavigationContext);
2981
3029
  if (!navigation) {
2982
3030
  throw new Error("useNavigation must be used within a NavigationProvider");
2983
3031
  }
2984
3032
  return zustand.useStore(navigation, selector);
2985
3033
  }
2986
3034
  function useNavigationStore() {
2987
- const navigation = React6.useContext(NavigationContext);
3035
+ const navigation = React4.useContext(NavigationContext);
2988
3036
  if (!navigation) {
2989
3037
  throw new Error("useNavigationStore must be used within a NavigationProvider");
2990
3038
  }
2991
3039
  return navigation;
2992
3040
  }
2993
3041
  function NavigationProvider({ children, defaultPage }) {
2994
- const storeRef = React6.useRef(
3042
+ const storeRef = React4.useRef(
2995
3043
  zustand.create(() => ({
2996
3044
  pageId: defaultPage || "home"
2997
3045
  }))
@@ -3000,7 +3048,7 @@ function NavigationProvider({ children, defaultPage }) {
3000
3048
  }
3001
3049
  function NavBar(props) {
3002
3050
  const pageId = useNavigation((state) => state.pageId);
3003
- const store = useNavigationStore();
3051
+ const store2 = useNavigationStore();
3004
3052
  return /* @__PURE__ */ jsxRuntime.jsx(
3005
3053
  SegmentedControl,
3006
3054
  {
@@ -3009,7 +3057,7 @@ function NavBar(props) {
3009
3057
  value: pageId,
3010
3058
  items: props.items,
3011
3059
  onChange: (value) => {
3012
- store.setState({ pageId: value });
3060
+ store2.setState({ pageId: value });
3013
3061
  }
3014
3062
  }
3015
3063
  );
@@ -3343,7 +3391,7 @@ function Modal({
3343
3391
  children
3344
3392
  }) {
3345
3393
  const theme2 = core.useMantineTheme();
3346
- const pointerDownOnOverlay = React6.useRef(false);
3394
+ const pointerDownOnOverlay = React4.useRef(false);
3347
3395
  return /* @__PURE__ */ jsxRuntime.jsx(core.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
3348
3396
  framerMotion.motion.div,
3349
3397
  {
@@ -3524,9 +3572,9 @@ function PromptModal(props) {
3524
3572
  }
3525
3573
  );
3526
3574
  }
3527
- var ModalContext = React6.createContext(null);
3575
+ var ModalContext = React4.createContext(null);
3528
3576
  function useModal(selector) {
3529
- const modal = React6.useContext(ModalContext);
3577
+ const modal = React4.useContext(ModalContext);
3530
3578
  if (!modal) {
3531
3579
  throw new Error("useModal must be used within a ModalProvider");
3532
3580
  }
@@ -3554,7 +3602,7 @@ function StoreModal() {
3554
3602
  ) });
3555
3603
  }
3556
3604
  function ModalProvider({ children }) {
3557
- const storeRef = React6.useRef(
3605
+ const storeRef = React4.useRef(
3558
3606
  zustand.create(() => ({
3559
3607
  active: null
3560
3608
  }))
@@ -3565,7 +3613,7 @@ function ModalProvider({ children }) {
3565
3613
  ] });
3566
3614
  }
3567
3615
  function useModalActions() {
3568
- const modal = React6.useContext(ModalContext);
3616
+ const modal = React4.useContext(ModalContext);
3569
3617
  if (!modal) throw new Error("useModalActions must be used within a ModalProvider");
3570
3618
  const showModal = (openModal) => {
3571
3619
  modal.setState({ active: openModal });
@@ -3585,7 +3633,7 @@ function ConfirmModal({
3585
3633
  zIndex = 200
3586
3634
  }) {
3587
3635
  const theme2 = core.useMantineTheme();
3588
- const [typed, setTyped] = React6.useState("");
3636
+ const [typed, setTyped] = React4.useState("");
3589
3637
  const canConfirm = !confirmText || typed === confirmText;
3590
3638
  return /* @__PURE__ */ jsxRuntime.jsx(core.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
3591
3639
  framerMotion.motion.div,
@@ -3852,14 +3900,14 @@ function MissingItemsBanner() {
3852
3900
  const loaded = useMissingItemsAudit((s) => s.loaded);
3853
3901
  const inFlight = useMissingItemsAudit((s) => s.inFlight);
3854
3902
  const refresh = useMissingItemsAudit((s) => s.refresh);
3855
- const [expanded, setExpanded] = React6.useState(false);
3856
- const [activeTab, setActiveTab] = React6.useState("ox");
3857
- const [hoveredTab, setHoveredTab] = React6.useState(null);
3858
- const [copied, setCopied] = React6.useState(null);
3859
- React6.useEffect(() => {
3903
+ const [expanded, setExpanded] = React4.useState(false);
3904
+ const [activeTab, setActiveTab] = React4.useState("ox");
3905
+ const [hoveredTab, setHoveredTab] = React4.useState(null);
3906
+ const [copied, setCopied] = React4.useState(null);
3907
+ React4.useEffect(() => {
3860
3908
  if (!loaded) refresh();
3861
3909
  }, [loaded, refresh]);
3862
- const handleCopy = React6.useCallback((tab) => {
3910
+ const handleCopy = React4.useCallback((tab) => {
3863
3911
  if (!audit) return;
3864
3912
  const text = audit.snippets[tab] ?? "";
3865
3913
  navigator.clipboard.writeText(text).then(() => {
@@ -3868,7 +3916,7 @@ function MissingItemsBanner() {
3868
3916
  }).catch(() => {
3869
3917
  });
3870
3918
  }, [audit]);
3871
- const handleRefresh = React6.useCallback((e) => {
3919
+ const handleRefresh = React4.useCallback((e) => {
3872
3920
  e.stopPropagation();
3873
3921
  refresh();
3874
3922
  }, [refresh]);
@@ -4000,7 +4048,7 @@ function CodeView({
4000
4048
  warnColor
4001
4049
  }) {
4002
4050
  const theme2 = core.useMantineTheme();
4003
- const [hovered, setHovered] = React6.useState(false);
4051
+ const [hovered, setHovered] = React4.useState(false);
4004
4052
  const lines = code === "" ? [""] : code.split("\n");
4005
4053
  const lineNumWidth = String(lines.length).length;
4006
4054
  const copyBg = copied ? core.alpha("#22c55e", 0.15) : hovered ? core.alpha(warnColor, 0.18) : core.alpha(warnColor, 0.1);
@@ -4303,42 +4351,42 @@ function createFormStore(initialValues, validationRules, onSubmit) {
4303
4351
  }
4304
4352
  }));
4305
4353
  }
4306
- var FormContext = React6.createContext(null);
4354
+ var FormContext = React4.createContext(null);
4307
4355
  function FormProvider({
4308
4356
  initialValues,
4309
4357
  validate,
4310
4358
  onSubmit,
4311
4359
  children
4312
4360
  }) {
4313
- const storeRef = React6.useRef(
4361
+ const storeRef = React4.useRef(
4314
4362
  createFormStore(initialValues, validate, onSubmit)
4315
4363
  );
4316
4364
  return /* @__PURE__ */ jsxRuntime.jsx(FormContext.Provider, { value: storeRef.current, children });
4317
4365
  }
4318
4366
  function useForm() {
4319
- const store = React6.useContext(FormContext);
4320
- if (!store) {
4367
+ const store2 = React4.useContext(FormContext);
4368
+ if (!store2) {
4321
4369
  throw new Error("useForm must be used inside <FormProvider>");
4322
4370
  }
4323
- const state = zustand.useStore(store);
4324
- const changedFields = React6.useMemo(() => {
4371
+ const state = zustand.useStore(store2);
4372
+ const changedFields = React4.useMemo(() => {
4325
4373
  return collectChangedPaths(state.values, state.initialValues);
4326
4374
  }, [state.values, state.initialValues]);
4327
4375
  return { ...state, changedFields, changedCount: changedFields.length };
4328
4376
  }
4329
4377
  function useFormField(path) {
4330
- const store = React6.useContext(FormContext);
4331
- if (!store) {
4378
+ const store2 = React4.useContext(FormContext);
4379
+ if (!store2) {
4332
4380
  throw new Error("useFormField must be used inside <FormProvider>");
4333
4381
  }
4334
- return zustand.useStore(store, (s) => getNested(s.values, path));
4382
+ return zustand.useStore(store2, (s) => getNested(s.values, path));
4335
4383
  }
4336
4384
  function useFormFields(...paths) {
4337
- const store = React6.useContext(FormContext);
4338
- if (!store) {
4385
+ const store2 = React4.useContext(FormContext);
4386
+ if (!store2) {
4339
4387
  throw new Error("useFormFields must be used inside <FormProvider>");
4340
4388
  }
4341
- return zustand.useStore(store, (s) => {
4389
+ return zustand.useStore(store2, (s) => {
4342
4390
  const result = {};
4343
4391
  for (const path of paths) {
4344
4392
  result[path] = getNested(s.values, path);
@@ -4347,18 +4395,18 @@ function useFormFields(...paths) {
4347
4395
  });
4348
4396
  }
4349
4397
  function useFormError(path) {
4350
- const store = React6.useContext(FormContext);
4351
- if (!store) {
4398
+ const store2 = React4.useContext(FormContext);
4399
+ if (!store2) {
4352
4400
  throw new Error("useFormError must be used inside <FormProvider>");
4353
4401
  }
4354
- return zustand.useStore(store, (s) => s.errors[path]);
4402
+ return zustand.useStore(store2, (s) => s.errors[path]);
4355
4403
  }
4356
4404
  function useFormErrors(...paths) {
4357
- const store = React6.useContext(FormContext);
4358
- if (!store) {
4405
+ const store2 = React4.useContext(FormContext);
4406
+ if (!store2) {
4359
4407
  throw new Error("useFormErrors must be used inside <FormProvider>");
4360
4408
  }
4361
- return zustand.useStore(store, (s) => {
4409
+ return zustand.useStore(store2, (s) => {
4362
4410
  const result = {};
4363
4411
  for (const path of paths) {
4364
4412
  result[path] = s.errors[path];
@@ -4367,18 +4415,229 @@ function useFormErrors(...paths) {
4367
4415
  });
4368
4416
  }
4369
4417
  function useFormActions() {
4370
- const store = React6.useContext(FormContext);
4371
- if (!store) {
4418
+ const store2 = React4.useContext(FormContext);
4419
+ if (!store2) {
4372
4420
  throw new Error("useFormActions must be used inside <FormProvider>");
4373
4421
  }
4374
- return store.getState();
4422
+ return store2.getState();
4423
+ }
4424
+ var store = /* @__PURE__ */ new Map();
4425
+ var listeners = /* @__PURE__ */ new Map();
4426
+ function notify(key) {
4427
+ const ls = listeners.get(key);
4428
+ if (!ls) return;
4429
+ ls.forEach((fn) => fn());
4430
+ }
4431
+ function useAdminState(key, initial) {
4432
+ const [, setTick] = React4.useState(0);
4433
+ React4.useEffect(() => {
4434
+ const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
4435
+ listeners.set(key, set);
4436
+ const handler = () => setTick((t3) => t3 + 1);
4437
+ set.add(handler);
4438
+ return () => {
4439
+ set.delete(handler);
4440
+ if (set.size === 0) listeners.delete(key);
4441
+ };
4442
+ }, [key]);
4443
+ const value = store.has(key) ? store.get(key) : initial;
4444
+ const setValue = (v) => {
4445
+ const next = typeof v === "function" ? v(value) : v;
4446
+ store.set(key, next);
4447
+ notify(key);
4448
+ };
4449
+ return [value, setValue];
4450
+ }
4451
+ function clearAdminState(key) {
4452
+ if (key) {
4453
+ store.delete(key);
4454
+ notify(key);
4455
+ } else {
4456
+ const keys = Array.from(store.keys());
4457
+ store.clear();
4458
+ keys.forEach(notify);
4459
+ }
4375
4460
  }
4461
+ var label = {
4462
+ fontSize: "var(--mantine-font-size-xs)",
4463
+ fontFamily: "Akrobat Bold",
4464
+ letterSpacing: "0.05em",
4465
+ textTransform: "uppercase"
4466
+ };
4467
+ var error = {
4468
+ fontSize: "var(--mantine-font-size-xs)",
4469
+ fontFamily: "Akrobat Regular"
4470
+ };
4471
+ var description = {
4472
+ fontSize: "var(--mantine-font-size-xs)"
4473
+ };
4474
+ var genericInputStyles = {
4475
+ styles: {
4476
+ label,
4477
+ error,
4478
+ description,
4479
+ input: {
4480
+ background: "rgba(255,255,255,0.04)",
4481
+ border: "0.1vh solid rgba(255,255,255,0.08)",
4482
+ color: "rgba(255,255,255,0.85)",
4483
+ minHeight: "4vh"
4484
+ }
4485
+ }
4486
+ };
4487
+ var theme = core.createTheme({
4488
+ primaryColor: "dirk",
4489
+ primaryShade: 9,
4490
+ defaultRadius: "xs",
4491
+ fontFamily: "Akrobat Regular, sans-serif",
4492
+ radius: {
4493
+ xxs: "0.3vh",
4494
+ xs: "0.5vh",
4495
+ sm: "0.75vh",
4496
+ md: "1vh",
4497
+ lg: "1.5vh",
4498
+ xl: "2vh",
4499
+ xxl: "3vh"
4500
+ },
4501
+ fontSizes: {
4502
+ xxs: "1.2vh",
4503
+ xs: "1.5vh",
4504
+ sm: "1.8vh",
4505
+ md: "2.2vh",
4506
+ lg: "2.8vh",
4507
+ xl: "3.3vh",
4508
+ xxl: "3.8vh"
4509
+ },
4510
+ lineHeights: {
4511
+ xxs: "1.4vh",
4512
+ xs: "1.8vh",
4513
+ sm: "2.2vh",
4514
+ md: "2.8vh",
4515
+ lg: "3.3vh",
4516
+ xl: "3.8vh"
4517
+ },
4518
+ spacing: {
4519
+ xxs: "0.5vh",
4520
+ xs: "0.75vh",
4521
+ sm: "1.5vh",
4522
+ md: "2vh",
4523
+ lg: "3vh",
4524
+ xl: "4vh",
4525
+ xxl: "5vh"
4526
+ },
4527
+ components: {
4528
+ Progress: {
4529
+ styles: {
4530
+ label: {
4531
+ fontFamily: "Akrobat Bold",
4532
+ letterSpacing: "0.05em",
4533
+ textTransform: "uppercase"
4534
+ },
4535
+ root: {
4536
+ backgroundColor: "rgba(77, 77, 77, 0.4)"
4537
+ }
4538
+ }
4539
+ },
4540
+ Input: genericInputStyles,
4541
+ TextInput: genericInputStyles,
4542
+ NumberInput: genericInputStyles,
4543
+ Select: genericInputStyles,
4544
+ MultiSelect: genericInputStyles,
4545
+ Textarea: genericInputStyles,
4546
+ ColorInput: genericInputStyles,
4547
+ DateInput: genericInputStyles,
4548
+ // Mantine's <Button> defaults to rem-based heights (xs ≈ 1.875rem)
4549
+ // which doesn't match this theme's vh-based input min-heights, so
4550
+ // `<Button size="xs">` rendered next to `<TextInput size="xs">` ends
4551
+ // up visibly shorter. Pin the button heights to the same vh values
4552
+ // the inputs use so xs-everything lines up out of the box.
4553
+ Button: {
4554
+ styles: {
4555
+ label: {
4556
+ fontFamily: "Akrobat Bold",
4557
+ letterSpacing: "0.05em",
4558
+ textTransform: "uppercase"
4559
+ },
4560
+ root: {
4561
+ // Mantine maps these to --button-height per size; setting them
4562
+ // directly here keeps native Button sizing logic intact.
4563
+ }
4564
+ },
4565
+ vars: (_theme, props) => {
4566
+ const heights = {
4567
+ xs: "4vh",
4568
+ sm: "4.5vh",
4569
+ md: "5vh",
4570
+ lg: "5.5vh",
4571
+ xl: "6vh"
4572
+ };
4573
+ const h = heights[props.size ?? "sm"] ?? "4.5vh";
4574
+ return {
4575
+ root: {
4576
+ "--button-height": h
4577
+ }
4578
+ };
4579
+ }
4580
+ },
4581
+ Pill: {
4582
+ styles: (theme2) => ({
4583
+ root: {
4584
+ display: "inline-flex",
4585
+ alignItems: "center",
4586
+ justifyContent: "space-between",
4587
+ backgroundColor: "rgba(76, 76, 76, 0.3)",
4588
+ height: "fit-content",
4589
+ textTransform: "uppercase",
4590
+ letterSpacing: "0.05em",
4591
+ fontFamily: "Akrobat Bold",
4592
+ fontSize: "1.25vh",
4593
+ borderRadius: theme2.defaultRadius,
4594
+ paddingBottom: "0.5vh",
4595
+ paddingTop: "0.5vh"
4596
+ }
4597
+ })
4598
+ },
4599
+ // Mantine's <Tooltip> defaults to a white card with black text — looks
4600
+ // jarring against every dirk consumer's dark configurator. Every script
4601
+ // used to hand-paste this dark style block per Tooltip; centralised here
4602
+ // so consumers get the right look automatically and never need to think
4603
+ // about it again.
4604
+ Tooltip: {
4605
+ styles: (theme2) => ({
4606
+ tooltip: {
4607
+ background: core.alpha(theme2.colors.dark[7], 0.95),
4608
+ border: "0.1vh solid rgba(255,255,255,0.1)",
4609
+ color: "rgba(255,255,255,0.75)",
4610
+ fontFamily: "Akrobat Bold",
4611
+ fontSize: "1.3vh",
4612
+ lineHeight: 1.3,
4613
+ padding: "0.6vh 0.8vh",
4614
+ letterSpacing: "0.03em"
4615
+ }
4616
+ })
4617
+ }
4618
+ },
4619
+ colors: {
4620
+ dirk: [
4621
+ "#ffffff",
4622
+ "#f3fce9",
4623
+ "#dbf5bd",
4624
+ "#c3ee91",
4625
+ "#ace765",
4626
+ "#94e039",
4627
+ "#7ac61f",
4628
+ "#5f9a18",
4629
+ "#29420a",
4630
+ "#446e11"
4631
+ ]
4632
+ }
4633
+ });
4634
+ var theme_default = theme;
4376
4635
  var useNuiEvent = (action, handler) => {
4377
- const savedHandler = React6.useRef(noop);
4378
- React6.useEffect(() => {
4636
+ const savedHandler = React4.useRef(noop);
4637
+ React4.useEffect(() => {
4379
4638
  savedHandler.current = handler;
4380
4639
  }, [handler]);
4381
- React6.useEffect(() => {
4640
+ React4.useEffect(() => {
4382
4641
  const eventListener = (event) => {
4383
4642
  const { action: eventAction, data } = event.data;
4384
4643
  if (savedHandler.current) {
@@ -4391,13 +4650,35 @@ var useNuiEvent = (action, handler) => {
4391
4650
  return () => window.removeEventListener("message", eventListener);
4392
4651
  }, [action]);
4393
4652
  };
4653
+
4654
+ // src/utils/mergeMantineTheme.ts
4655
+ var isValidColorScale = (v) => Array.isArray(v) && v.length === 10 && v.every((shade) => typeof shade === "string");
4656
+ function mergeMantineThemeSafe(base, custom, override) {
4657
+ const colors = { ...base.colors };
4658
+ if (custom && isValidColorScale(custom)) {
4659
+ colors["custom"] = custom;
4660
+ } else if (!colors["custom"]) {
4661
+ const fallback = base.colors && base.colors.dirk;
4662
+ if (fallback && isValidColorScale(fallback)) {
4663
+ colors["custom"] = fallback;
4664
+ }
4665
+ }
4666
+ return {
4667
+ ...base,
4668
+ ...override,
4669
+ colors: {
4670
+ ...colors,
4671
+ ...override?.colors ?? {}
4672
+ }
4673
+ };
4674
+ }
4394
4675
  var _instance = null;
4395
4676
  function getScriptConfigInstance() {
4396
4677
  if (!_instance) throw new Error("[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel");
4397
4678
  return _instance;
4398
4679
  }
4399
4680
  function createScriptConfig(defaultValue) {
4400
- const store = zustand.create(() => defaultValue);
4681
+ const store2 = zustand.create(() => defaultValue);
4401
4682
  let clientVersion = 0;
4402
4683
  const useScriptConfigHooks = () => {
4403
4684
  useNuiEvent("UPDATE_SCRIPT_CONFIG", (data) => {
@@ -4406,7 +4687,7 @@ function createScriptConfig(defaultValue) {
4406
4687
  clientVersion = data.clientVersion;
4407
4688
  }
4408
4689
  if (data.config && typeof data.config === "object") {
4409
- store.setState((prev) => ({ ...prev, ...data.config }));
4690
+ store2.setState((prev) => ({ ...prev, ...data.config }));
4410
4691
  }
4411
4692
  });
4412
4693
  };
@@ -4414,7 +4695,7 @@ function createScriptConfig(defaultValue) {
4414
4695
  try {
4415
4696
  const response = await fetchNui("GET_FULL_SCRIPT_CONFIG");
4416
4697
  if (response?.success && response.data?.config) {
4417
- store.setState(() => response.data.config);
4698
+ store2.setState(() => response.data.config);
4418
4699
  if (typeof response.data.clientVersion === "number") {
4419
4700
  clientVersion = response.data.clientVersion;
4420
4701
  }
@@ -4425,7 +4706,7 @@ function createScriptConfig(defaultValue) {
4425
4706
  return null;
4426
4707
  };
4427
4708
  const updateScriptConfig = async (newConfig) => {
4428
- store.setState((prev) => ({ ...prev, ...newConfig }));
4709
+ store2.setState((prev) => ({ ...prev, ...newConfig }));
4429
4710
  const response = await fetchNui("UPDATE_SCRIPT_CONFIG", {
4430
4711
  data: newConfig,
4431
4712
  expectedVersion: clientVersion
@@ -4434,7 +4715,7 @@ function createScriptConfig(defaultValue) {
4434
4715
  clientVersion = response.meta.client_version;
4435
4716
  }
4436
4717
  if (response?.success === false && response?.meta?.latestData) {
4437
- store.setState((prev) => ({ ...prev, ...response.meta.latestData }));
4718
+ store2.setState((prev) => ({ ...prev, ...response.meta.latestData }));
4438
4719
  }
4439
4720
  return response;
4440
4721
  };
@@ -4446,28 +4727,251 @@ function createScriptConfig(defaultValue) {
4446
4727
  if (response?.success) {
4447
4728
  const fresh = await fetchScriptConfig();
4448
4729
  if (fresh) {
4449
- store.setState(() => fresh);
4730
+ store2.setState(() => fresh);
4450
4731
  }
4451
4732
  }
4452
4733
  return response;
4453
4734
  };
4454
4735
  _instance = {
4455
- store,
4736
+ store: store2,
4456
4737
  updateConfig: updateScriptConfig,
4457
4738
  resetConfig,
4458
4739
  getHistory: getScriptConfigHistory,
4459
4740
  fetchConfig: fetchScriptConfig
4460
4741
  };
4461
- return { store, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig };
4742
+ return { store: store2, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig };
4743
+ }
4744
+ var DirkErrorBoundary = class extends React4__default.default.Component {
4745
+ constructor() {
4746
+ super(...arguments);
4747
+ __publicField(this, "state", { error: null, stack: void 0 });
4748
+ }
4749
+ static getDerivedStateFromError(error2) {
4750
+ return { error: error2 };
4751
+ }
4752
+ componentDidCatch(error2, info) {
4753
+ console.group("\u{1F525} Dirk UI Crash");
4754
+ console.error("Error:", error2);
4755
+ console.error("Component Stack:", info.componentStack);
4756
+ console.groupEnd();
4757
+ }
4758
+ render() {
4759
+ if (!this.state.error) return this.props.children;
4760
+ return /* @__PURE__ */ jsxRuntime.jsx(
4761
+ core.Box,
4762
+ {
4763
+ style: {
4764
+ position: "fixed",
4765
+ inset: 0,
4766
+ width: "100vw",
4767
+ height: "100vh",
4768
+ background: "rgba(10, 10, 12, 0.92)",
4769
+ display: "flex",
4770
+ alignItems: "center",
4771
+ justifyContent: "center",
4772
+ padding: "2rem",
4773
+ zIndex: 999999
4774
+ },
4775
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4776
+ core.Box,
4777
+ {
4778
+ maw: 900,
4779
+ w: "100%",
4780
+ p: "lg",
4781
+ style: {
4782
+ background: "rgba(20,20,24,0.75)",
4783
+ border: "1px solid rgba(255,255,255,0.08)",
4784
+ borderRadius: "10px",
4785
+ boxShadow: "0 10px 40px rgba(0,0,0,0.6)"
4786
+ },
4787
+ children: /* @__PURE__ */ jsxRuntime.jsxs(core.Stack, { gap: "sm", children: [
4788
+ /* @__PURE__ */ jsxRuntime.jsx(core.Title, { order: 2, c: "red.5", children: "Dirk UI Crash" }),
4789
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { c: "dimmed", size: "sm", children: "The interface encountered a fatal error and stopped rendering." }),
4790
+ /* @__PURE__ */ jsxRuntime.jsx(core.Code, { block: true, style: { maxHeight: 150, overflow: "auto" }, children: this.state.error?.message }),
4791
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xs", c: "dimmed", children: "Check console for full stack trace" })
4792
+ ] })
4793
+ }
4794
+ )
4795
+ }
4796
+ );
4797
+ }
4798
+ };
4799
+ var useAdminToolStore = zustand.create((set, get) => ({
4800
+ active: null,
4801
+ begin: (spec) => new Promise((resolve) => {
4802
+ const prev = get().active;
4803
+ if (prev) prev.resolve(null);
4804
+ set({ active: { ...spec, resolve } });
4805
+ }),
4806
+ resolveActive: (value) => {
4807
+ const cur = get().active;
4808
+ if (!cur) return;
4809
+ cur.resolve(value);
4810
+ set({ active: null });
4811
+ },
4812
+ cancelActive: () => {
4813
+ const cur = get().active;
4814
+ if (!cur) return;
4815
+ cur.resolve(null);
4816
+ set({ active: null });
4817
+ }
4818
+ }));
4819
+
4820
+ // src/components/AdminTools/AdminOverlays.tsx
4821
+ var listenerInstalled = false;
4822
+ function installNuiListener() {
4823
+ if (listenerInstalled || typeof window === "undefined") return;
4824
+ listenerInstalled = true;
4825
+ window.addEventListener("message", (e) => {
4826
+ const msg = e?.data;
4827
+ if (!msg || typeof msg !== "object" || typeof msg.action !== "string") return;
4828
+ const action = msg.action;
4829
+ const cur = useAdminToolStore.getState().active;
4830
+ if (!cur) return;
4831
+ if (action === `${cur.id}_RESULT`) {
4832
+ useAdminToolStore.getState().resolveActive(msg.data ?? null);
4833
+ } else if (action === `${cur.id}_CANCELLED`) {
4834
+ useAdminToolStore.getState().cancelActive();
4835
+ }
4836
+ });
4837
+ }
4838
+ var BODY_HIDE_STYLE_ID = "dirk-instruction-panel-style";
4839
+ var BODY_HIDE_ATTR = "data-dirk-instruction-active";
4840
+ var OVERLAY_ATTR = "data-dirk-instruction-overlay";
4841
+ function ensureBodyHideStyle() {
4842
+ if (typeof document === "undefined") return;
4843
+ if (document.getElementById(BODY_HIDE_STYLE_ID)) return;
4844
+ const el = document.createElement("style");
4845
+ el.id = BODY_HIDE_STYLE_ID;
4846
+ el.textContent = `
4847
+ body[${BODY_HIDE_ATTR}] > *:not([${OVERLAY_ATTR}]) {
4848
+ visibility: hidden !important;
4849
+ opacity: 0 !important;
4850
+ pointer-events: none !important;
4851
+ }
4852
+ `;
4853
+ document.head.appendChild(el);
4854
+ }
4855
+ function AdminOverlays() {
4856
+ const active = useAdminToolStore((s) => s.active);
4857
+ React4.useEffect(() => {
4858
+ installNuiListener();
4859
+ }, []);
4860
+ React4.useEffect(() => {
4861
+ if (!active || typeof document === "undefined") return;
4862
+ ensureBodyHideStyle();
4863
+ document.body.setAttribute(BODY_HIDE_ATTR, "");
4864
+ return () => {
4865
+ document.body.removeAttribute(BODY_HIDE_ATTR);
4866
+ };
4867
+ }, [active]);
4868
+ return null;
4462
4869
  }
4463
- var configPanelQueryClient = new reactQuery.QueryClient({
4870
+ fontawesomeSvgCore.library.add(freeSolidSvgIcons.fas, freeRegularSvgIcons.far, freeBrandsSvgIcons.fab);
4871
+ var dirkQueryClient = new reactQuery.QueryClient({
4464
4872
  defaultOptions: { queries: { staleTime: 3e4, gcTime: 5 * 6e4 } }
4465
4873
  });
4466
- function NavItemButton({
4467
- icon: Icon,
4468
- label: label2,
4469
- active,
4470
- onClick
4874
+ function DirkProvider({ children, overideResourceName, themeOverride }) {
4875
+ const {
4876
+ primaryColor,
4877
+ primaryShade,
4878
+ customTheme,
4879
+ game
4880
+ } = useSettings();
4881
+ localeStore((s) => s.locales);
4882
+ const [scTheme, setScTheme] = React4.useState(null);
4883
+ React4.useLayoutEffect(() => {
4884
+ useSettings.setState({
4885
+ overideResourceName
4886
+ });
4887
+ }, [overideResourceName]);
4888
+ React4.useEffect(() => {
4889
+ fetchNui("NUI_READY").catch(() => {
4890
+ });
4891
+ Promise.all([
4892
+ fetchNui("GET_SETTINGS"),
4893
+ fetchNui("GET_RESOURCE_VERSION", void 0, { version: "dev" })
4894
+ ]).then(([data, resourceInfo]) => {
4895
+ useSettings.setState({
4896
+ ...data,
4897
+ resourceVersion: resourceInfo?.version || "dev"
4898
+ });
4899
+ }).catch((err) => {
4900
+ console.error("Failed to fetch initial settings within dirk-cfx-react:", err);
4901
+ });
4902
+ }, []);
4903
+ useNuiEvent("UPDATE_DIRK_LIB_SETTINGS", (data) => {
4904
+ if (!data || typeof data !== "object") return;
4905
+ useSettings.setState(data);
4906
+ });
4907
+ useNuiEvent(
4908
+ "UPDATE_SCRIPT_CONFIG",
4909
+ (data) => {
4910
+ if (!data || !data.config || typeof data.config !== "object") return;
4911
+ try {
4912
+ const inst = getScriptConfigInstance();
4913
+ inst.store.setState((prev) => ({ ...prev, ...data.config }));
4914
+ } catch {
4915
+ }
4916
+ }
4917
+ );
4918
+ React4.useEffect(() => {
4919
+ let unsubscribe;
4920
+ try {
4921
+ const inst = getScriptConfigInstance();
4922
+ setScTheme(inst.store.getState()?.theme ?? null);
4923
+ const subscribable = inst.store;
4924
+ if (typeof subscribable.subscribe === "function") {
4925
+ unsubscribe = subscribable.subscribe((s) => {
4926
+ setScTheme(s?.theme ?? null);
4927
+ });
4928
+ }
4929
+ inst.fetchConfig?.().then((full) => {
4930
+ if (full && typeof full === "object") {
4931
+ setScTheme(full.theme ?? null);
4932
+ }
4933
+ }).catch(() => {
4934
+ });
4935
+ } catch {
4936
+ }
4937
+ return () => {
4938
+ unsubscribe?.();
4939
+ };
4940
+ }, []);
4941
+ const overrideActive = scTheme?.useOverride === true;
4942
+ const effectivePrimaryColor = overrideActive ? scTheme.primaryColor ?? primaryColor : primaryColor;
4943
+ const effectivePrimaryShade = overrideActive ? scTheme.primaryShade ?? primaryShade : primaryShade;
4944
+ const effectiveCustomTheme = overrideActive ? scTheme.customTheme ?? customTheme : customTheme;
4945
+ const mergedTheme = React4.useMemo(
4946
+ () => mergeMantineThemeSafe(
4947
+ { ...theme_default, primaryColor: effectivePrimaryColor, primaryShade: effectivePrimaryShade },
4948
+ effectiveCustomTheme,
4949
+ themeOverride
4950
+ ),
4951
+ [effectivePrimaryColor, effectivePrimaryShade, effectiveCustomTheme, themeOverride]
4952
+ );
4953
+ React4.useEffect(() => {
4954
+ document.body.style.fontFamily = game === "rdr3" ? '"Red Dead", sans-serif' : '"Akrobat Regular", sans-serif';
4955
+ }, [game]);
4956
+ const content = isEnvBrowser() ? /* @__PURE__ */ jsxRuntime.jsx(
4957
+ core.BackgroundImage,
4958
+ {
4959
+ w: "100vw",
4960
+ h: "100vh",
4961
+ 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",
4962
+ children
4963
+ }
4964
+ ) : children;
4965
+ return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: dirkQueryClient, children: /* @__PURE__ */ jsxRuntime.jsx(core.MantineProvider, { theme: mergedTheme, defaultColorScheme: "dark", children: /* @__PURE__ */ jsxRuntime.jsxs(DirkErrorBoundary, { children: [
4966
+ content,
4967
+ /* @__PURE__ */ jsxRuntime.jsx(AdminOverlays, {})
4968
+ ] }) }) });
4969
+ }
4970
+ function NavItemButton({
4971
+ icon: Icon,
4972
+ label: label2,
4973
+ active,
4974
+ onClick
4471
4975
  }) {
4472
4976
  const theme2 = core.useMantineTheme();
4473
4977
  const color = theme2.colors[theme2.primaryColor][5];
@@ -4511,8 +5015,8 @@ function ConfigJsonModal({
4511
5015
  const theme2 = core.useMantineTheme();
4512
5016
  const color = theme2.colors[theme2.primaryColor][5];
4513
5017
  const form = useForm();
4514
- const [json, setJson] = React6.useState(() => JSON.stringify(form.values, null, 2));
4515
- const [error2, setError] = React6.useState(null);
5018
+ const [json, setJson] = React4.useState(() => JSON.stringify(form.values, null, 2));
5019
+ const [error2, setError] = React4.useState(null);
4516
5020
  const handleSave = () => {
4517
5021
  try {
4518
5022
  const parsed = JSON.parse(json);
@@ -4541,7 +5045,7 @@ function ConfigJsonModal({
4541
5045
  setError(e.message);
4542
5046
  }
4543
5047
  };
4544
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config JSON", icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
5048
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: locale("cfgpanel_json_title"), icon: lucideReact.Code2, iconColor: color, onClose, width: "60vh", maxHeight: "80vh", zIndex: 200, children: [
4545
5049
  /* @__PURE__ */ jsxRuntime.jsxs(core.Box, { flex: 1, p: "0.8vh", style: { overflowY: "auto" }, children: [
4546
5050
  /* @__PURE__ */ jsxRuntime.jsx(
4547
5051
  core.JsonInput,
@@ -4569,7 +5073,7 @@ function ConfigJsonModal({
4569
5073
  style: { background: "transparent", border: `0.1vh solid ${theme2.colors.dark[6]}`, borderRadius: theme2.radius.xs, padding: `${theme2.spacing.xxs} ${theme2.spacing.xs}`, cursor: "pointer", display: "flex", alignItems: "center", gap: theme2.spacing.xxs },
4570
5074
  children: [
4571
5075
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { color: "rgba(255,255,255,0.4)" }),
4572
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: "Cancel" })
5076
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: locale("cfgpanel_cancel") })
4573
5077
  ]
4574
5078
  }
4575
5079
  ),
@@ -4582,7 +5086,7 @@ function ConfigJsonModal({
4582
5086
  style: { background: core.alpha(color, 0.14), border: `0.1vh solid ${core.alpha(color, 0.4)}`, borderRadius: theme2.radius.xs, padding: `${theme2.spacing.xxs} ${theme2.spacing.xs}`, cursor: "pointer", display: "flex", alignItems: "center", gap: theme2.spacing.xxs },
4583
5087
  children: [
4584
5088
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { color }),
4585
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: color, children: "Apply" })
5089
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: color, children: locale("cfgpanel_apply") })
4586
5090
  ]
4587
5091
  }
4588
5092
  )
@@ -4689,14 +5193,14 @@ function ConfigHistoryModal({
4689
5193
  const { getHistory } = getScriptConfigInstance();
4690
5194
  const theme2 = core.useMantineTheme();
4691
5195
  const color = theme2.colors[theme2.primaryColor][5];
4692
- const [queryInput, setQueryInput] = React6.useState("");
4693
- const [pathInput, setPathInput] = React6.useState("");
4694
- const [adminInput, setAdminInput] = React6.useState("");
4695
- const [query, setQuery] = React6.useState("");
4696
- const [path, setPath] = React6.useState("");
4697
- const [admin, setAdmin] = React6.useState("");
4698
- const [expandedKey, setExpandedKey] = React6.useState(null);
4699
- const filters = React6.useMemo(() => ({ query, path, admin }), [query, path, admin]);
5196
+ const [queryInput, setQueryInput] = React4.useState("");
5197
+ const [pathInput, setPathInput] = React4.useState("");
5198
+ const [adminInput, setAdminInput] = React4.useState("");
5199
+ const [query, setQuery] = React4.useState("");
5200
+ const [path, setPath] = React4.useState("");
5201
+ const [admin, setAdmin] = React4.useState("");
5202
+ const [expandedKey, setExpandedKey] = React4.useState(null);
5203
+ const filters = React4.useMemo(() => ({ query, path, admin }), [query, path, admin]);
4700
5204
  const historyQuery = reactQuery.useInfiniteQuery({
4701
5205
  queryKey: ["scriptConfigHistory", filters],
4702
5206
  initialPageParam: 0,
@@ -4723,7 +5227,7 @@ function ConfigHistoryModal({
4723
5227
  historyQuery.fetchNextPage();
4724
5228
  }
4725
5229
  };
4726
- return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: "Config History", icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
5230
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { title: locale("cfgpanel_history_title"), icon: lucideReact.History, iconColor: color, onClose, width: "88vh", maxHeight: "82vh", zIndex: 260, children: [
4727
5231
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minHeight: 0 }, children: [
4728
5232
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { gap: "xs", p: "sm", style: { borderBottom: `0.1vh solid ${core.alpha(theme2.colors.dark[7], 0.8)}` }, children: [
4729
5233
  /* @__PURE__ */ jsxRuntime.jsx(core.TextInput, { leftSection: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { size: "1.4vh" }), placeholder: "Search path/admin/value", value: queryInput, onChange: (e) => setQueryInput(e.currentTarget.value), size: "xs", style: { flex: 1 } }),
@@ -4766,7 +5270,7 @@ function ConfigHistoryModal({
4766
5270
  style: { background: "transparent", border: `0.1vh solid ${theme2.colors.dark[6]}`, borderRadius: theme2.radius.xs, padding: `${theme2.spacing.xxs} ${theme2.spacing.xs}`, cursor: "pointer", display: "flex", alignItems: "center", gap: theme2.spacing.xxs },
4767
5271
  children: [
4768
5272
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { color: "rgba(255,255,255,0.4)", size: "1.5vh" }),
4769
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: "Close" })
5273
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.05em", c: "rgba(255,255,255,0.4)", children: locale("cfgpanel_close") })
4770
5274
  ]
4771
5275
  }
4772
5276
  ) })
@@ -4791,12 +5295,12 @@ function ConfigPanelInner({
4791
5295
  const theme2 = core.useMantineTheme();
4792
5296
  const color = theme2.colors[theme2.primaryColor][5];
4793
5297
  const version = useSettings((s) => s.resourceVersion);
4794
- const [activeTab, setActiveTab] = React6.useState(navItems[0]?.id ?? "");
4795
- const firstMountRef = React6.useRef(true);
4796
- const [jsonOpen, setJsonOpen] = React6.useState(false);
4797
- const [historyOpen, setHistoryOpen] = React6.useState(false);
4798
- const [resetOpen, setResetOpen] = React6.useState(false);
4799
- const [pendingAction, setPendingAction] = React6.useState(null);
5298
+ const [activeTab, setActiveTab] = useAdminState("__dirkConfigPanel:activeTab", navItems[0]?.id ?? "");
5299
+ const firstMountRef = React4.useRef(true);
5300
+ const [jsonOpen, setJsonOpen] = React4.useState(false);
5301
+ const [historyOpen, setHistoryOpen] = React4.useState(false);
5302
+ const [resetOpen, setResetOpen] = React4.useState(false);
5303
+ const [pendingAction, setPendingAction] = React4.useState(null);
4800
5304
  const changedCount = form.changedCount ?? 0;
4801
5305
  const isDirty = changedCount > 0;
4802
5306
  const goBack = () => fetchNui("CONFIG_PANEL_BACK");
@@ -4807,7 +5311,7 @@ function ConfigPanelInner({
4807
5311
  }
4808
5312
  goBack();
4809
5313
  };
4810
- React6.useEffect(() => {
5314
+ React4.useEffect(() => {
4811
5315
  function handleKeyDown(e) {
4812
5316
  if (e.key !== "Escape") return;
4813
5317
  if (isDirty) {
@@ -4826,16 +5330,16 @@ function ConfigPanelInner({
4826
5330
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: resetOpen && /* @__PURE__ */ jsxRuntime.jsx(
4827
5331
  ConfirmModal,
4828
5332
  {
4829
- title: "Reset to Defaults",
4830
- description: "This will permanently reset ALL config back to the defaults. Every value you have configured will be overwritten. This cannot be undone.",
4831
- confirmLabel: "Reset Config",
5333
+ title: locale("cfgpanel_reset_title"),
5334
+ description: locale("cfgpanel_reset_desc"),
5335
+ confirmLabel: locale("cfgpanel_reset_confirm"),
4832
5336
  confirmText: resetConfirmText,
4833
5337
  onConfirm: async () => {
4834
5338
  setResetOpen(false);
4835
5339
  const result = await resetConfig();
4836
5340
  if (result?.success) {
4837
- const { store } = getScriptConfigInstance();
4838
- form.reinitialize(cloneConfig(store.getState()));
5341
+ const { store: store2 } = getScriptConfigInstance();
5342
+ form.reinitialize(cloneConfig(store2.getState()));
4839
5343
  notifications.notifications.show({
4840
5344
  color: "green",
4841
5345
  title: locale("ConfigResetSuccessTitle"),
@@ -4860,9 +5364,9 @@ function ConfigPanelInner({
4860
5364
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: pendingAction !== null && /* @__PURE__ */ jsxRuntime.jsx(
4861
5365
  ConfirmModal,
4862
5366
  {
4863
- title: "Discard Unsaved Changes?",
4864
- description: pendingAction === "back" ? "You have unsaved changes. Going back now will discard them." : "You have unsaved changes. Closing now will discard them.",
4865
- confirmLabel: pendingAction === "back" ? "Go Back Without Saving" : "Close Without Saving",
5367
+ title: locale("cfgpanel_discard_title"),
5368
+ description: pendingAction === "back" ? locale("cfgpanel_discard_desc_back") : locale("cfgpanel_discard_desc_close"),
5369
+ confirmLabel: pendingAction === "back" ? locale("cfgpanel_discard_confirm_back") : locale("cfgpanel_discard_confirm_close"),
4866
5370
  onConfirm: () => {
4867
5371
  const action = pendingAction;
4868
5372
  setPendingAction(null);
@@ -4900,7 +5404,7 @@ function ConfigPanelInner({
4900
5404
  /* @__PURE__ */ jsxRuntime.jsx(
4901
5405
  framerMotion.motion.button,
4902
5406
  {
4903
- title: "Back to script list",
5407
+ title: locale("cfgpanel_back_title"),
4904
5408
  onClick: handleBack,
4905
5409
  whileHover: { background: core.alpha(color, 0.16), borderColor: core.alpha(color, 0.45) },
4906
5410
  whileTap: { scale: 0.95 },
@@ -4928,7 +5432,7 @@ function ConfigPanelInner({
4928
5432
  /* @__PURE__ */ jsxRuntime.jsx(
4929
5433
  framerMotion.motion.button,
4930
5434
  {
4931
- title: "Undo",
5435
+ title: locale("cfgpanel_undo"),
4932
5436
  onClick: () => form.canBack && form.back(),
4933
5437
  disabled: !form.canBack,
4934
5438
  whileHover: form.canBack ? { background: "rgba(255,255,255,0.07)" } : void 0,
@@ -4940,7 +5444,7 @@ function ConfigPanelInner({
4940
5444
  /* @__PURE__ */ jsxRuntime.jsx(
4941
5445
  framerMotion.motion.button,
4942
5446
  {
4943
- title: "Redo",
5447
+ title: locale("cfgpanel_redo"),
4944
5448
  onClick: () => form.canForward && form.forward(),
4945
5449
  disabled: !form.canForward,
4946
5450
  whileHover: form.canForward ? { background: "rgba(255,255,255,0.07)" } : void 0,
@@ -4952,7 +5456,7 @@ function ConfigPanelInner({
4952
5456
  /* @__PURE__ */ jsxRuntime.jsx(
4953
5457
  framerMotion.motion.button,
4954
5458
  {
4955
- title: isSaving ? "Saving..." : isDirty ? `Save (${changedCount})` : "Save",
5459
+ title: isSaving ? locale("cfgpanel_saving") : isDirty ? `${locale("cfgpanel_save")} (${changedCount})` : locale("cfgpanel_save"),
4956
5460
  onClick: () => isDirty && !isSaving && form.submit(),
4957
5461
  disabled: !isDirty || isSaving,
4958
5462
  whileHover: isDirty && !isSaving ? { background: core.alpha(color, 0.25), borderColor: core.alpha(color, 0.5) } : void 0,
@@ -4964,7 +5468,7 @@ function ConfigPanelInner({
4964
5468
  /* @__PURE__ */ jsxRuntime.jsx(
4965
5469
  framerMotion.motion.button,
4966
5470
  {
4967
- title: "History",
5471
+ title: locale("cfgpanel_history"),
4968
5472
  onClick: () => setHistoryOpen(true),
4969
5473
  whileHover: { background: core.alpha("#22d3ee", 0.16), borderColor: core.alpha("#22d3ee", 0.5) },
4970
5474
  whileTap: { scale: 0.97 },
@@ -4985,7 +5489,7 @@ function ConfigPanelInner({
4985
5489
  style: { width: "100%", background: "transparent", border: `0.1vh solid ${isDirty ? core.alpha("#f97316", 0.35) : core.alpha(theme2.colors.dark[5], 0.3)}`, borderRadius: theme2.radius.xs, padding: "0.65vh 0.8vh", cursor: isDirty ? "pointer" : "default", display: "flex", alignItems: "center", gap: "0.55vh", opacity: isDirty ? 1 : 0.35, transition: "opacity 0.2s" },
4986
5490
  children: [
4987
5491
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { color: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", size: "1.6vh" }),
4988
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", children: "Discard" })
5492
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: isDirty ? "#f97316" : "rgba(255,255,255,0.35)", children: locale("cfgpanel_discard") })
4989
5493
  ]
4990
5494
  }
4991
5495
  ),
@@ -4999,7 +5503,7 @@ function ConfigPanelInner({
4999
5503
  style: { width: "100%", background: core.alpha(color, 0.07), border: `0.1vh solid ${core.alpha(color, 0.28)}`, borderRadius: theme2.radius.xs, padding: "0.65vh 0.8vh", cursor: "pointer", display: "flex", alignItems: "center", gap: "0.55vh" },
5000
5504
  children: [
5001
5505
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code2, { color, size: "1.6vh" }),
5002
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: "Manual Edit" })
5506
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: locale("cfgpanel_manual_edit") })
5003
5507
  ]
5004
5508
  }
5005
5509
  ),
@@ -5012,12 +5516,13 @@ function ConfigPanelInner({
5012
5516
  style: { width: "100%", background: core.alpha("#ef4444", 0.07), border: `0.1vh solid ${core.alpha("#ef4444", 0.25)}`, borderRadius: theme2.radius.xs, padding: "0.65vh 0.8vh", cursor: "pointer", display: "flex", alignItems: "center", gap: "0.55vh" },
5013
5517
  children: [
5014
5518
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { color: "#ef4444", size: "1.6vh" }),
5015
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "#ef4444", children: "Reset Defaults" })
5519
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: "#ef4444", children: locale("cfgpanel_reset_defaults") })
5016
5520
  ]
5017
5521
  }
5018
5522
  ),
5019
5523
  version && /* @__PURE__ */ jsxRuntime.jsxs(core.Text, { size: "0.95vh", c: "dimmed", ta: "center", style: { letterSpacing: "0.04em", opacity: 0.8 }, children: [
5020
- "Version ",
5524
+ locale("cfgpanel_version"),
5525
+ " ",
5021
5526
  version
5022
5527
  ] })
5023
5528
  ] })
@@ -5048,7 +5553,7 @@ function cloneConfig(value) {
5048
5553
  function ServerOnlyFetcher() {
5049
5554
  const { fetchConfig } = getScriptConfigInstance();
5050
5555
  const { reinitialize } = useFormActions();
5051
- React6.useEffect(() => {
5556
+ React4.useEffect(() => {
5052
5557
  let cancelled = false;
5053
5558
  fetchConfig().then((full) => {
5054
5559
  if (!cancelled && full) reinitialize(full);
@@ -5063,13 +5568,13 @@ function ServerOnlyFetcher() {
5063
5568
  var defaultOnClose = () => fetchNui("CLOSE_ADMIN_SECTION");
5064
5569
  function ConfigPanel(props) {
5065
5570
  const { open, onClose = defaultOnClose } = props;
5066
- const { store, updateConfig } = getScriptConfigInstance();
5067
- const [isSaving, setIsSaving] = React6.useState(false);
5571
+ const { store: store2, updateConfig } = getScriptConfigInstance();
5572
+ const [isSaving, setIsSaving] = React4.useState(false);
5068
5573
  if (!open) return null;
5069
- return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: configPanelQueryClient, children: /* @__PURE__ */ jsxRuntime.jsxs(
5574
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs(
5070
5575
  FormProvider,
5071
5576
  {
5072
- initialValues: cloneConfig(store.getState()),
5577
+ initialValues: cloneConfig(store2.getState()),
5073
5578
  onSubmit: async (form) => {
5074
5579
  if (isSaving) return;
5075
5580
  setIsSaving(true);
@@ -5077,7 +5582,7 @@ function ConfigPanel(props) {
5077
5582
  const result = await updateConfig(form.values);
5078
5583
  if (result?.success) {
5079
5584
  form.reinitialize(cloneConfig(form.values));
5080
- configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
5585
+ dirkQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
5081
5586
  useMissingItemsAudit.getState().refresh();
5082
5587
  notifications.notifications.show({
5083
5588
  color: "green",
@@ -5087,7 +5592,7 @@ function ConfigPanel(props) {
5087
5592
  });
5088
5593
  return;
5089
5594
  }
5090
- form.reinitialize(cloneConfig(store.getState()));
5595
+ form.reinitialize(cloneConfig(store2.getState()));
5091
5596
  const err = result?._error || "Unknown";
5092
5597
  console.warn(`[ConfigPanel] config save failed: ${err}`);
5093
5598
  const titleKey = err === "NoPermission" ? "ConfigSaveNoPermissionTitle" : err === "VersionConflict" ? "ConfigSaveVersionConflictTitle" : err === "NotReady" ? "ConfigSaveNotReadyTitle" : "ConfigSaveFailedTitle";
@@ -5118,9 +5623,9 @@ function ConfigPanel(props) {
5118
5623
  }
5119
5624
  var MISSING_COLOR = "#f59e0b";
5120
5625
  function LazyImage({ src, style }) {
5121
- const [visible, setVisible] = React6.useState(false);
5122
- const ref = React6.useRef(null);
5123
- React6.useEffect(() => {
5626
+ const [visible, setVisible] = React4.useState(false);
5627
+ const ref = React4.useRef(null);
5628
+ React4.useEffect(() => {
5124
5629
  const observer = new IntersectionObserver(([entry]) => {
5125
5630
  if (entry.isIntersecting) {
5126
5631
  setVisible(true);
@@ -5135,7 +5640,7 @@ function LazyImage({ src, style }) {
5135
5640
  function SelectItem(props) {
5136
5641
  const invItems = useItems();
5137
5642
  const isMissing = !!props.value && !invItems[props.value];
5138
- const formattedItems = React6.useMemo(() => {
5643
+ const formattedItems = React4.useMemo(() => {
5139
5644
  const seen = /* @__PURE__ */ new Set();
5140
5645
  const list = useItemsList(props.excludeItemNames ?? []).filter((item) => {
5141
5646
  if (seen.has(item.name)) return false;
@@ -5225,9 +5730,9 @@ function PositionPicker(props) {
5225
5730
  } = props;
5226
5731
  const theme2 = core.useMantineTheme();
5227
5732
  const color = theme2.colors[theme2.primaryColor][5];
5228
- const [previewing, setPreviewing] = React6.useState(false);
5229
- const previewingRef = React6.useRef(false);
5230
- React6.useEffect(() => {
5733
+ const [previewing, setPreviewing] = React4.useState(false);
5734
+ const previewingRef = React4.useRef(false);
5735
+ React4.useEffect(() => {
5231
5736
  return () => {
5232
5737
  if (previewingRef.current) {
5233
5738
  fetchNui(stopPreviewEvent, { relativeTo }).catch(() => {
@@ -5565,7 +6070,7 @@ function Vector4DeleteButton({
5565
6070
  }
5566
6071
  );
5567
6072
  }
5568
- var GroupSelectContext = React6.createContext(null);
6073
+ var GroupSelectContext = React4.createContext(null);
5569
6074
  function GroupSelect({
5570
6075
  value,
5571
6076
  onChange,
@@ -5581,9 +6086,12 @@ function filterByType(jobs, gangs, type) {
5581
6086
  return [...jobs, ...gangs];
5582
6087
  }
5583
6088
  function GroupName(props) {
5584
- const ctx = React6.useContext(GroupSelectContext);
6089
+ const ctx = React4.useContext(GroupSelectContext);
5585
6090
  const jobs = useFrameworkGroups((s) => s.jobs);
5586
6091
  const gangs = useFrameworkGroups((s) => s.gangs);
6092
+ React4.useEffect(() => {
6093
+ ensureFrameworkGroups();
6094
+ }, []);
5587
6095
  const inCompound = ctx !== null;
5588
6096
  const currentValue = inCompound ? ctx.value.name : props.value;
5589
6097
  const filterType = inCompound ? ctx.type : props.type;
@@ -5617,12 +6125,15 @@ function GroupName(props) {
5617
6125
  );
5618
6126
  }
5619
6127
  function GroupRank(props) {
5620
- const ctx = React6.useContext(GroupSelectContext);
6128
+ const ctx = React4.useContext(GroupSelectContext);
5621
6129
  if (ctx === null) {
5622
6130
  throw new Error("<GroupRank> must be a child of <GroupSelect>");
5623
6131
  }
5624
6132
  const jobs = useFrameworkGroups((s) => s.jobs);
5625
6133
  const gangs = useFrameworkGroups((s) => s.gangs);
6134
+ React4.useEffect(() => {
6135
+ ensureFrameworkGroups();
6136
+ }, []);
5626
6137
  const all = [...jobs, ...gangs];
5627
6138
  const selectedGroup = all.find((g) => g.name === ctx.value.name) ?? null;
5628
6139
  const grades = selectedGroup?.grades ?? [];
@@ -5653,9 +6164,9 @@ function GroupRank(props) {
5653
6164
  }
5654
6165
  GroupSelect.Name = GroupName;
5655
6166
  GroupSelect.Rank = GroupRank;
5656
- var KeyBindContext = React6.createContext(null);
6167
+ var KeyBindContext = React4.createContext(null);
5657
6168
  function useKeyBindContext() {
5658
- const ctx = React6.useContext(KeyBindContext);
6169
+ const ctx = React4.useContext(KeyBindContext);
5659
6170
  if (!ctx) {
5660
6171
  throw new Error("FiveMKeyBindInput.* must be used inside <FiveMKeyBindInput>");
5661
6172
  }
@@ -5814,7 +6325,7 @@ function AsyncSaveButton({
5814
6325
  style
5815
6326
  }) {
5816
6327
  const theme2 = core.useMantineTheme();
5817
- const [state, setState] = React6.useState("idle");
6328
+ const [state, setState] = React4.useState("idle");
5818
6329
  const handleClick = async () => {
5819
6330
  if (state === "pending") return;
5820
6331
  setState("pending");
@@ -5954,10 +6465,10 @@ function TestBed({
5954
6465
  title = "TestBed",
5955
6466
  placement = "top-left"
5956
6467
  }) {
5957
- const [open, setOpen] = React6.useState(false);
5958
- const itemsRef = React6.useRef(items);
6468
+ const [open, setOpen] = React4.useState(false);
6469
+ const itemsRef = React4.useRef(items);
5959
6470
  itemsRef.current = items;
5960
- React6.useEffect(() => {
6471
+ React4.useEffect(() => {
5961
6472
  if (!isEnvBrowser() || disablePersistence) return;
5962
6473
  const persisted = loadPersistedState(storageKey);
5963
6474
  itemsRef.current.forEach((item) => {
@@ -6294,7 +6805,7 @@ function SwatchTile({
6294
6805
  isPrimary,
6295
6806
  onChange
6296
6807
  }) {
6297
- const [opened, setOpened] = React6.useState(false);
6808
+ const [opened, setOpened] = React4.useState(false);
6298
6809
  return /* @__PURE__ */ jsxRuntime.jsxs(core.Popover, { opened, onChange: setOpened, position: "bottom", withArrow: true, zIndex: 1e4, children: [
6299
6810
  /* @__PURE__ */ jsxRuntime.jsx(core.Popover.Target, { children: /* @__PURE__ */ jsxRuntime.jsx(
6300
6811
  "button",
@@ -6964,9 +7475,9 @@ var NOT_CONFIGURED_TINT = "rgba(255,184,0,0.35)";
6964
7475
  var NOT_CONFIGURED_FILL = "rgba(255,184,0,0.05)";
6965
7476
  function DiscordRoleSelect(props) {
6966
7477
  const { endpoint, label: label2, size = "xs", placeholder, disabled, style, styles } = props;
6967
- const [roles, setRoles] = React6.useState(null);
6968
- const [loading, setLoading] = React6.useState(false);
6969
- const [errorCode, setErrorCode] = React6.useState(null);
7478
+ const [roles, setRoles] = React4.useState(null);
7479
+ const [loading, setLoading] = React4.useState(false);
7480
+ const [errorCode, setErrorCode] = React4.useState(null);
6970
7481
  const refresh = async () => {
6971
7482
  setLoading(true);
6972
7483
  try {
@@ -6992,10 +7503,10 @@ function DiscordRoleSelect(props) {
6992
7503
  setLoading(false);
6993
7504
  }
6994
7505
  };
6995
- React6.useEffect(() => {
7506
+ React4.useEffect(() => {
6996
7507
  refresh();
6997
7508
  }, [endpoint]);
6998
- const rolesById = React6.useMemo(() => {
7509
+ const rolesById = React4.useMemo(() => {
6999
7510
  const map = {};
7000
7511
  for (const r of roles ?? []) map[r.id] = r;
7001
7512
  return map;
@@ -7161,9 +7672,9 @@ function FrameworkHint({ framework }) {
7161
7672
  }
7162
7673
  function AccountSelect(props) {
7163
7674
  const { endpoint, label: label2, size = "xs", placeholder, disabled, style, styles, hideFrameworkHint } = props;
7164
- const [accounts, setAccounts] = React6.useState(null);
7165
- const [framework, setFramework] = React6.useState(void 0);
7166
- const [loading, setLoading] = React6.useState(false);
7675
+ const [accounts, setAccounts] = React4.useState(null);
7676
+ const [framework, setFramework] = React4.useState(void 0);
7677
+ const [loading, setLoading] = React4.useState(false);
7167
7678
  const refresh = async () => {
7168
7679
  setLoading(true);
7169
7680
  try {
@@ -7188,10 +7699,10 @@ function AccountSelect(props) {
7188
7699
  setLoading(false);
7189
7700
  }
7190
7701
  };
7191
- React6.useEffect(() => {
7702
+ React4.useEffect(() => {
7192
7703
  refresh();
7193
7704
  }, [endpoint]);
7194
- const accountsByName = React6.useMemo(() => {
7705
+ const accountsByName = React4.useMemo(() => {
7195
7706
  const map = {};
7196
7707
  for (const a of accounts ?? []) map[a.name] = a;
7197
7708
  if (props.multi) {
@@ -7268,349 +7779,831 @@ function AccountSelect(props) {
7268
7779
  !hideFrameworkHint && /* @__PURE__ */ jsxRuntime.jsx(FrameworkHint, { framework })
7269
7780
  ] });
7270
7781
  }
7271
- function useTornEdges() {
7272
- const game = useSettings((state) => state.game);
7273
- return game === "rdr3" ? "torn-edge-wrapper" : "";
7782
+ var BODY_HIDE_STYLE_ID2 = "dirk-instruction-panel-style";
7783
+ var BODY_HIDE_ATTR2 = "data-dirk-instruction-active";
7784
+ var OVERLAY_ATTR2 = "data-dirk-instruction-overlay";
7785
+ function ensureBodyHideStyle2() {
7786
+ if (document.getElementById(BODY_HIDE_STYLE_ID2)) return;
7787
+ const el = document.createElement("style");
7788
+ el.id = BODY_HIDE_STYLE_ID2;
7789
+ el.textContent = `
7790
+ body[${BODY_HIDE_ATTR2}] > *:not([${OVERLAY_ATTR2}]) {
7791
+ visibility: hidden !important;
7792
+ opacity: 0 !important;
7793
+ pointer-events: none !important;
7794
+ }
7795
+ `;
7796
+ document.head.appendChild(el);
7274
7797
  }
7275
- function TornEdgeSVGFilter() {
7798
+ function InstructionPanel({
7799
+ visible,
7800
+ title,
7801
+ hint,
7802
+ keys,
7803
+ icon: Icon = lucideReact.MapPin,
7804
+ hideRestOfAdmin = true
7805
+ }) {
7806
+ const theme2 = core.useMantineTheme();
7807
+ const pc = theme2.colors[theme2.primaryColor];
7808
+ React4.useEffect(() => {
7809
+ if (!visible || !hideRestOfAdmin) return;
7810
+ ensureBodyHideStyle2();
7811
+ document.body.setAttribute(BODY_HIDE_ATTR2, "");
7812
+ return () => {
7813
+ document.body.removeAttribute(BODY_HIDE_ATTR2);
7814
+ };
7815
+ }, [visible, hideRestOfAdmin]);
7816
+ if (!visible) return null;
7817
+ return reactDom.createPortal(
7818
+ /* @__PURE__ */ jsxRuntime.jsx(
7819
+ "div",
7820
+ {
7821
+ ...{ [OVERLAY_ATTR2]: "" },
7822
+ style: { position: "fixed", inset: 0, pointerEvents: "none", zIndex: 1e4 },
7823
+ children: /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: /* @__PURE__ */ jsxRuntime.jsx(
7824
+ framerMotion.motion.div,
7825
+ {
7826
+ initial: { opacity: 0, y: 12, scale: 0.92 },
7827
+ animate: { opacity: 1, y: 0, scale: 1 },
7828
+ exit: { opacity: 0, y: 12, scale: 0.92 },
7829
+ transition: { duration: 0.25, ease: "easeInOut" },
7830
+ style: { position: "absolute", bottom: "3vh", right: "3vh", userSelect: "none" },
7831
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
7832
+ core.Flex,
7833
+ {
7834
+ direction: "column",
7835
+ gap: "0.8vh",
7836
+ style: {
7837
+ background: core.alpha(theme2.colors.dark[9], 0.55),
7838
+ border: "0.1vh solid rgba(255,255,255,0.07)",
7839
+ borderRadius: theme2.radius.sm,
7840
+ boxShadow: "0 0.74vh 2.96vh rgba(0,0,0,0.5)",
7841
+ padding: "1.4vh 1.6vh",
7842
+ minWidth: "22vh",
7843
+ maxWidth: "28vh"
7844
+ },
7845
+ children: [
7846
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "0.6vh", children: [
7847
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: "1.6vh", color: pc[6], strokeWidth: 2.5 }),
7848
+ /* @__PURE__ */ jsxRuntime.jsx(
7849
+ core.Text,
7850
+ {
7851
+ style: {
7852
+ fontFamily: "Akrobat Bold, sans-serif",
7853
+ fontSize: "1.7vh",
7854
+ fontWeight: 700,
7855
+ letterSpacing: "0.14em",
7856
+ textTransform: "uppercase",
7857
+ color: pc[6],
7858
+ textShadow: `0 0 0.8vh ${core.alpha(pc[7], 0.5)}, 0 0 1.6vh ${core.alpha(pc[9], 0.3)}`
7859
+ },
7860
+ children: title
7861
+ }
7862
+ )
7863
+ ] }),
7864
+ (hint || keys && keys.length > 0) && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "0.1vh", background: "rgba(255,255,255,0.06)", margin: "0.1vh 0" } }),
7865
+ hint && /* @__PURE__ */ jsxRuntime.jsx(
7866
+ core.Text,
7867
+ {
7868
+ style: {
7869
+ fontFamily: "Akrobat Bold, sans-serif",
7870
+ fontSize: "1.05vh",
7871
+ color: "rgba(255,255,255,0.45)",
7872
+ letterSpacing: "0.06em",
7873
+ textTransform: "uppercase",
7874
+ lineHeight: 1.4
7875
+ },
7876
+ children: hint
7877
+ }
7878
+ ),
7879
+ keys && keys.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7880
+ hint && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "0.1vh", background: "rgba(255,255,255,0.06)", margin: "0.1vh 0" } }),
7881
+ /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { direction: "column", gap: "0.5vh", children: keys.map((k, i) => /* @__PURE__ */ jsxRuntime.jsx(InstructionKeyRow, { keyLabel: k.key, action: k.action }, i)) })
7882
+ ] })
7883
+ ]
7884
+ }
7885
+ )
7886
+ },
7887
+ "instruction-card"
7888
+ ) })
7889
+ }
7890
+ ),
7891
+ document.body
7892
+ );
7893
+ }
7894
+ function renderKeyContent(keyLabel) {
7895
+ const normalized = keyLabel.trim().toUpperCase();
7896
+ if (normalized === "LMB") return /* @__PURE__ */ jsxRuntime.jsx(MouseIcon, { side: "left" });
7897
+ if (normalized === "RMB") return /* @__PURE__ */ jsxRuntime.jsx(MouseIcon, { side: "right" });
7898
+ if (normalized === "BACKSPACE" || keyLabel === "\u232B") return /* @__PURE__ */ jsxRuntime.jsx(BackspaceIcon, {});
7276
7899
  return /* @__PURE__ */ jsxRuntime.jsx(
7277
- "svg",
7900
+ core.Text,
7278
7901
  {
7279
- style: { position: "absolute", width: 0, height: 0, pointerEvents: "none" },
7280
- "aria-hidden": "true",
7281
- children: /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("filter", { id: "torn-edge-filter", x: "-50%", y: "-50%", width: "200%", height: "200%", children: [
7282
- /* @__PURE__ */ jsxRuntime.jsx(
7283
- "feTurbulence",
7284
- {
7285
- type: "fractalNoise",
7286
- baseFrequency: "0.018 0.022",
7287
- numOctaves: "5",
7288
- seed: "9",
7289
- result: "noise1"
7290
- }
7291
- ),
7292
- /* @__PURE__ */ jsxRuntime.jsx(
7293
- "feTurbulence",
7294
- {
7295
- type: "fractalNoise",
7296
- baseFrequency: "0.08 0.12",
7297
- numOctaves: "2",
7298
- seed: "3",
7299
- result: "noise2"
7300
- }
7301
- ),
7302
- /* @__PURE__ */ jsxRuntime.jsx("feBlend", { in: "noise1", in2: "noise2", mode: "multiply", result: "combinedNoise" }),
7303
- /* @__PURE__ */ jsxRuntime.jsx(
7304
- "feDisplacementMap",
7305
- {
7306
- in: "SourceGraphic",
7307
- in2: "combinedNoise",
7308
- scale: "52",
7309
- xChannelSelector: "R",
7310
- yChannelSelector: "G",
7311
- result: "displaced"
7312
- }
7313
- ),
7314
- /* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { stdDeviation: "0.8", in: "displaced", result: "blurred" }),
7315
- /* @__PURE__ */ jsxRuntime.jsx("feComponentTransfer", { in: "blurred", result: "alphaFade", children: /* @__PURE__ */ jsxRuntime.jsx("feFuncA", { type: "gamma", amplitude: "1", exponent: "1.3", offset: "-0.05" }) }),
7316
- /* @__PURE__ */ jsxRuntime.jsx("feMorphology", { operator: "erode", radius: "0.4", in: "alphaFade", result: "eroded" }),
7317
- /* @__PURE__ */ jsxRuntime.jsx("feMerge", { children: /* @__PURE__ */ jsxRuntime.jsx("feMergeNode", { in: "eroded" }) })
7318
- ] }) })
7902
+ style: {
7903
+ fontFamily: "Akrobat Bold, sans-serif",
7904
+ fontSize: "1.2vh",
7905
+ color: "rgba(255,255,255,0.85)",
7906
+ lineHeight: 1,
7907
+ padding: "0 0.3vh"
7908
+ },
7909
+ children: keyLabel
7319
7910
  }
7320
7911
  );
7321
7912
  }
7322
- var label = {
7323
- fontSize: "var(--mantine-font-size-xs)",
7324
- fontFamily: "Akrobat Bold",
7325
- letterSpacing: "0.05em",
7326
- textTransform: "uppercase"
7327
- };
7328
- var error = {
7329
- fontSize: "var(--mantine-font-size-xs)",
7330
- fontFamily: "Akrobat Regular"
7331
- };
7332
- var description = {
7333
- fontSize: "var(--mantine-font-size-xs)"
7334
- };
7335
- var genericInputStyles = {
7336
- styles: {
7337
- label,
7338
- error,
7339
- description,
7340
- input: {
7341
- background: "rgba(255,255,255,0.04)",
7342
- border: "0.1vh solid rgba(255,255,255,0.08)",
7343
- color: "rgba(255,255,255,0.85)",
7344
- minHeight: "4vh"
7345
- }
7346
- }
7347
- };
7348
- var theme = core.createTheme({
7349
- primaryColor: "dirk",
7350
- primaryShade: 9,
7351
- defaultRadius: "xs",
7352
- fontFamily: "Akrobat Regular, sans-serif",
7353
- radius: {
7354
- xxs: "0.3vh",
7355
- xs: "0.5vh",
7356
- sm: "0.75vh",
7357
- md: "1vh",
7358
- lg: "1.5vh",
7359
- xl: "2vh",
7360
- xxl: "3vh"
7361
- },
7362
- fontSizes: {
7363
- xxs: "1.2vh",
7364
- xs: "1.5vh",
7365
- sm: "1.8vh",
7366
- md: "2.2vh",
7367
- lg: "2.8vh",
7368
- xl: "3.3vh",
7369
- xxl: "3.8vh"
7370
- },
7371
- lineHeights: {
7372
- xxs: "1.4vh",
7373
- xs: "1.8vh",
7374
- sm: "2.2vh",
7375
- md: "2.8vh",
7376
- lg: "3.3vh",
7377
- xl: "3.8vh"
7378
- },
7379
- spacing: {
7380
- xxs: "0.5vh",
7381
- xs: "0.75vh",
7382
- sm: "1.5vh",
7383
- md: "2vh",
7384
- lg: "3vh",
7385
- xl: "4vh",
7386
- xxl: "5vh"
7387
- },
7388
- components: {
7389
- Progress: {
7390
- styles: {
7391
- label: {
7392
- fontFamily: "Akrobat Bold",
7393
- letterSpacing: "0.05em",
7913
+ function InstructionKeyRow({ keyLabel, action }) {
7914
+ const normalized = keyLabel.trim().toUpperCase();
7915
+ const isIconKey = normalized === "LMB" || normalized === "RMB" || normalized === "BACKSPACE" || keyLabel === "\u232B";
7916
+ const minWidth = isIconKey ? "2.6vh" : "2.4vh";
7917
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "0.6vh", children: [
7918
+ /* @__PURE__ */ jsxRuntime.jsx(
7919
+ "div",
7920
+ {
7921
+ style: {
7922
+ minWidth,
7923
+ height: "2.4vh",
7924
+ padding: "0 0.4vh",
7925
+ borderRadius: "0.3vh",
7926
+ border: "0.15vh solid rgba(255,255,255,0.35)",
7927
+ background: "rgba(255,255,255,0.06)",
7928
+ display: "flex",
7929
+ alignItems: "center",
7930
+ justifyContent: "center",
7931
+ opacity: 0.85,
7932
+ filter: "drop-shadow(0 0 0.3vh rgba(0,0,0,0.5))",
7933
+ flexShrink: 0,
7934
+ boxSizing: "border-box"
7935
+ },
7936
+ children: renderKeyContent(keyLabel)
7937
+ }
7938
+ ),
7939
+ /* @__PURE__ */ jsxRuntime.jsx(
7940
+ core.Text,
7941
+ {
7942
+ style: {
7943
+ fontFamily: "Akrobat Bold, sans-serif",
7944
+ fontSize: "1.05vh",
7945
+ color: "rgba(255,255,255,0.45)",
7946
+ letterSpacing: "0.06em",
7394
7947
  textTransform: "uppercase"
7395
7948
  },
7396
- root: {
7397
- backgroundColor: "rgba(77, 77, 77, 0.4)"
7398
- }
7949
+ children: action
7399
7950
  }
7400
- },
7401
- Input: genericInputStyles,
7402
- TextInput: genericInputStyles,
7403
- NumberInput: genericInputStyles,
7404
- Select: genericInputStyles,
7405
- MultiSelect: genericInputStyles,
7406
- Textarea: genericInputStyles,
7407
- ColorInput: genericInputStyles,
7408
- DateInput: genericInputStyles,
7409
- Pill: {
7410
- styles: (theme2) => ({
7411
- root: {
7412
- display: "inline-flex",
7413
- alignItems: "center",
7414
- justifyContent: "space-between",
7415
- backgroundColor: "rgba(76, 76, 76, 0.3)",
7416
- height: "fit-content",
7417
- textTransform: "uppercase",
7418
- letterSpacing: "0.05em",
7419
- fontFamily: "Akrobat Bold",
7420
- fontSize: "1.25vh",
7421
- borderRadius: theme2.defaultRadius,
7422
- paddingBottom: "0.5vh",
7423
- paddingTop: "0.5vh"
7424
- }
7425
- })
7951
+ )
7952
+ ] });
7953
+ }
7954
+ function MouseIcon({ side }) {
7955
+ const stroke = "rgba(255,255,255,0.85)";
7956
+ const fillActive = "rgba(255,255,255,0.85)";
7957
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 22", width: "1.4vh", height: "1.9vh", fill: "none", stroke, strokeWidth: "1.4", strokeLinejoin: "round", children: [
7958
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "1", width: "14", height: "20", rx: "6" }),
7959
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "8", y1: "1", x2: "8", y2: "9" }),
7960
+ /* @__PURE__ */ jsxRuntime.jsx(
7961
+ "path",
7962
+ {
7963
+ 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",
7964
+ fill: fillActive,
7965
+ stroke: "none"
7966
+ }
7967
+ )
7968
+ ] });
7969
+ }
7970
+ function BackspaceIcon() {
7971
+ const stroke = "rgba(255,255,255,0.85)";
7972
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 22 16", width: "1.7vh", height: "1.3vh", fill: "none", stroke, strokeWidth: "1.4", strokeLinejoin: "round", strokeLinecap: "round", children: [
7973
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 21 2 L 8 2 L 2 8 L 8 14 L 21 14 Z" }),
7974
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "11", y1: "6", x2: "16", y2: "11" }),
7975
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "6", x2: "11", y2: "11" })
7976
+ ] });
7977
+ }
7978
+ function WorldPositionPicker({ value, onChange, compact, setOnly, gotoOnly }) {
7979
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7980
+ !gotoOnly && /* @__PURE__ */ jsxRuntime.jsx(WorldPositionSetButton2, { value, onChange, compact }),
7981
+ !setOnly && /* @__PURE__ */ jsxRuntime.jsx(WorldPositionGotoButton2, { value, compact })
7982
+ ] });
7983
+ }
7984
+ function WorldPositionSetButton2({
7985
+ value,
7986
+ onChange,
7987
+ compact
7988
+ }) {
7989
+ const theme2 = core.useMantineTheme();
7990
+ const color = theme2.colors[theme2.primaryColor][5];
7991
+ const begin = useAdminToolStore((s) => s.begin);
7992
+ const onClick = async () => {
7993
+ const instructions = {
7994
+ title: locale("PickPositionTitle") || "Pick Position",
7995
+ hint: locale("PickPositionHint") || "Walk to where you want this set",
7996
+ keys: [
7997
+ { key: "E", action: locale("Set") || "Set" },
7998
+ { key: "\u232B", action: locale("Cancel") || "Cancel" }
7999
+ ]
8000
+ };
8001
+ const pendingResult = begin({ id: "capturePosition", ...instructions });
8002
+ fetchNui("ADMIN_TOOL_BEGIN", { id: "capturePosition", instructions }).catch(() => {
8003
+ useAdminToolStore.getState().cancelActive();
8004
+ });
8005
+ const result = await pendingResult;
8006
+ if (result) onChange(result);
8007
+ };
8008
+ return /* @__PURE__ */ jsxRuntime.jsx(core.Tooltip, { label: locale("SetWorldTooltip"), position: "top", withArrow: true, withinPortal: true, zIndex: 2e3, children: /* @__PURE__ */ jsxRuntime.jsxs(
8009
+ framerMotion.motion.button,
8010
+ {
8011
+ onClick,
8012
+ whileHover: { background: core.alpha(color, 0.18) },
8013
+ whileTap: { scale: 0.95 },
8014
+ style: {
8015
+ background: core.alpha(color, 0.1),
8016
+ border: `0.1vh solid ${core.alpha(color, 0.35)}`,
8017
+ borderRadius: theme2.radius.xs,
8018
+ padding: compact ? "0.25vh 0.6vh" : "0.5vh 0.8vh",
8019
+ cursor: "pointer",
8020
+ display: "flex",
8021
+ alignItems: "center",
8022
+ gap: compact ? "0.3vh" : "0.4vh"
8023
+ },
8024
+ children: [
8025
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Crosshair, { size: compact ? "1.1vh" : "1.3vh", color }),
8026
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: locale("Set") })
8027
+ ]
7426
8028
  }
7427
- },
7428
- colors: {
7429
- dirk: [
7430
- "#ffffff",
7431
- "#f3fce9",
7432
- "#dbf5bd",
7433
- "#c3ee91",
7434
- "#ace765",
7435
- "#94e039",
7436
- "#7ac61f",
7437
- "#5f9a18",
7438
- "#29420a",
7439
- "#446e11"
7440
- ]
7441
- }
7442
- });
7443
- var theme_default = theme;
7444
-
7445
- // src/utils/mergeMantineTheme.ts
7446
- var isValidColorScale = (v) => Array.isArray(v) && v.length === 10 && v.every((shade) => typeof shade === "string");
7447
- function mergeMantineThemeSafe(base, custom, override) {
7448
- const colors = { ...base.colors };
7449
- if (custom && isValidColorScale(custom)) {
7450
- colors["custom"] = custom;
7451
- } else if (!colors["custom"]) {
7452
- const fallback = base.colors && base.colors.dirk;
7453
- if (fallback && isValidColorScale(fallback)) {
7454
- colors["custom"] = fallback;
8029
+ ) });
8030
+ }
8031
+ function WorldPositionGotoButton2({
8032
+ value,
8033
+ compact
8034
+ }) {
8035
+ const theme2 = core.useMantineTheme();
8036
+ const color = theme2.colors[theme2.primaryColor][5];
8037
+ const onClick = () => {
8038
+ fetchNui("ADMIN_TOOL_INVOKE", { id: "gotoCoord", value }).catch(() => {
8039
+ });
8040
+ };
8041
+ return /* @__PURE__ */ jsxRuntime.jsx(core.Tooltip, { label: locale("GotoWorldTooltip"), position: "top", withArrow: true, withinPortal: true, zIndex: 2e3, children: /* @__PURE__ */ jsxRuntime.jsxs(
8042
+ framerMotion.motion.button,
8043
+ {
8044
+ onClick,
8045
+ whileHover: { background: core.alpha(color, 0.18) },
8046
+ whileTap: { scale: 0.95 },
8047
+ style: {
8048
+ background: core.alpha(color, 0.1),
8049
+ border: `0.1vh solid ${core.alpha(color, 0.35)}`,
8050
+ borderRadius: theme2.radius.xs,
8051
+ padding: compact ? "0.25vh 0.6vh" : "0.5vh 0.8vh",
8052
+ cursor: "pointer",
8053
+ display: "flex",
8054
+ alignItems: "center",
8055
+ gap: compact ? "0.3vh" : "0.4vh"
8056
+ },
8057
+ children: [
8058
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MapPin, { size: compact ? "1.1vh" : "1.3vh", color }),
8059
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: locale("Goto") })
8060
+ ]
7455
8061
  }
7456
- }
8062
+ ) });
8063
+ }
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
+ });
7457
8097
  return {
7458
- ...base,
7459
- ...override,
7460
- colors: {
7461
- ...colors,
7462
- ...override?.colors ?? {}
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 });
7463
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
+ ] });
7464
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
+ );
7465
8207
  }
7466
- var DirkErrorBoundary = class extends React6__default.default.Component {
7467
- constructor() {
7468
- super(...arguments);
7469
- __publicField(this, "state", { error: null, stack: void 0 });
7470
- }
7471
- static getDerivedStateFromError(error2) {
7472
- return { error: error2 };
7473
- }
7474
- componentDidCatch(error2, info) {
7475
- console.group("\u{1F525} Dirk UI Crash");
7476
- console.error("Error:", error2);
7477
- console.error("Component Stack:", info.componentStack);
7478
- console.groupEnd();
7479
- }
7480
- render() {
7481
- if (!this.state.error) return this.props.children;
7482
- return /* @__PURE__ */ jsxRuntime.jsx(
7483
- core.Box,
7484
- {
7485
- style: {
7486
- position: "fixed",
7487
- inset: 0,
7488
- width: "100vw",
7489
- height: "100vh",
7490
- background: "rgba(10, 10, 12, 0.92)",
7491
- display: "flex",
7492
- alignItems: "center",
7493
- justifyContent: "center",
7494
- padding: "2rem",
7495
- zIndex: 999999
7496
- },
7497
- children: /* @__PURE__ */ jsxRuntime.jsx(
7498
- core.Box,
7499
- {
7500
- maw: 900,
7501
- w: "100%",
7502
- p: "lg",
7503
- style: {
7504
- background: "rgba(20,20,24,0.75)",
7505
- border: "1px solid rgba(255,255,255,0.08)",
7506
- borderRadius: "10px",
7507
- boxShadow: "0 10px 40px rgba(0,0,0,0.6)"
7508
- },
7509
- children: /* @__PURE__ */ jsxRuntime.jsxs(core.Stack, { gap: "sm", children: [
7510
- /* @__PURE__ */ jsxRuntime.jsx(core.Title, { order: 2, c: "red.5", children: "Dirk UI Crash" }),
7511
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { c: "dimmed", size: "sm", children: "The interface encountered a fatal error and stopped rendering." }),
7512
- /* @__PURE__ */ jsxRuntime.jsx(core.Code, { block: true, style: { maxHeight: 150, overflow: "auto" }, children: this.state.error?.message }),
7513
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xs", c: "dimmed", children: "Check console for full stack trace" })
7514
- ] })
7515
- }
7516
- )
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
7517
8221
  }
7518
- );
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}`);
7519
8230
  }
7520
- };
7521
- fontawesomeSvgCore.library.add(freeSolidSvgIcons.fas, freeRegularSvgIcons.far, freeBrandsSvgIcons.fab);
7522
- function DirkProvider({ children, overideResourceName, themeOverride }) {
7523
- const {
7524
- primaryColor,
7525
- primaryShade,
7526
- customTheme,
7527
- game
7528
- } = useSettings();
7529
- localeStore((s) => s.locales);
7530
- const [scTheme, setScTheme] = React6.useState(null);
7531
- React6.useLayoutEffect(() => {
7532
- useSettings.setState({
7533
- overideResourceName
8231
+ return parts.join(" \xB7 ");
8232
+ }
8233
+ function usePickDoor() {
8234
+ const begin = useAdminToolStore((s) => s.begin);
8235
+ return async () => {
8236
+ const pending = begin({
8237
+ id: "pickDoor",
8238
+ title: locale("PickDoorTitle"),
8239
+ hint: locale("PickDoorHint"),
8240
+ keys: [
8241
+ { key: "LMB", action: locale("Toggle") },
8242
+ { key: "E", action: locale("Confirm") },
8243
+ { key: "BACKSPACE", action: locale("Cancel") }
8244
+ ]
7534
8245
  });
7535
- }, [overideResourceName]);
7536
- React6.useEffect(() => {
7537
- fetchNui("NUI_READY").catch(() => {
8246
+ fetchNui("ADMIN_TOOL_BEGIN", { id: "pickDoor" }).catch(() => {
8247
+ useAdminToolStore.getState().cancelActive();
7538
8248
  });
7539
- Promise.all([
7540
- fetchNui("GET_SETTINGS"),
7541
- fetchNui("GET_RESOURCE_VERSION", void 0, { version: "dev" })
7542
- ]).then(([data, resourceInfo]) => {
7543
- useSettings.setState({
7544
- ...data,
7545
- resourceVersion: resourceInfo?.version || "dev"
7546
- });
7547
- }).catch((err) => {
7548
- console.error("Failed to fetch initial settings within dirk-cfx-react:", err);
7549
- });
7550
- }, []);
7551
- useNuiEvent("UPDATE_DIRK_LIB_SETTINGS", (data) => {
7552
- if (!data || typeof data !== "object") return;
7553
- useSettings.setState(data);
7554
- });
7555
- useNuiEvent(
7556
- "UPDATE_SCRIPT_CONFIG",
7557
- (data) => {
7558
- if (!data || !data.config || typeof data.config !== "object") return;
7559
- try {
7560
- const inst = getScriptConfigInstance();
7561
- inst.store.setState((prev) => ({ ...prev, ...data.config }));
7562
- } catch {
7563
- }
8249
+ return await pending;
8250
+ };
8251
+ }
8252
+ function DoorPickerButton({
8253
+ onPick,
8254
+ compact,
8255
+ label: label2,
8256
+ tooltip
8257
+ }) {
8258
+ const theme2 = core.useMantineTheme();
8259
+ const color = theme2.colors[theme2.primaryColor][5];
8260
+ const pickDoor = usePickDoor();
8261
+ const onClick = async () => {
8262
+ const picked = await pickDoor();
8263
+ if (picked && picked.doors.length > 0) onPick(picked);
8264
+ };
8265
+ return /* @__PURE__ */ jsxRuntime.jsx(
8266
+ core.Tooltip,
8267
+ {
8268
+ label: tooltip ?? locale("PickDoorTooltip"),
8269
+ position: "top",
8270
+ withArrow: true,
8271
+ withinPortal: true,
8272
+ zIndex: 2e3,
8273
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
8274
+ framerMotion.motion.button,
8275
+ {
8276
+ onClick,
8277
+ whileHover: { background: core.alpha(color, 0.18) },
8278
+ whileTap: { scale: 0.95 },
8279
+ style: {
8280
+ background: core.alpha(color, 0.1),
8281
+ border: `0.1vh solid ${core.alpha(color, 0.35)}`,
8282
+ borderRadius: theme2.radius.xs,
8283
+ padding: compact ? "0.25vh 0.6vh" : "0.5vh 0.8vh",
8284
+ cursor: "pointer",
8285
+ display: "flex",
8286
+ alignItems: "center",
8287
+ gap: compact ? "0.3vh" : "0.4vh"
8288
+ },
8289
+ children: [
8290
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DoorOpen, { size: compact ? "1.1vh" : "1.3vh", color }),
8291
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: label2 ?? locale("PickDoor") })
8292
+ ]
8293
+ }
8294
+ )
7564
8295
  }
7565
8296
  );
7566
- React6.useEffect(() => {
7567
- let unsubscribe;
7568
- try {
7569
- const inst = getScriptConfigInstance();
7570
- setScTheme(inst.store.getState()?.theme ?? null);
7571
- const subscribable = inst.store;
7572
- if (typeof subscribable.subscribe === "function") {
7573
- unsubscribe = subscribable.subscribe((s) => {
7574
- setScTheme(s?.theme ?? null);
7575
- });
7576
- }
7577
- inst.fetchConfig?.().then((full) => {
7578
- if (full && typeof full === "object") {
7579
- setScTheme(full.theme ?? null);
7580
- }
7581
- }).catch(() => {
7582
- });
7583
- } catch {
8297
+ }
8298
+ var MapImpl = React4.memo(({
8299
+ children,
8300
+ initialZoom = 2,
8301
+ initialCenter = gameToMap(0, 0),
8302
+ mapStyle = "game"
8303
+ }) => {
8304
+ const mapRef = React4.useRef(null);
8305
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8306
+ reactLeaflet.MapContainer,
8307
+ {
8308
+ maxBoundsViscosity: 1,
8309
+ preferCanvas: true,
8310
+ zoom: Math.round(initialZoom),
8311
+ zoomSnap: 1,
8312
+ zoomDelta: 1,
8313
+ zoomControl: false,
8314
+ crs: leaflet.CRS.Simple,
8315
+ style: {
8316
+ height: "100%",
8317
+ width: "100%",
8318
+ overflow: "hidden",
8319
+ outline: "none !important",
8320
+ border: "none !important",
8321
+ boxShadow: "none !important",
8322
+ backgroundColor: "#384950"
8323
+ },
8324
+ center: initialCenter,
8325
+ attributionControl: false,
8326
+ doubleClickZoom: false,
8327
+ inertia: false,
8328
+ zoomAnimation: false,
8329
+ ref: mapRef,
8330
+ maxBounds: [[-250, -250], [250, 250]],
8331
+ children: [
8332
+ /* @__PURE__ */ jsxRuntime.jsx(MapLayer, { mapLayer: mapStyle }),
8333
+ children
8334
+ ]
7584
8335
  }
8336
+ );
8337
+ });
8338
+ MapImpl.displayName = "DirkMap";
8339
+ var Map2 = MapImpl;
8340
+ var MapLayer = ({ mapLayer }) => {
8341
+ const map = reactLeaflet.useMap();
8342
+ const layerRef = React4.useRef(null);
8343
+ React4.useEffect(() => {
8344
+ if (layerRef.current) {
8345
+ map.removeLayer(layerRef.current);
8346
+ }
8347
+ const layer = leaflet.tileLayer(
8348
+ `https://s.rsg.sc/sc/images/games/GTAV/map/${mapLayer}/{z}/{x}/{y}.jpg`,
8349
+ {
8350
+ maxZoom: 6,
8351
+ minZoom: 4,
8352
+ bounds: leaflet.latLngBounds(leaflet.latLng(0, 128), leaflet.latLng(-192, 0)),
8353
+ tileSize: 256,
8354
+ updateWhenZooming: false,
8355
+ keepBuffer: 2,
8356
+ opacity: 0.75
8357
+ }
8358
+ );
8359
+ layer.addTo(map);
8360
+ layerRef.current = layer;
7585
8361
  return () => {
7586
- unsubscribe?.();
8362
+ if (layerRef.current) {
8363
+ map.removeLayer(layerRef.current);
8364
+ }
7587
8365
  };
7588
- }, []);
7589
- const overrideActive = scTheme?.useOverride === true;
7590
- const effectivePrimaryColor = overrideActive ? scTheme.primaryColor ?? primaryColor : primaryColor;
7591
- const effectivePrimaryShade = overrideActive ? scTheme.primaryShade ?? primaryShade : primaryShade;
7592
- const effectiveCustomTheme = overrideActive ? scTheme.customTheme ?? customTheme : customTheme;
7593
- const mergedTheme = React6.useMemo(
7594
- () => mergeMantineThemeSafe(
7595
- { ...theme_default, primaryColor: effectivePrimaryColor, primaryShade: effectivePrimaryShade },
7596
- effectiveCustomTheme,
7597
- themeOverride
7598
- ),
7599
- [effectivePrimaryColor, effectivePrimaryShade, effectiveCustomTheme, themeOverride]
8366
+ }, [mapLayer, map]);
8367
+ return null;
8368
+ };
8369
+ function ZoomControls() {
8370
+ const theme2 = core.useMantineTheme();
8371
+ const map = reactLeaflet.useMap();
8372
+ const buttons = [
8373
+ { Icon: lucideReact.Plus, fn: () => map.zoomIn() },
8374
+ { Icon: lucideReact.Minus, fn: () => map.zoomOut() }
8375
+ ];
8376
+ return /* @__PURE__ */ jsxRuntime.jsx(
8377
+ framerMotion.motion.div,
8378
+ {
8379
+ style: {
8380
+ position: "absolute",
8381
+ right: "2vh",
8382
+ top: "2vh",
8383
+ display: "flex",
8384
+ flexDirection: "column",
8385
+ zIndex: 999999,
8386
+ boxShadow: `0 0 1vh ${core.alpha(theme2.colors.dark[9], 0.85)}`,
8387
+ background: core.alpha(theme2.colors.dark[9], 0.85),
8388
+ borderRadius: theme2.radius.xs
8389
+ },
8390
+ initial: { opacity: 0, y: -20 },
8391
+ animate: { opacity: 1, y: 0 },
8392
+ exit: { opacity: 0, y: -20 },
8393
+ children: buttons.map(({ Icon, fn }, i) => /* @__PURE__ */ jsxRuntime.jsx(
8394
+ framerMotion.motion.div,
8395
+ {
8396
+ whileHover: { scale: 1.1, filter: "brightness(1.5)" },
8397
+ onClick: fn,
8398
+ style: {
8399
+ padding: theme2.spacing.xs,
8400
+ cursor: "pointer",
8401
+ display: "flex"
8402
+ },
8403
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 34, color: theme2.colors.gray[5] })
8404
+ },
8405
+ i
8406
+ ))
8407
+ }
7600
8408
  );
7601
- React6.useEffect(() => {
7602
- document.body.style.fontFamily = game === "rdr3" ? '"Red Dead", sans-serif' : '"Akrobat Regular", sans-serif';
7603
- }, [game]);
7604
- const content = isEnvBrowser() ? /* @__PURE__ */ jsxRuntime.jsx(
7605
- core.BackgroundImage,
8409
+ }
8410
+ var DEFAULT_SPRITE = 162;
8411
+ var DEFAULT_COLOR = 5;
8412
+ function BlipMarker({
8413
+ position,
8414
+ sprite,
8415
+ color,
8416
+ scale = 1,
8417
+ onClick,
8418
+ selected,
8419
+ disabled,
8420
+ fallbackSprite = DEFAULT_SPRITE,
8421
+ fallbackColor = DEFAULT_COLOR
8422
+ }) {
8423
+ const [hovered, setHovered] = React4.useState(false);
8424
+ const mapCoords = React4.useMemo(() => gameToMap(position.x, position.y), [position.x, position.y]);
8425
+ const effectiveSprite = sprite ?? fallbackSprite;
8426
+ const effectiveColor = color ?? fallbackColor;
8427
+ const url = blipUrlForSprite(effectiveSprite);
8428
+ const colorHex = getBlipColor(effectiveColor)?.hex ?? "#ffffff";
8429
+ const handleClick = (e) => {
8430
+ e.originalEvent?.stopPropagation?.();
8431
+ if (disabled) return;
8432
+ onClick?.();
8433
+ };
8434
+ const baseSize = 1.8 * scale;
8435
+ const size = `${baseSize}vh`;
8436
+ const ringSize = `${baseSize * 1.6}vh`;
8437
+ return /* @__PURE__ */ jsxRuntime.jsx(
8438
+ reactLeafletComponentMarker.Marker,
7606
8439
  {
7607
- w: "100vw",
7608
- h: "100vh",
7609
- 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",
7610
- children
8440
+ position: mapCoords,
8441
+ eventHandlers: onClick ? { click: handleClick } : void 0,
8442
+ icon: /* @__PURE__ */ jsxRuntime.jsxs(
8443
+ framerMotion.motion.div,
8444
+ {
8445
+ onHoverStart: () => setHovered(true),
8446
+ onHoverEnd: () => setHovered(false),
8447
+ style: {
8448
+ position: "relative",
8449
+ display: "flex",
8450
+ alignItems: "center",
8451
+ justifyContent: "center",
8452
+ cursor: disabled ? "not-allowed" : onClick ? "pointer" : "default",
8453
+ opacity: disabled ? 0.35 : 1,
8454
+ width: size,
8455
+ height: size
8456
+ },
8457
+ animate: { scale: hovered && !disabled ? 1.2 : selected ? 1.15 : 1 },
8458
+ transition: { duration: 0.15, ease: "easeOut" },
8459
+ children: [
8460
+ (selected || hovered) && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
8461
+ framerMotion.motion.div,
8462
+ {
8463
+ style: {
8464
+ position: "absolute",
8465
+ width: ringSize,
8466
+ height: ringSize,
8467
+ borderRadius: "50%",
8468
+ border: `0.18vh solid ${core.alpha(colorHex, 0.85)}`,
8469
+ boxShadow: `0 0 1vh ${core.alpha(colorHex, 0.55)}`
8470
+ },
8471
+ initial: { opacity: 0, scale: 0.8 },
8472
+ animate: { opacity: 1, scale: 1 }
8473
+ }
8474
+ ),
8475
+ url && /* @__PURE__ */ jsxRuntime.jsx(
8476
+ "div",
8477
+ {
8478
+ style: {
8479
+ width: size,
8480
+ height: size,
8481
+ backgroundColor: colorHex,
8482
+ WebkitMaskImage: `url(${url})`,
8483
+ maskImage: `url(${url})`,
8484
+ WebkitMaskRepeat: "no-repeat",
8485
+ maskRepeat: "no-repeat",
8486
+ WebkitMaskPosition: "center",
8487
+ maskPosition: "center",
8488
+ WebkitMaskSize: "contain",
8489
+ maskSize: "contain",
8490
+ filter: [
8491
+ "drop-shadow(0.12vh 0 0 #000)",
8492
+ "drop-shadow(-0.12vh 0 0 #000)",
8493
+ "drop-shadow(0 0.12vh 0 #000)",
8494
+ "drop-shadow(0 -0.12vh 0 #000)",
8495
+ `drop-shadow(0 0 0.4vh ${core.alpha("#000", 0.7)})`
8496
+ ].join(" "),
8497
+ pointerEvents: "none",
8498
+ userSelect: "none"
8499
+ }
8500
+ }
8501
+ )
8502
+ ]
8503
+ }
8504
+ )
7611
8505
  }
7612
- ) : children;
7613
- return /* @__PURE__ */ jsxRuntime.jsx(core.MantineProvider, { theme: mergedTheme, defaultColorScheme: "dark", children: /* @__PURE__ */ jsxRuntime.jsx(DirkErrorBoundary, { children: content }) });
8506
+ );
8507
+ }
8508
+ function useTornEdges() {
8509
+ const game = useSettings((state) => state.game);
8510
+ return game === "rdr3" ? "torn-edge-wrapper" : "";
8511
+ }
8512
+ function TornEdgeSVGFilter() {
8513
+ return /* @__PURE__ */ jsxRuntime.jsx(
8514
+ "svg",
8515
+ {
8516
+ style: { position: "absolute", width: 0, height: 0, pointerEvents: "none" },
8517
+ "aria-hidden": "true",
8518
+ children: /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("filter", { id: "torn-edge-filter", x: "-50%", y: "-50%", width: "200%", height: "200%", children: [
8519
+ /* @__PURE__ */ jsxRuntime.jsx(
8520
+ "feTurbulence",
8521
+ {
8522
+ type: "fractalNoise",
8523
+ baseFrequency: "0.018 0.022",
8524
+ numOctaves: "5",
8525
+ seed: "9",
8526
+ result: "noise1"
8527
+ }
8528
+ ),
8529
+ /* @__PURE__ */ jsxRuntime.jsx(
8530
+ "feTurbulence",
8531
+ {
8532
+ type: "fractalNoise",
8533
+ baseFrequency: "0.08 0.12",
8534
+ numOctaves: "2",
8535
+ seed: "3",
8536
+ result: "noise2"
8537
+ }
8538
+ ),
8539
+ /* @__PURE__ */ jsxRuntime.jsx("feBlend", { in: "noise1", in2: "noise2", mode: "multiply", result: "combinedNoise" }),
8540
+ /* @__PURE__ */ jsxRuntime.jsx(
8541
+ "feDisplacementMap",
8542
+ {
8543
+ in: "SourceGraphic",
8544
+ in2: "combinedNoise",
8545
+ scale: "52",
8546
+ xChannelSelector: "R",
8547
+ yChannelSelector: "G",
8548
+ result: "displaced"
8549
+ }
8550
+ ),
8551
+ /* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { stdDeviation: "0.8", in: "displaced", result: "blurred" }),
8552
+ /* @__PURE__ */ jsxRuntime.jsx("feComponentTransfer", { in: "blurred", result: "alphaFade", children: /* @__PURE__ */ jsxRuntime.jsx("feFuncA", { type: "gamma", amplitude: "1", exponent: "1.3", offset: "-0.05" }) }),
8553
+ /* @__PURE__ */ jsxRuntime.jsx("feMorphology", { operator: "erode", radius: "0.4", in: "alphaFade", result: "eroded" }),
8554
+ /* @__PURE__ */ jsxRuntime.jsx("feMerge", { children: /* @__PURE__ */ jsxRuntime.jsx("feMergeNode", { in: "eroded" }) })
8555
+ ] }) })
8556
+ }
8557
+ );
8558
+ }
8559
+ var moduleCache = /* @__PURE__ */ new Map();
8560
+ function useValidModels(names) {
8561
+ const cacheKey = React4.useMemo(() => {
8562
+ const unique = Array.from(new Set(names.filter((n) => typeof n === "string" && n.length > 0)));
8563
+ unique.sort();
8564
+ return unique.join("|");
8565
+ }, [names]);
8566
+ const [version, setVersion] = React4.useState(0);
8567
+ const inflight = React4.useRef(/* @__PURE__ */ new Set());
8568
+ React4.useEffect(() => {
8569
+ if (!cacheKey) return;
8570
+ const all = cacheKey.split("|").filter(Boolean);
8571
+ const missing = all.filter((n) => !moduleCache.has(n) && !inflight.current.has(n));
8572
+ if (missing.length === 0) return;
8573
+ missing.forEach((n) => inflight.current.add(n));
8574
+ let cancelled = false;
8575
+ fetchNui(
8576
+ "ADMIN_TOOL_QUERY",
8577
+ { id: "validateModels", value: missing },
8578
+ // Fallback when running outside FiveM (browser dev) — assume valid so the
8579
+ // dev shell doesn't grey out every row.
8580
+ Object.fromEntries(missing.map((n) => [n, true]))
8581
+ ).then((result) => {
8582
+ if (cancelled) return;
8583
+ const map = result && typeof result === "object" ? result : {};
8584
+ missing.forEach((n) => {
8585
+ moduleCache.set(n, !!map[n]);
8586
+ inflight.current.delete(n);
8587
+ });
8588
+ setVersion((v) => v + 1);
8589
+ }).catch(() => {
8590
+ if (cancelled) return;
8591
+ missing.forEach((n) => {
8592
+ inflight.current.delete(n);
8593
+ });
8594
+ });
8595
+ return () => {
8596
+ cancelled = true;
8597
+ };
8598
+ }, [cacheKey]);
8599
+ return React4.useMemo(() => {
8600
+ const out = /* @__PURE__ */ new Set();
8601
+ if (!cacheKey) return out;
8602
+ for (const n of cacheKey.split("|")) {
8603
+ if (n && moduleCache.get(n) === true) out.add(n);
8604
+ }
8605
+ return out;
8606
+ }, [cacheKey, version]);
7614
8607
  }
7615
8608
  var Vector2Schema = zod.z.object({
7616
8609
  x: zod.z.number(),
@@ -7635,6 +8628,7 @@ exports.AsyncSaveButton = AsyncSaveButton;
7635
8628
  exports.BlipColorSelect = BlipColorSelect;
7636
8629
  exports.BlipDisplaySelect = BlipDisplaySelect;
7637
8630
  exports.BlipIconSelect = BlipIconSelect;
8631
+ exports.BlipMarker = BlipMarker;
7638
8632
  exports.BorderedIcon = BorderedIcon;
7639
8633
  exports.ConfigPanel = ConfigPanel;
7640
8634
  exports.ConfirmModal = ConfirmModal;
@@ -7643,6 +8637,7 @@ exports.ControlSelect = ControlSelect;
7643
8637
  exports.Counter = Counter;
7644
8638
  exports.DirkProvider = DirkProvider;
7645
8639
  exports.DiscordRoleSelect = DiscordRoleSelect;
8640
+ exports.DoorPickerButton = DoorPickerButton;
7646
8641
  exports.FiveMKeyBindInput = FiveMKeyBindInput;
7647
8642
  exports.FloatingParticles = FloatingParticles;
7648
8643
  exports.FormProvider = FormProvider;
@@ -7655,8 +8650,11 @@ exports.INPUT_MAPPER_KEYS_BY_PRIMARY = INPUT_MAPPER_KEYS_BY_PRIMARY;
7655
8650
  exports.INPUT_MAPPER_PRIMARY_OPTIONS = INPUT_MAPPER_PRIMARY_OPTIONS;
7656
8651
  exports.InfoBox = InfoBox;
7657
8652
  exports.InputContainer = InputContainer;
8653
+ exports.InstructionPanel = InstructionPanel;
7658
8654
  exports.LevelBanner = LevelBanner;
7659
8655
  exports.LevelPanel = LevelPanel;
8656
+ exports.Map = Map2;
8657
+ exports.MapLayer = MapLayer;
7660
8658
  exports.MissingItemsBanner = MissingItemsBanner;
7661
8659
  exports.Modal = Modal;
7662
8660
  exports.ModalContext = ModalContext;
@@ -7668,6 +8666,7 @@ exports.MotionText = MotionText;
7668
8666
  exports.NavBar = NavBar;
7669
8667
  exports.NavigationContext = NavigationContext;
7670
8668
  exports.NavigationProvider = NavigationProvider;
8669
+ exports.PlayerSelect = PlayerSelect;
7671
8670
  exports.PositionPicker = PositionPicker;
7672
8671
  exports.PromptModal = PromptModal;
7673
8672
  exports.ScenarioSelect = ScenarioSelect;
@@ -7685,17 +8684,26 @@ exports.Vector4DeleteButton = Vector4DeleteButton;
7685
8684
  exports.Vector4Display = Vector4Display;
7686
8685
  exports.Vector4Schema = Vector4Schema;
7687
8686
  exports.WorldPositionGotoButton = WorldPositionGotoButton;
8687
+ exports.WorldPositionPicker = WorldPositionPicker;
7688
8688
  exports.WorldPositionSetButton = WorldPositionSetButton;
8689
+ exports.ZoomControls = ZoomControls;
8690
+ exports.blipUrl = blipUrl;
8691
+ exports.blipUrlForSprite = blipUrlForSprite;
8692
+ exports.clearAdminState = clearAdminState;
7689
8693
  exports.colorWithAlpha = colorWithAlpha;
7690
8694
  exports.copyToClipboard = copyToClipboard;
7691
8695
  exports.createFormStore = createFormStore;
7692
8696
  exports.createScriptConfig = createScriptConfig;
7693
8697
  exports.createSkill = createSkill;
8698
+ exports.dirkQueryClient = dirkQueryClient;
8699
+ exports.ensureFrameworkGroups = ensureFrameworkGroups;
7694
8700
  exports.extractDefaults = extractDefaults;
7695
8701
  exports.fetchLuaTable = fetchLuaTable;
7696
8702
  exports.fetchNui = fetchNui;
7697
8703
  exports.formatGtaControl = formatGtaControl;
7698
8704
  exports.gameToMap = gameToMap;
8705
+ exports.getBlipColor = getBlipColor;
8706
+ exports.getBlipEntry = getBlipEntry;
7699
8707
  exports.getGtaControl = getGtaControl;
7700
8708
  exports.getImageShape = getImageShape;
7701
8709
  exports.getItemImageUrl = getItemImageUrl;
@@ -7719,6 +8727,8 @@ exports.selectAllGroups = selectAllGroups;
7719
8727
  exports.splitFAString = splitFAString;
7720
8728
  exports.updatePresignedURL = updatePresignedURL;
7721
8729
  exports.uploadImage = uploadImage;
8730
+ exports.useAdminState = useAdminState;
8731
+ exports.useAdminToolStore = useAdminToolStore;
7722
8732
  exports.useAudio = useAudio;
7723
8733
  exports.useAutoFetcher = useAutoFetcher;
7724
8734
  exports.useForm = useForm;
@@ -7736,8 +8746,11 @@ exports.useModalActions = useModalActions;
7736
8746
  exports.useNavigation = useNavigation;
7737
8747
  exports.useNavigationStore = useNavigationStore;
7738
8748
  exports.useNuiEvent = useNuiEvent;
8749
+ exports.usePickDoor = usePickDoor;
8750
+ exports.usePlayers = usePlayers;
7739
8751
  exports.useProfanityStore = useProfanityStore;
7740
8752
  exports.useSettings = useSettings;
7741
8753
  exports.useTornEdges = useTornEdges;
8754
+ exports.useValidModels = useValidModels;
7742
8755
  //# sourceMappingURL=index.cjs.map
7743
8756
  //# sourceMappingURL=index.cjs.map