dirk-cfx-react 1.1.71 → 1.1.73

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,11 +1,11 @@
1
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, ActionIcon, MantineProvider, BackgroundImage, Group, JsonInput } from '@mantine/core';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import React6, { createContext, useContext, useEffect, useRef, useState, useMemo, useLayoutEffect } from 'react';
3
+ import React6, { createContext, useContext, useEffect, useRef, 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, MapPin, Crosshair, EyeOff, Eye, RotateCcw, Check, FlaskConical, ChevronUp, ChevronDown, 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, 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';
@@ -20,6 +20,7 @@ import { library } from '@fortawesome/fontawesome-svg-core';
20
20
  import { fab } from '@fortawesome/free-brands-svg-icons';
21
21
  import { far } from '@fortawesome/free-regular-svg-icons';
22
22
  import { fas } from '@fortawesome/free-solid-svg-icons';
23
+ import { z } from 'zod';
23
24
 
24
25
  var __defProp = Object.defineProperty;
25
26
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -1242,11 +1243,11 @@ var colorNames = {
1242
1243
  Yellow: { r: 255, g: 255, b: 0 },
1243
1244
  YellowGreen: { r: 154, g: 205, b: 50 }
1244
1245
  };
1245
- function colorWithAlpha(color, alpha9) {
1246
+ function colorWithAlpha(color, alpha10) {
1246
1247
  const lowerCasedColor = color.toLowerCase();
1247
1248
  if (colorNames[lowerCasedColor]) {
1248
1249
  const rgb = colorNames[lowerCasedColor];
1249
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha9})`;
1250
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha10})`;
1250
1251
  }
1251
1252
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1252
1253
  const hex = color.slice(1);
@@ -1254,12 +1255,12 @@ function colorWithAlpha(color, alpha9) {
1254
1255
  const r = bigint >> 16 & 255;
1255
1256
  const g = bigint >> 8 & 255;
1256
1257
  const b = bigint & 255;
1257
- return `rgba(${r}, ${g}, ${b}, ${alpha9})`;
1258
+ return `rgba(${r}, ${g}, ${b}, ${alpha10})`;
1258
1259
  }
1259
1260
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1260
1261
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1261
1262
  if (result) {
1262
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha9})`;
1263
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha10})`;
1263
1264
  }
1264
1265
  }
1265
1266
  return color;
@@ -1331,7 +1332,11 @@ async function fetchNui(eventName, data, mockData) {
1331
1332
  return {};
1332
1333
  }
1333
1334
  const overrideResourceName = useSettings.getState().overideResourceName;
1334
- const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName ? overrideResourceName : "dirk-cfx-react";
1335
+ const hasResourceContext = typeof window.GetParentResourceName === "function" || !!overrideResourceName;
1336
+ if (!hasResourceContext) {
1337
+ return mockData ?? {};
1338
+ }
1339
+ const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName;
1335
1340
  try {
1336
1341
  const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1337
1342
  return await resp.json();
@@ -3074,9 +3079,25 @@ function SegmentedProgress(props) {
3074
3079
  }
3075
3080
  );
3076
3081
  }
3082
+ function getSizePreset(size, themeMdFontSize) {
3083
+ switch (size) {
3084
+ case "xs":
3085
+ return { iconFontSize: "1.2vh", iconPadding: "xxs", titleSize: "xxs", titleLineHeight: "1.2vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
3086
+ case "sm":
3087
+ return { iconFontSize: "1.6vh", iconPadding: "xxs", titleSize: "xs", titleLineHeight: "1.6vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
3088
+ case "lg":
3089
+ return { iconFontSize: "2.6vh", iconPadding: "sm", titleSize: "md", titleLineHeight: "2.6vh", descriptionSize: "sm", innerGap: "sm", bottomPad: "sm" };
3090
+ case "xl":
3091
+ return { iconFontSize: "3.2vh", iconPadding: "sm", titleSize: "lg", titleLineHeight: "3.2vh", descriptionSize: "md", innerGap: "md", bottomPad: "md" };
3092
+ case "md":
3093
+ default:
3094
+ return { iconFontSize: themeMdFontSize, iconPadding: "xs", titleSize: "sm", titleLineHeight: themeMdFontSize, descriptionSize: "xs", innerGap: "sm", bottomPad: "sm" };
3095
+ }
3096
+ }
3077
3097
  function Title(props) {
3078
3098
  const game = useSettings((state) => state.game);
3079
3099
  const theme2 = useMantineTheme();
3100
+ const preset = getSizePreset(props.size ?? "md", theme2.fontSizes.md);
3080
3101
  return /* @__PURE__ */ jsx(
3081
3102
  Flex,
3082
3103
  {
@@ -3085,71 +3106,50 @@ function Title(props) {
3085
3106
  gap: "xs",
3086
3107
  w: props.w || "100%",
3087
3108
  p: props.p || "unset",
3088
- pb: !props.p ? "sm" : props.p,
3109
+ pb: !props.p ? preset.bottomPad : props.p,
3089
3110
  style: {
3090
3111
  userSelect: "none",
3091
3112
  borderBottom: props.removeBorder ? "none" : `0.3vh solid ${props.borderColor || colorWithAlpha(theme2.colors[theme2.primaryColor][9], 0.5)}`
3092
3113
  },
3093
- children: /* @__PURE__ */ jsxs(
3094
- Flex,
3095
- {
3096
- align: "center",
3097
- justify: "center",
3098
- children: [
3099
- /* @__PURE__ */ jsxs(
3100
- Flex,
3114
+ children: /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "center", children: [
3115
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: preset.innerGap, pr: "xs", children: [
3116
+ /* @__PURE__ */ jsx(
3117
+ BorderedIcon,
3118
+ {
3119
+ icon: props.icon,
3120
+ fontSize: preset.iconFontSize,
3121
+ color: props.iconColor,
3122
+ p: preset.iconPadding
3123
+ }
3124
+ ),
3125
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "0.25vh", children: [
3126
+ /* @__PURE__ */ jsx(
3127
+ Text,
3101
3128
  {
3102
- align: "center",
3103
- gap: "sm",
3104
- pr: "xs",
3105
- children: [
3106
- /* @__PURE__ */ jsx(
3107
- BorderedIcon,
3108
- {
3109
- icon: props.icon,
3110
- fontSize: theme2.fontSizes.md,
3111
- color: props.iconColor
3112
- }
3113
- ),
3114
- /* @__PURE__ */ jsxs(
3115
- Flex,
3116
- {
3117
- direction: "column",
3118
- gap: "0.25vh",
3119
- children: [
3120
- /* @__PURE__ */ jsx(Text, { p: "0", size: "sm", style: {
3121
- lineHeight: theme2.fontSizes.md,
3122
- fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
3123
- letterSpacing: "0.05em",
3124
- textTransform: "uppercase"
3125
- }, children: props.title }),
3126
- /* @__PURE__ */ jsx(
3127
- Text,
3128
- {
3129
- size: "xs",
3130
- c: "grey",
3131
- style: { whiteSpace: "normal", wordWrap: "break-word" },
3132
- children: props.description
3133
- }
3134
- )
3135
- ]
3136
- }
3137
- )
3138
- ]
3129
+ p: "0",
3130
+ size: preset.titleSize,
3131
+ style: {
3132
+ lineHeight: preset.titleLineHeight,
3133
+ fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
3134
+ letterSpacing: "0.05em",
3135
+ textTransform: "uppercase"
3136
+ },
3137
+ children: props.title
3139
3138
  }
3140
3139
  ),
3141
3140
  /* @__PURE__ */ jsx(
3142
- Flex,
3141
+ Text,
3143
3142
  {
3144
- ml: "auto",
3145
- align: "center",
3146
- gap: "xs",
3147
- children: props.rightSection
3143
+ size: preset.descriptionSize,
3144
+ c: "grey",
3145
+ style: { whiteSpace: "normal", wordWrap: "break-word" },
3146
+ children: props.description
3148
3147
  }
3149
3148
  )
3150
- ]
3151
- }
3152
- )
3149
+ ] })
3150
+ ] }),
3151
+ /* @__PURE__ */ jsx(Flex, { ml: "auto", align: "center", gap: "xs", children: props.rightSection })
3152
+ ] })
3153
3153
  }
3154
3154
  );
3155
3155
  }
@@ -3807,6 +3807,246 @@ function ConfirmModal({
3807
3807
  }
3808
3808
  ) });
3809
3809
  }
3810
+ var TABS = [
3811
+ { id: "ox", label: "ox_inventory" },
3812
+ { id: "qb", label: "qb-inventory" },
3813
+ { id: "esx", label: "ESX legacy SQL" }
3814
+ ];
3815
+ var useMissingItemsAudit = create((set, get) => ({
3816
+ data: null,
3817
+ loaded: false,
3818
+ inFlight: false,
3819
+ refresh: async () => {
3820
+ if (get().inFlight) return;
3821
+ set({ inFlight: true });
3822
+ try {
3823
+ const res = await fetchNui("GET_MISSING_ITEMS", void 0, {
3824
+ success: true,
3825
+ data: { missing: [], snippets: { ox: "", qb: "", esx: "" } }
3826
+ });
3827
+ if (res?.success && res.data) {
3828
+ set({ data: res.data, loaded: true });
3829
+ } else {
3830
+ set({ loaded: true });
3831
+ }
3832
+ } catch {
3833
+ set({ loaded: true });
3834
+ } finally {
3835
+ set({ inFlight: false });
3836
+ }
3837
+ }
3838
+ }));
3839
+ function MissingItemsBanner() {
3840
+ const theme2 = useMantineTheme();
3841
+ const audit = useMissingItemsAudit((s) => s.data);
3842
+ const loaded = useMissingItemsAudit((s) => s.loaded);
3843
+ const inFlight = useMissingItemsAudit((s) => s.inFlight);
3844
+ const refresh = useMissingItemsAudit((s) => s.refresh);
3845
+ const [expanded, setExpanded] = useState(false);
3846
+ const [activeTab, setActiveTab] = useState("ox");
3847
+ const [hoveredTab, setHoveredTab] = useState(null);
3848
+ const [copied, setCopied] = useState(null);
3849
+ useEffect(() => {
3850
+ if (!loaded) refresh();
3851
+ }, [loaded, refresh]);
3852
+ const handleCopy = useCallback((tab) => {
3853
+ if (!audit) return;
3854
+ const text = audit.snippets[tab] ?? "";
3855
+ navigator.clipboard.writeText(text).then(() => {
3856
+ setCopied(tab);
3857
+ setTimeout(() => setCopied((c) => c === tab ? null : c), 1500);
3858
+ }).catch(() => {
3859
+ });
3860
+ }, [audit]);
3861
+ const handleRefresh = useCallback((e) => {
3862
+ e.stopPropagation();
3863
+ refresh();
3864
+ }, [refresh]);
3865
+ if (!audit || audit.missing.length === 0) return null;
3866
+ const warnColor = "#f59e0b";
3867
+ const names = audit.missing.map((m) => m.name);
3868
+ return /* @__PURE__ */ jsxs(
3869
+ "div",
3870
+ {
3871
+ style: {
3872
+ background: alpha(warnColor, 0.06),
3873
+ border: `0.1vh solid ${alpha(warnColor, 0.35)}`,
3874
+ borderLeft: `0.3vh solid ${warnColor}`,
3875
+ borderRadius: theme2.radius.xs,
3876
+ margin: "0.6vh 1vh",
3877
+ overflow: "hidden"
3878
+ },
3879
+ children: [
3880
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "0.8vh", p: "0.8vh 1vh", style: { cursor: "pointer" }, onClick: () => setExpanded((e) => !e), children: [
3881
+ /* @__PURE__ */ jsx(AlertTriangle, { size: "1.8vh", color: warnColor, strokeWidth: 2.5 }),
3882
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
3883
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xs", tt: "uppercase", lts: "0.07em", c: warnColor, children: audit.missing.length === 1 ? "1 item missing from your inventory" : `${audit.missing.length} items missing from your inventory` }),
3884
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.5)", lineClamp: 1, style: { fontFamily: "monospace" }, children: names.join(", ") })
3885
+ ] }),
3886
+ /* @__PURE__ */ jsx(
3887
+ "button",
3888
+ {
3889
+ onClick: handleRefresh,
3890
+ disabled: inFlight,
3891
+ style: {
3892
+ background: "transparent",
3893
+ border: "none",
3894
+ padding: "0.3vh",
3895
+ cursor: inFlight ? "wait" : "pointer",
3896
+ display: "flex",
3897
+ alignItems: "center",
3898
+ justifyContent: "center",
3899
+ opacity: inFlight ? 0.4 : 0.7
3900
+ },
3901
+ title: "Re-check",
3902
+ children: /* @__PURE__ */ jsx(
3903
+ motion.span,
3904
+ {
3905
+ animate: { rotate: inFlight ? 360 : 0 },
3906
+ transition: inFlight ? { duration: 1, repeat: Infinity, ease: "linear" } : { duration: 0 },
3907
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3908
+ children: /* @__PURE__ */ jsx(RefreshCw, { size: "1.5vh", color: alpha(warnColor, 0.7) })
3909
+ }
3910
+ )
3911
+ }
3912
+ ),
3913
+ /* @__PURE__ */ jsx(
3914
+ motion.div,
3915
+ {
3916
+ animate: { rotate: expanded ? 180 : 0 },
3917
+ transition: { duration: 0.18 },
3918
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3919
+ children: /* @__PURE__ */ jsx(ChevronDown, { size: "1.8vh", color: alpha(warnColor, 0.7) })
3920
+ }
3921
+ )
3922
+ ] }),
3923
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: expanded && /* @__PURE__ */ jsxs(
3924
+ motion.div,
3925
+ {
3926
+ initial: { height: 0, opacity: 0 },
3927
+ animate: { height: "auto", opacity: 1 },
3928
+ exit: { height: 0, opacity: 0 },
3929
+ transition: { duration: 0.18, ease: "easeOut" },
3930
+ style: { overflow: "hidden", borderTop: `0.1vh solid ${alpha(warnColor, 0.18)}` },
3931
+ children: [
3932
+ /* @__PURE__ */ jsx(Flex, { gap: "0", style: { borderBottom: `0.1vh solid ${alpha(warnColor, 0.18)}` }, children: TABS.map((tab) => {
3933
+ const active = tab.id === activeTab;
3934
+ const hovered = hoveredTab === tab.id;
3935
+ let bg = "transparent";
3936
+ if (active) bg = alpha(warnColor, 0.12);
3937
+ else if (hovered) bg = alpha(warnColor, 0.08);
3938
+ return /* @__PURE__ */ jsx(
3939
+ "button",
3940
+ {
3941
+ onClick: (e) => {
3942
+ e.stopPropagation();
3943
+ setActiveTab(tab.id);
3944
+ },
3945
+ onMouseEnter: () => setHoveredTab(tab.id),
3946
+ onMouseLeave: () => setHoveredTab((h) => h === tab.id ? null : h),
3947
+ style: {
3948
+ flex: 1,
3949
+ background: bg,
3950
+ border: "none",
3951
+ borderBottom: active ? `0.2vh solid ${warnColor}` : "0.2vh solid transparent",
3952
+ padding: "0.3vh 1vh",
3953
+ cursor: active ? "default" : "pointer",
3954
+ color: active ? warnColor : "rgba(255,255,255,0.5)",
3955
+ fontFamily: "Akrobat Bold",
3956
+ fontSize: "var(--mantine-font-size-xxs)",
3957
+ letterSpacing: "0.07em",
3958
+ textTransform: "uppercase",
3959
+ transition: "background 0.12s"
3960
+ },
3961
+ children: tab.label
3962
+ },
3963
+ tab.id
3964
+ );
3965
+ }) }),
3966
+ /* @__PURE__ */ jsx(
3967
+ CodeView,
3968
+ {
3969
+ code: audit.snippets[activeTab] ?? "",
3970
+ copied: copied === activeTab,
3971
+ onCopy: (e) => {
3972
+ e.stopPropagation();
3973
+ handleCopy(activeTab);
3974
+ },
3975
+ warnColor
3976
+ }
3977
+ )
3978
+ ]
3979
+ },
3980
+ "expanded"
3981
+ ) })
3982
+ ]
3983
+ }
3984
+ );
3985
+ }
3986
+ function CodeView({
3987
+ code,
3988
+ copied,
3989
+ onCopy,
3990
+ warnColor
3991
+ }) {
3992
+ const theme2 = useMantineTheme();
3993
+ const [hovered, setHovered] = useState(false);
3994
+ const lines = code === "" ? [""] : code.split("\n");
3995
+ const lineNumWidth = String(lines.length).length;
3996
+ const copyBg = copied ? alpha("#22c55e", 0.15) : hovered ? alpha(warnColor, 0.18) : alpha(warnColor, 0.1);
3997
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
3998
+ /* @__PURE__ */ jsxs(
3999
+ "button",
4000
+ {
4001
+ onClick: onCopy,
4002
+ onMouseEnter: () => setHovered(true),
4003
+ onMouseLeave: () => setHovered(false),
4004
+ style: {
4005
+ position: "absolute",
4006
+ top: "0.6vh",
4007
+ right: "0.8vh",
4008
+ zIndex: 2,
4009
+ background: copyBg,
4010
+ border: `0.1vh solid ${alpha(copied ? "#22c55e" : warnColor, 0.35)}`,
4011
+ borderRadius: theme2.radius.xs,
4012
+ padding: "0.4vh 0.7vh",
4013
+ cursor: "pointer",
4014
+ display: "flex",
4015
+ alignItems: "center",
4016
+ gap: "0.4vh",
4017
+ transition: "background 0.12s"
4018
+ },
4019
+ children: [
4020
+ copied ? /* @__PURE__ */ jsx(Check, { size: "1.4vh", color: "#22c55e" }) : /* @__PURE__ */ jsx(Copy, { size: "1.4vh", color: warnColor }),
4021
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: copied ? "#22c55e" : warnColor, children: copied ? "Copied" : "Copy" })
4022
+ ]
4023
+ }
4024
+ ),
4025
+ /* @__PURE__ */ jsx("div", { style: {
4026
+ background: alpha(theme2.colors.dark[9], 0.6),
4027
+ maxHeight: "40vh",
4028
+ overflowY: "auto",
4029
+ overflowX: "auto",
4030
+ padding: "0.6vh 0"
4031
+ }, children: /* @__PURE__ */ jsx("table", { style: { borderCollapse: "collapse", width: "100%", fontFamily: "monospace", fontSize: "1.2vh", lineHeight: 1.5 }, children: /* @__PURE__ */ jsx("tbody", { children: lines.map((line, i) => /* @__PURE__ */ jsxs("tr", { children: [
4032
+ /* @__PURE__ */ jsx("td", { style: {
4033
+ width: `${lineNumWidth + 2}ch`,
4034
+ textAlign: "right",
4035
+ padding: "0 0.8vh 0 1vh",
4036
+ color: "rgba(255,255,255,0.25)",
4037
+ userSelect: "none",
4038
+ whiteSpace: "nowrap",
4039
+ verticalAlign: "top"
4040
+ }, children: i + 1 }),
4041
+ /* @__PURE__ */ jsx("td", { style: {
4042
+ padding: "0 1vh",
4043
+ color: "rgba(255,255,255,0.85)",
4044
+ whiteSpace: "pre",
4045
+ verticalAlign: "top"
4046
+ }, children: line || "\u200B" })
4047
+ ] }, i)) }) }) })
4048
+ ] });
4049
+ }
3810
4050
  function getNested(obj, path) {
3811
4051
  return path.split(".").reduce((acc, key) => acc ? acc[key] : void 0, obj);
3812
4052
  }
@@ -4533,7 +4773,8 @@ function ConfigPanelInner({
4533
4773
  resetConfirmText,
4534
4774
  defaultConfig,
4535
4775
  width,
4536
- height
4776
+ height,
4777
+ suppressMissingItemsBanner
4537
4778
  }) {
4538
4779
  const { updateConfig, resetConfig, getHistory } = getScriptConfigInstance();
4539
4780
  const form = useForm();
@@ -4653,8 +4894,8 @@ function ConfigPanelInner({
4653
4894
  children: /* @__PURE__ */ jsx(ArrowLeft, { size: "1.4vh", color })
4654
4895
  }
4655
4896
  ),
4656
- /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { minWidth: 0, lineHeight: 1 }, children: [
4657
- /* @__PURE__ */ jsx(Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4897
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { minWidth: 0, flex: 1, lineHeight: 1, overflow: "hidden" }, children: [
4898
+ /* @__PURE__ */ jsx(Text, { size: "md", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4658
4899
  subtitle && /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.08em", c: color, truncate: true, children: subtitle })
4659
4900
  ] })
4660
4901
  ] }),
@@ -4756,18 +4997,21 @@ function ConfigPanelInner({
4756
4997
  ] })
4757
4998
  ] })
4758
4999
  ] }),
4759
- /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
4760
- motion.div,
4761
- {
4762
- initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4763
- animate: { opacity: 1, y: 0 },
4764
- exit: { opacity: 0, y: -4 },
4765
- transition: { duration: 0.15 },
4766
- style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
4767
- children: children(activeTab)
4768
- },
4769
- activeTab
4770
- ) })
5000
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
5001
+ !suppressMissingItemsBanner && /* @__PURE__ */ jsx(MissingItemsBanner, {}),
5002
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
5003
+ motion.div,
5004
+ {
5005
+ initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
5006
+ animate: { opacity: 1, y: 0 },
5007
+ exit: { opacity: 0, y: -4 },
5008
+ transition: { duration: 0.15 },
5009
+ style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
5010
+ children: children(activeTab)
5011
+ },
5012
+ activeTab
5013
+ ) })
5014
+ ] })
4771
5015
  ]
4772
5016
  }
4773
5017
  )
@@ -4809,6 +5053,7 @@ function ConfigPanel(props) {
4809
5053
  if (result?.success) {
4810
5054
  form.reinitialize(cloneConfig(form.values));
4811
5055
  configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
5056
+ useMissingItemsAudit.getState().refresh();
4812
5057
  notifications.show({
4813
5058
  color: "green",
4814
5059
  title: locale("ConfigSaveSuccessTitle"),
@@ -4846,6 +5091,7 @@ function ConfigPanel(props) {
4846
5091
  }
4847
5092
  ) });
4848
5093
  }
5094
+ var MISSING_COLOR = "#f59e0b";
4849
5095
  function LazyImage({ src, style }) {
4850
5096
  const [visible, setVisible] = useState(false);
4851
5097
  const ref = useRef(null);
@@ -4863,14 +5109,19 @@ function LazyImage({ src, style }) {
4863
5109
  }
4864
5110
  function SelectItem(props) {
4865
5111
  const invItems = useItems();
5112
+ const isMissing = !!props.value && !invItems[props.value];
4866
5113
  const formattedItems = useMemo(() => {
4867
5114
  const seen = /* @__PURE__ */ new Set();
4868
- return useItemsList(props.excludeItemNames ?? []).filter((item) => {
5115
+ const list = useItemsList(props.excludeItemNames ?? []).filter((item) => {
4869
5116
  if (seen.has(item.name)) return false;
4870
5117
  seen.add(item.name);
4871
5118
  return true;
4872
5119
  }).map((item) => ({ value: item.name, label: item.label, image: item.image }));
4873
- }, [invItems, props.excludeItemNames]);
5120
+ if (isMissing) {
5121
+ list.unshift({ value: props.value, label: props.value, image: "" });
5122
+ }
5123
+ return list;
5124
+ }, [invItems, props.excludeItemNames, props.value, isMissing]);
4874
5125
  return /* @__PURE__ */ jsx(
4875
5126
  Select,
4876
5127
  {
@@ -4885,10 +5136,11 @@ function SelectItem(props) {
4885
5136
  data: formattedItems,
4886
5137
  allowDeselect: false,
4887
5138
  searchable: true,
5139
+ styles: isMissing ? { input: { color: MISSING_COLOR } } : void 0,
4888
5140
  comboboxProps: { withinPortal: true, zIndex: 2e3 },
4889
5141
  leftSectionWidth: "4vh",
4890
5142
  leftSectionPointerEvents: "none",
4891
- leftSection: props.value ? /* @__PURE__ */ jsx(
5143
+ leftSection: isMissing ? /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", w: "4vh", h: "4vh", children: /* @__PURE__ */ jsx(AlertTriangle, { size: "2vh", color: MISSING_COLOR, strokeWidth: 2.5 }) }) : props.value ? /* @__PURE__ */ jsx(
4892
5144
  Image$1,
4893
5145
  {
4894
5146
  fallbackSrc: "/placeholder.png",
@@ -4899,19 +5151,37 @@ function SelectItem(props) {
4899
5151
  }
4900
5152
  ) : null,
4901
5153
  nothingFoundMessage: locale("NoItemsFound"),
4902
- renderOption: (item) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", w: "100%", children: [
4903
- /* @__PURE__ */ jsx(
4904
- LazyImage,
4905
- {
4906
- src: invItems[item.option.value]?.image || "",
4907
- style: { aspectRatio: "1 / 1" }
4908
- }
4909
- ),
4910
- /* @__PURE__ */ jsxs(Flex, { direction: "column", children: [
4911
- /* @__PURE__ */ jsx(Text, { size: "sm", children: item.option.label }),
4912
- /* @__PURE__ */ jsx(Text, { size: "xxs", c: "dimmed", children: item.option.value })
4913
- ] })
4914
- ] })
5154
+ renderOption: (item) => {
5155
+ const optionMissing = !invItems[item.option.value] && item.option.value === props.value;
5156
+ if (optionMissing) {
5157
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", w: "100%", children: [
5158
+ /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", w: "4vh", h: "4vh", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx(AlertTriangle, { size: "2vh", color: MISSING_COLOR, strokeWidth: 2.5 }) }),
5159
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
5160
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: MISSING_COLOR, style: { fontFamily: "monospace" }, children: item.option.value }),
5161
+ /* @__PURE__ */ jsx(Text, { size: "xxs", c: "dimmed", children: locale("ItemNotInInventory") })
5162
+ ] }),
5163
+ /* @__PURE__ */ jsx("div", { style: {
5164
+ background: "rgba(245,158,11,0.12)",
5165
+ border: `0.1vh solid ${MISSING_COLOR}59`,
5166
+ borderRadius: "0.3vh",
5167
+ padding: "0.1vh 0.6vh"
5168
+ }, children: /* @__PURE__ */ jsx(Text, { size: "xxs", c: MISSING_COLOR, ff: "Akrobat Bold", tt: "uppercase", lts: "0.06em", children: locale("Missing") }) })
5169
+ ] });
5170
+ }
5171
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", w: "100%", children: [
5172
+ /* @__PURE__ */ jsx(
5173
+ LazyImage,
5174
+ {
5175
+ src: invItems[item.option.value]?.image || "",
5176
+ style: { aspectRatio: "1 / 1" }
5177
+ }
5178
+ ),
5179
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", children: [
5180
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: item.option.label }),
5181
+ /* @__PURE__ */ jsx(Text, { size: "xxs", c: "dimmed", children: item.option.value })
5182
+ ] })
5183
+ ] });
5184
+ }
4915
5185
  }
4916
5186
  );
4917
5187
  }
@@ -5566,6 +5836,17 @@ function AdminPageTitle(props) {
5566
5836
  /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", tt: "uppercase", lts: "0.1em", size: "sm", c: "rgba(255,255,255,0.6)", children: locale(props.title) })
5567
5837
  ] });
5568
5838
  }
5839
+ var placementStyle = (placement) => {
5840
+ switch (placement) {
5841
+ case "top-center":
5842
+ return { top: "1vh", left: "50%", transform: "translateX(-50%)" };
5843
+ case "top-right":
5844
+ return { top: "1vh", right: "1vh" };
5845
+ case "top-left":
5846
+ default:
5847
+ return { top: "1vh", left: "1vh" };
5848
+ }
5849
+ };
5569
5850
  var loadPersistedState = (storageKey) => {
5570
5851
  try {
5571
5852
  const raw = localStorage.getItem(storageKey);
@@ -5584,7 +5865,8 @@ function TestBed({
5584
5865
  items,
5585
5866
  storageKey = "testbed:open-state",
5586
5867
  disablePersistence = false,
5587
- title = "TestBed"
5868
+ title = "TestBed",
5869
+ placement = "top-left"
5588
5870
  }) {
5589
5871
  const [open, setOpen] = useState(false);
5590
5872
  const itemsRef = useRef(items);
@@ -5614,8 +5896,7 @@ function TestBed({
5614
5896
  {
5615
5897
  style: {
5616
5898
  position: "fixed",
5617
- top: "1vh",
5618
- left: "1vh",
5899
+ ...placementStyle(placement),
5619
5900
  zIndex: 2147483647,
5620
5901
  pointerEvents: "auto",
5621
5902
  fontSize: "1.4vh"
@@ -6025,7 +6306,22 @@ function DirkProvider({ children, overideResourceName, themeOverride }) {
6025
6306
  ) : children;
6026
6307
  return /* @__PURE__ */ jsx(MantineProvider, { theme: mergedTheme, defaultColorScheme: "dark", children: /* @__PURE__ */ jsx(DirkErrorBoundary, { children: content }) });
6027
6308
  }
6309
+ var Vector2Schema = z.object({
6310
+ x: z.number(),
6311
+ y: z.number()
6312
+ });
6313
+ var Vector3Schema = z.object({
6314
+ x: z.number(),
6315
+ y: z.number(),
6316
+ z: z.number()
6317
+ });
6318
+ var Vector4Schema = z.object({
6319
+ x: z.number(),
6320
+ y: z.number(),
6321
+ z: z.number(),
6322
+ w: z.number()
6323
+ });
6028
6324
 
6029
- export { AdminPageTitle, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, DirkProvider, 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, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PositionPicker, PromptModal, SegmentedControl, SegmentedProgress, SelectItem, TestBed, Title, TornEdgeSVGFilter, Vector4DeleteButton, Vector4Display, 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, useModal, useModalActions, useNavigation, useNavigationStore, useNuiEvent, useProfanityStore, useSettings, useTornEdges };
6325
+ export { AdminPageTitle, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, DirkProvider, 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, SegmentedControl, SegmentedProgress, SelectItem, TestBed, 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 };
6030
6326
  //# sourceMappingURL=index.js.map
6031
6327
  //# sourceMappingURL=index.js.map