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