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.cjs CHANGED
@@ -22,6 +22,7 @@ var fontawesomeSvgCore = require('@fortawesome/fontawesome-svg-core');
22
22
  var freeBrandsSvgIcons = require('@fortawesome/free-brands-svg-icons');
23
23
  var freeRegularSvgIcons = require('@fortawesome/free-regular-svg-icons');
24
24
  var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons');
25
+ var zod = require('zod');
25
26
 
26
27
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
27
28
 
@@ -1251,11 +1252,11 @@ var colorNames = {
1251
1252
  Yellow: { r: 255, g: 255, b: 0 },
1252
1253
  YellowGreen: { r: 154, g: 205, b: 50 }
1253
1254
  };
1254
- function colorWithAlpha(color, alpha9) {
1255
+ function colorWithAlpha(color, alpha10) {
1255
1256
  const lowerCasedColor = color.toLowerCase();
1256
1257
  if (colorNames[lowerCasedColor]) {
1257
1258
  const rgb = colorNames[lowerCasedColor];
1258
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha9})`;
1259
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha10})`;
1259
1260
  }
1260
1261
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1261
1262
  const hex = color.slice(1);
@@ -1263,12 +1264,12 @@ function colorWithAlpha(color, alpha9) {
1263
1264
  const r = bigint >> 16 & 255;
1264
1265
  const g = bigint >> 8 & 255;
1265
1266
  const b = bigint & 255;
1266
- return `rgba(${r}, ${g}, ${b}, ${alpha9})`;
1267
+ return `rgba(${r}, ${g}, ${b}, ${alpha10})`;
1267
1268
  }
1268
1269
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1269
1270
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1270
1271
  if (result) {
1271
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha9})`;
1272
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha10})`;
1272
1273
  }
1273
1274
  }
1274
1275
  return color;
@@ -1340,7 +1341,11 @@ async function fetchNui(eventName, data, mockData) {
1340
1341
  return {};
1341
1342
  }
1342
1343
  const overrideResourceName = useSettings.getState().overideResourceName;
1343
- const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName ? overrideResourceName : "dirk-cfx-react";
1344
+ const hasResourceContext = typeof window.GetParentResourceName === "function" || !!overrideResourceName;
1345
+ if (!hasResourceContext) {
1346
+ return mockData ?? {};
1347
+ }
1348
+ const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName;
1344
1349
  try {
1345
1350
  const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1346
1351
  return await resp.json();
@@ -3083,9 +3088,25 @@ function SegmentedProgress(props) {
3083
3088
  }
3084
3089
  );
3085
3090
  }
3091
+ function getSizePreset(size, themeMdFontSize) {
3092
+ switch (size) {
3093
+ case "xs":
3094
+ return { iconFontSize: "1.2vh", iconPadding: "xxs", titleSize: "xxs", titleLineHeight: "1.2vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
3095
+ case "sm":
3096
+ return { iconFontSize: "1.6vh", iconPadding: "xxs", titleSize: "xs", titleLineHeight: "1.6vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
3097
+ case "lg":
3098
+ return { iconFontSize: "2.6vh", iconPadding: "sm", titleSize: "md", titleLineHeight: "2.6vh", descriptionSize: "sm", innerGap: "sm", bottomPad: "sm" };
3099
+ case "xl":
3100
+ return { iconFontSize: "3.2vh", iconPadding: "sm", titleSize: "lg", titleLineHeight: "3.2vh", descriptionSize: "md", innerGap: "md", bottomPad: "md" };
3101
+ case "md":
3102
+ default:
3103
+ return { iconFontSize: themeMdFontSize, iconPadding: "xs", titleSize: "sm", titleLineHeight: themeMdFontSize, descriptionSize: "xs", innerGap: "sm", bottomPad: "sm" };
3104
+ }
3105
+ }
3086
3106
  function Title(props) {
3087
3107
  const game = useSettings((state) => state.game);
3088
3108
  const theme2 = core.useMantineTheme();
3109
+ const preset = getSizePreset(props.size ?? "md", theme2.fontSizes.md);
3089
3110
  return /* @__PURE__ */ jsxRuntime.jsx(
3090
3111
  core.Flex,
3091
3112
  {
@@ -3094,71 +3115,50 @@ function Title(props) {
3094
3115
  gap: "xs",
3095
3116
  w: props.w || "100%",
3096
3117
  p: props.p || "unset",
3097
- pb: !props.p ? "sm" : props.p,
3118
+ pb: !props.p ? preset.bottomPad : props.p,
3098
3119
  style: {
3099
3120
  userSelect: "none",
3100
3121
  borderBottom: props.removeBorder ? "none" : `0.3vh solid ${props.borderColor || colorWithAlpha(theme2.colors[theme2.primaryColor][9], 0.5)}`
3101
3122
  },
3102
- children: /* @__PURE__ */ jsxRuntime.jsxs(
3103
- core.Flex,
3104
- {
3105
- align: "center",
3106
- justify: "center",
3107
- children: [
3108
- /* @__PURE__ */ jsxRuntime.jsxs(
3109
- core.Flex,
3123
+ children: /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", justify: "center", children: [
3124
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: preset.innerGap, pr: "xs", children: [
3125
+ /* @__PURE__ */ jsxRuntime.jsx(
3126
+ BorderedIcon,
3127
+ {
3128
+ icon: props.icon,
3129
+ fontSize: preset.iconFontSize,
3130
+ color: props.iconColor,
3131
+ p: preset.iconPadding
3132
+ }
3133
+ ),
3134
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "0.25vh", children: [
3135
+ /* @__PURE__ */ jsxRuntime.jsx(
3136
+ core.Text,
3110
3137
  {
3111
- align: "center",
3112
- gap: "sm",
3113
- pr: "xs",
3114
- children: [
3115
- /* @__PURE__ */ jsxRuntime.jsx(
3116
- BorderedIcon,
3117
- {
3118
- icon: props.icon,
3119
- fontSize: theme2.fontSizes.md,
3120
- color: props.iconColor
3121
- }
3122
- ),
3123
- /* @__PURE__ */ jsxRuntime.jsxs(
3124
- core.Flex,
3125
- {
3126
- direction: "column",
3127
- gap: "0.25vh",
3128
- children: [
3129
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { p: "0", size: "sm", style: {
3130
- lineHeight: theme2.fontSizes.md,
3131
- fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
3132
- letterSpacing: "0.05em",
3133
- textTransform: "uppercase"
3134
- }, children: props.title }),
3135
- /* @__PURE__ */ jsxRuntime.jsx(
3136
- core.Text,
3137
- {
3138
- size: "xs",
3139
- c: "grey",
3140
- style: { whiteSpace: "normal", wordWrap: "break-word" },
3141
- children: props.description
3142
- }
3143
- )
3144
- ]
3145
- }
3146
- )
3147
- ]
3138
+ p: "0",
3139
+ size: preset.titleSize,
3140
+ style: {
3141
+ lineHeight: preset.titleLineHeight,
3142
+ fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
3143
+ letterSpacing: "0.05em",
3144
+ textTransform: "uppercase"
3145
+ },
3146
+ children: props.title
3148
3147
  }
3149
3148
  ),
3150
3149
  /* @__PURE__ */ jsxRuntime.jsx(
3151
- core.Flex,
3150
+ core.Text,
3152
3151
  {
3153
- ml: "auto",
3154
- align: "center",
3155
- gap: "xs",
3156
- children: props.rightSection
3152
+ size: preset.descriptionSize,
3153
+ c: "grey",
3154
+ style: { whiteSpace: "normal", wordWrap: "break-word" },
3155
+ children: props.description
3157
3156
  }
3158
3157
  )
3159
- ]
3160
- }
3161
- )
3158
+ ] })
3159
+ ] }),
3160
+ /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { ml: "auto", align: "center", gap: "xs", children: props.rightSection })
3161
+ ] })
3162
3162
  }
3163
3163
  );
3164
3164
  }
@@ -3816,6 +3816,246 @@ function ConfirmModal({
3816
3816
  }
3817
3817
  ) });
3818
3818
  }
3819
+ var TABS = [
3820
+ { id: "ox", label: "ox_inventory" },
3821
+ { id: "qb", label: "qb-inventory" },
3822
+ { id: "esx", label: "ESX legacy SQL" }
3823
+ ];
3824
+ var useMissingItemsAudit = zustand.create((set, get) => ({
3825
+ data: null,
3826
+ loaded: false,
3827
+ inFlight: false,
3828
+ refresh: async () => {
3829
+ if (get().inFlight) return;
3830
+ set({ inFlight: true });
3831
+ try {
3832
+ const res = await fetchNui("GET_MISSING_ITEMS", void 0, {
3833
+ success: true,
3834
+ data: { missing: [], snippets: { ox: "", qb: "", esx: "" } }
3835
+ });
3836
+ if (res?.success && res.data) {
3837
+ set({ data: res.data, loaded: true });
3838
+ } else {
3839
+ set({ loaded: true });
3840
+ }
3841
+ } catch {
3842
+ set({ loaded: true });
3843
+ } finally {
3844
+ set({ inFlight: false });
3845
+ }
3846
+ }
3847
+ }));
3848
+ function MissingItemsBanner() {
3849
+ const theme2 = core.useMantineTheme();
3850
+ const audit = useMissingItemsAudit((s) => s.data);
3851
+ const loaded = useMissingItemsAudit((s) => s.loaded);
3852
+ const inFlight = useMissingItemsAudit((s) => s.inFlight);
3853
+ const refresh = useMissingItemsAudit((s) => s.refresh);
3854
+ const [expanded, setExpanded] = React6.useState(false);
3855
+ const [activeTab, setActiveTab] = React6.useState("ox");
3856
+ const [hoveredTab, setHoveredTab] = React6.useState(null);
3857
+ const [copied, setCopied] = React6.useState(null);
3858
+ React6.useEffect(() => {
3859
+ if (!loaded) refresh();
3860
+ }, [loaded, refresh]);
3861
+ const handleCopy = React6.useCallback((tab) => {
3862
+ if (!audit) return;
3863
+ const text = audit.snippets[tab] ?? "";
3864
+ navigator.clipboard.writeText(text).then(() => {
3865
+ setCopied(tab);
3866
+ setTimeout(() => setCopied((c) => c === tab ? null : c), 1500);
3867
+ }).catch(() => {
3868
+ });
3869
+ }, [audit]);
3870
+ const handleRefresh = React6.useCallback((e) => {
3871
+ e.stopPropagation();
3872
+ refresh();
3873
+ }, [refresh]);
3874
+ if (!audit || audit.missing.length === 0) return null;
3875
+ const warnColor = "#f59e0b";
3876
+ const names = audit.missing.map((m) => m.name);
3877
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3878
+ "div",
3879
+ {
3880
+ style: {
3881
+ background: core.alpha(warnColor, 0.06),
3882
+ border: `0.1vh solid ${core.alpha(warnColor, 0.35)}`,
3883
+ borderLeft: `0.3vh solid ${warnColor}`,
3884
+ borderRadius: theme2.radius.xs,
3885
+ margin: "0.6vh 1vh",
3886
+ overflow: "hidden"
3887
+ },
3888
+ children: [
3889
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "0.8vh", p: "0.8vh 1vh", style: { cursor: "pointer" }, onClick: () => setExpanded((e) => !e), children: [
3890
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: "1.8vh", color: warnColor, strokeWidth: 2.5 }),
3891
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
3892
+ /* @__PURE__ */ jsxRuntime.jsx(core.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` }),
3893
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.5)", lineClamp: 1, style: { fontFamily: "monospace" }, children: names.join(", ") })
3894
+ ] }),
3895
+ /* @__PURE__ */ jsxRuntime.jsx(
3896
+ "button",
3897
+ {
3898
+ onClick: handleRefresh,
3899
+ disabled: inFlight,
3900
+ style: {
3901
+ background: "transparent",
3902
+ border: "none",
3903
+ padding: "0.3vh",
3904
+ cursor: inFlight ? "wait" : "pointer",
3905
+ display: "flex",
3906
+ alignItems: "center",
3907
+ justifyContent: "center",
3908
+ opacity: inFlight ? 0.4 : 0.7
3909
+ },
3910
+ title: "Re-check",
3911
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3912
+ framerMotion.motion.span,
3913
+ {
3914
+ animate: { rotate: inFlight ? 360 : 0 },
3915
+ transition: inFlight ? { duration: 1, repeat: Infinity, ease: "linear" } : { duration: 0 },
3916
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3917
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.5vh", color: core.alpha(warnColor, 0.7) })
3918
+ }
3919
+ )
3920
+ }
3921
+ ),
3922
+ /* @__PURE__ */ jsxRuntime.jsx(
3923
+ framerMotion.motion.div,
3924
+ {
3925
+ animate: { rotate: expanded ? 180 : 0 },
3926
+ transition: { duration: 0.18 },
3927
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3928
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: "1.8vh", color: core.alpha(warnColor, 0.7) })
3929
+ }
3930
+ )
3931
+ ] }),
3932
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { initial: false, children: expanded && /* @__PURE__ */ jsxRuntime.jsxs(
3933
+ framerMotion.motion.div,
3934
+ {
3935
+ initial: { height: 0, opacity: 0 },
3936
+ animate: { height: "auto", opacity: 1 },
3937
+ exit: { height: 0, opacity: 0 },
3938
+ transition: { duration: 0.18, ease: "easeOut" },
3939
+ style: { overflow: "hidden", borderTop: `0.1vh solid ${core.alpha(warnColor, 0.18)}` },
3940
+ children: [
3941
+ /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { gap: "0", style: { borderBottom: `0.1vh solid ${core.alpha(warnColor, 0.18)}` }, children: TABS.map((tab) => {
3942
+ const active = tab.id === activeTab;
3943
+ const hovered = hoveredTab === tab.id;
3944
+ let bg = "transparent";
3945
+ if (active) bg = core.alpha(warnColor, 0.12);
3946
+ else if (hovered) bg = core.alpha(warnColor, 0.08);
3947
+ return /* @__PURE__ */ jsxRuntime.jsx(
3948
+ "button",
3949
+ {
3950
+ onClick: (e) => {
3951
+ e.stopPropagation();
3952
+ setActiveTab(tab.id);
3953
+ },
3954
+ onMouseEnter: () => setHoveredTab(tab.id),
3955
+ onMouseLeave: () => setHoveredTab((h) => h === tab.id ? null : h),
3956
+ style: {
3957
+ flex: 1,
3958
+ background: bg,
3959
+ border: "none",
3960
+ borderBottom: active ? `0.2vh solid ${warnColor}` : "0.2vh solid transparent",
3961
+ padding: "0.3vh 1vh",
3962
+ cursor: active ? "default" : "pointer",
3963
+ color: active ? warnColor : "rgba(255,255,255,0.5)",
3964
+ fontFamily: "Akrobat Bold",
3965
+ fontSize: "var(--mantine-font-size-xxs)",
3966
+ letterSpacing: "0.07em",
3967
+ textTransform: "uppercase",
3968
+ transition: "background 0.12s"
3969
+ },
3970
+ children: tab.label
3971
+ },
3972
+ tab.id
3973
+ );
3974
+ }) }),
3975
+ /* @__PURE__ */ jsxRuntime.jsx(
3976
+ CodeView,
3977
+ {
3978
+ code: audit.snippets[activeTab] ?? "",
3979
+ copied: copied === activeTab,
3980
+ onCopy: (e) => {
3981
+ e.stopPropagation();
3982
+ handleCopy(activeTab);
3983
+ },
3984
+ warnColor
3985
+ }
3986
+ )
3987
+ ]
3988
+ },
3989
+ "expanded"
3990
+ ) })
3991
+ ]
3992
+ }
3993
+ );
3994
+ }
3995
+ function CodeView({
3996
+ code,
3997
+ copied,
3998
+ onCopy,
3999
+ warnColor
4000
+ }) {
4001
+ const theme2 = core.useMantineTheme();
4002
+ const [hovered, setHovered] = React6.useState(false);
4003
+ const lines = code === "" ? [""] : code.split("\n");
4004
+ const lineNumWidth = String(lines.length).length;
4005
+ const copyBg = copied ? core.alpha("#22c55e", 0.15) : hovered ? core.alpha(warnColor, 0.18) : core.alpha(warnColor, 0.1);
4006
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
4007
+ /* @__PURE__ */ jsxRuntime.jsxs(
4008
+ "button",
4009
+ {
4010
+ onClick: onCopy,
4011
+ onMouseEnter: () => setHovered(true),
4012
+ onMouseLeave: () => setHovered(false),
4013
+ style: {
4014
+ position: "absolute",
4015
+ top: "0.6vh",
4016
+ right: "0.8vh",
4017
+ zIndex: 2,
4018
+ background: copyBg,
4019
+ border: `0.1vh solid ${core.alpha(copied ? "#22c55e" : warnColor, 0.35)}`,
4020
+ borderRadius: theme2.radius.xs,
4021
+ padding: "0.4vh 0.7vh",
4022
+ cursor: "pointer",
4023
+ display: "flex",
4024
+ alignItems: "center",
4025
+ gap: "0.4vh",
4026
+ transition: "background 0.12s"
4027
+ },
4028
+ children: [
4029
+ copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: "1.4vh", color: "#22c55e" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { size: "1.4vh", color: warnColor }),
4030
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: copied ? "#22c55e" : warnColor, children: copied ? "Copied" : "Copy" })
4031
+ ]
4032
+ }
4033
+ ),
4034
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
4035
+ background: core.alpha(theme2.colors.dark[9], 0.6),
4036
+ maxHeight: "40vh",
4037
+ overflowY: "auto",
4038
+ overflowX: "auto",
4039
+ padding: "0.6vh 0"
4040
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("table", { style: { borderCollapse: "collapse", width: "100%", fontFamily: "monospace", fontSize: "1.2vh", lineHeight: 1.5 }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: lines.map((line, i) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
4041
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: {
4042
+ width: `${lineNumWidth + 2}ch`,
4043
+ textAlign: "right",
4044
+ padding: "0 0.8vh 0 1vh",
4045
+ color: "rgba(255,255,255,0.25)",
4046
+ userSelect: "none",
4047
+ whiteSpace: "nowrap",
4048
+ verticalAlign: "top"
4049
+ }, children: i + 1 }),
4050
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: {
4051
+ padding: "0 1vh",
4052
+ color: "rgba(255,255,255,0.85)",
4053
+ whiteSpace: "pre",
4054
+ verticalAlign: "top"
4055
+ }, children: line || "\u200B" })
4056
+ ] }, i)) }) }) })
4057
+ ] });
4058
+ }
3819
4059
  function getNested(obj, path) {
3820
4060
  return path.split(".").reduce((acc, key) => acc ? acc[key] : void 0, obj);
3821
4061
  }
@@ -4542,7 +4782,8 @@ function ConfigPanelInner({
4542
4782
  resetConfirmText,
4543
4783
  defaultConfig,
4544
4784
  width,
4545
- height
4785
+ height,
4786
+ suppressMissingItemsBanner
4546
4787
  }) {
4547
4788
  const { updateConfig, resetConfig, getHistory } = getScriptConfigInstance();
4548
4789
  const form = useForm();
@@ -4662,8 +4903,8 @@ function ConfigPanelInner({
4662
4903
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { size: "1.4vh", color })
4663
4904
  }
4664
4905
  ),
4665
- /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { minWidth: 0, lineHeight: 1 }, children: [
4666
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4906
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { minWidth: 0, flex: 1, lineHeight: 1, overflow: "hidden" }, children: [
4907
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "md", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4667
4908
  subtitle && /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.08em", c: color, truncate: true, children: subtitle })
4668
4909
  ] })
4669
4910
  ] }),
@@ -4765,18 +5006,21 @@ function ConfigPanelInner({
4765
5006
  ] })
4766
5007
  ] })
4767
5008
  ] }),
4768
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
4769
- framerMotion.motion.div,
4770
- {
4771
- initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4772
- animate: { opacity: 1, y: 0 },
4773
- exit: { opacity: 0, y: -4 },
4774
- transition: { duration: 0.15 },
4775
- style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
4776
- children: children(activeTab)
4777
- },
4778
- activeTab
4779
- ) })
5009
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
5010
+ !suppressMissingItemsBanner && /* @__PURE__ */ jsxRuntime.jsx(MissingItemsBanner, {}),
5011
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
5012
+ framerMotion.motion.div,
5013
+ {
5014
+ initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
5015
+ animate: { opacity: 1, y: 0 },
5016
+ exit: { opacity: 0, y: -4 },
5017
+ transition: { duration: 0.15 },
5018
+ style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
5019
+ children: children(activeTab)
5020
+ },
5021
+ activeTab
5022
+ ) })
5023
+ ] })
4780
5024
  ]
4781
5025
  }
4782
5026
  )
@@ -4818,6 +5062,7 @@ function ConfigPanel(props) {
4818
5062
  if (result?.success) {
4819
5063
  form.reinitialize(cloneConfig(form.values));
4820
5064
  configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
5065
+ useMissingItemsAudit.getState().refresh();
4821
5066
  notifications.notifications.show({
4822
5067
  color: "green",
4823
5068
  title: locale("ConfigSaveSuccessTitle"),
@@ -4855,6 +5100,7 @@ function ConfigPanel(props) {
4855
5100
  }
4856
5101
  ) });
4857
5102
  }
5103
+ var MISSING_COLOR = "#f59e0b";
4858
5104
  function LazyImage({ src, style }) {
4859
5105
  const [visible, setVisible] = React6.useState(false);
4860
5106
  const ref = React6.useRef(null);
@@ -4872,14 +5118,19 @@ function LazyImage({ src, style }) {
4872
5118
  }
4873
5119
  function SelectItem(props) {
4874
5120
  const invItems = useItems();
5121
+ const isMissing = !!props.value && !invItems[props.value];
4875
5122
  const formattedItems = React6.useMemo(() => {
4876
5123
  const seen = /* @__PURE__ */ new Set();
4877
- return useItemsList(props.excludeItemNames ?? []).filter((item) => {
5124
+ const list = useItemsList(props.excludeItemNames ?? []).filter((item) => {
4878
5125
  if (seen.has(item.name)) return false;
4879
5126
  seen.add(item.name);
4880
5127
  return true;
4881
5128
  }).map((item) => ({ value: item.name, label: item.label, image: item.image }));
4882
- }, [invItems, props.excludeItemNames]);
5129
+ if (isMissing) {
5130
+ list.unshift({ value: props.value, label: props.value, image: "" });
5131
+ }
5132
+ return list;
5133
+ }, [invItems, props.excludeItemNames, props.value, isMissing]);
4883
5134
  return /* @__PURE__ */ jsxRuntime.jsx(
4884
5135
  core.Select,
4885
5136
  {
@@ -4894,10 +5145,11 @@ function SelectItem(props) {
4894
5145
  data: formattedItems,
4895
5146
  allowDeselect: false,
4896
5147
  searchable: true,
5148
+ styles: isMissing ? { input: { color: MISSING_COLOR } } : void 0,
4897
5149
  comboboxProps: { withinPortal: true, zIndex: 2e3 },
4898
5150
  leftSectionWidth: "4vh",
4899
5151
  leftSectionPointerEvents: "none",
4900
- leftSection: props.value ? /* @__PURE__ */ jsxRuntime.jsx(
5152
+ leftSection: isMissing ? /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { align: "center", justify: "center", w: "4vh", h: "4vh", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: "2vh", color: MISSING_COLOR, strokeWidth: 2.5 }) }) : props.value ? /* @__PURE__ */ jsxRuntime.jsx(
4901
5153
  core.Image,
4902
5154
  {
4903
5155
  fallbackSrc: "/placeholder.png",
@@ -4908,19 +5160,37 @@ function SelectItem(props) {
4908
5160
  }
4909
5161
  ) : null,
4910
5162
  nothingFoundMessage: locale("NoItemsFound"),
4911
- renderOption: (item) => /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", w: "100%", children: [
4912
- /* @__PURE__ */ jsxRuntime.jsx(
4913
- LazyImage,
4914
- {
4915
- src: invItems[item.option.value]?.image || "",
4916
- style: { aspectRatio: "1 / 1" }
4917
- }
4918
- ),
4919
- /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", children: [
4920
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "sm", children: item.option.label }),
4921
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xxs", c: "dimmed", children: item.option.value })
4922
- ] })
4923
- ] })
5163
+ renderOption: (item) => {
5164
+ const optionMissing = !invItems[item.option.value] && item.option.value === props.value;
5165
+ if (optionMissing) {
5166
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", w: "100%", children: [
5167
+ /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { align: "center", justify: "center", w: "4vh", h: "4vh", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: "2vh", color: MISSING_COLOR, strokeWidth: 2.5 }) }),
5168
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
5169
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "sm", c: MISSING_COLOR, style: { fontFamily: "monospace" }, children: item.option.value }),
5170
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xxs", c: "dimmed", children: locale("ItemNotInInventory") })
5171
+ ] }),
5172
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
5173
+ background: "rgba(245,158,11,0.12)",
5174
+ border: `0.1vh solid ${MISSING_COLOR}59`,
5175
+ borderRadius: "0.3vh",
5176
+ padding: "0.1vh 0.6vh"
5177
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xxs", c: MISSING_COLOR, ff: "Akrobat Bold", tt: "uppercase", lts: "0.06em", children: locale("Missing") }) })
5178
+ ] });
5179
+ }
5180
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", w: "100%", children: [
5181
+ /* @__PURE__ */ jsxRuntime.jsx(
5182
+ LazyImage,
5183
+ {
5184
+ src: invItems[item.option.value]?.image || "",
5185
+ style: { aspectRatio: "1 / 1" }
5186
+ }
5187
+ ),
5188
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", children: [
5189
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "sm", children: item.option.label }),
5190
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xxs", c: "dimmed", children: item.option.value })
5191
+ ] })
5192
+ ] });
5193
+ }
4924
5194
  }
4925
5195
  );
4926
5196
  }
@@ -5575,6 +5845,17 @@ function AdminPageTitle(props) {
5575
5845
  /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", tt: "uppercase", lts: "0.1em", size: "sm", c: "rgba(255,255,255,0.6)", children: locale(props.title) })
5576
5846
  ] });
5577
5847
  }
5848
+ var placementStyle = (placement) => {
5849
+ switch (placement) {
5850
+ case "top-center":
5851
+ return { top: "1vh", left: "50%", transform: "translateX(-50%)" };
5852
+ case "top-right":
5853
+ return { top: "1vh", right: "1vh" };
5854
+ case "top-left":
5855
+ default:
5856
+ return { top: "1vh", left: "1vh" };
5857
+ }
5858
+ };
5578
5859
  var loadPersistedState = (storageKey) => {
5579
5860
  try {
5580
5861
  const raw = localStorage.getItem(storageKey);
@@ -5593,7 +5874,8 @@ function TestBed({
5593
5874
  items,
5594
5875
  storageKey = "testbed:open-state",
5595
5876
  disablePersistence = false,
5596
- title = "TestBed"
5877
+ title = "TestBed",
5878
+ placement = "top-left"
5597
5879
  }) {
5598
5880
  const [open, setOpen] = React6.useState(false);
5599
5881
  const itemsRef = React6.useRef(items);
@@ -5623,8 +5905,7 @@ function TestBed({
5623
5905
  {
5624
5906
  style: {
5625
5907
  position: "fixed",
5626
- top: "1vh",
5627
- left: "1vh",
5908
+ ...placementStyle(placement),
5628
5909
  zIndex: 2147483647,
5629
5910
  pointerEvents: "auto",
5630
5911
  fontSize: "1.4vh"
@@ -6034,6 +6315,21 @@ function DirkProvider({ children, overideResourceName, themeOverride }) {
6034
6315
  ) : children;
6035
6316
  return /* @__PURE__ */ jsxRuntime.jsx(core.MantineProvider, { theme: mergedTheme, defaultColorScheme: "dark", children: /* @__PURE__ */ jsxRuntime.jsx(DirkErrorBoundary, { children: content }) });
6036
6317
  }
6318
+ var Vector2Schema = zod.z.object({
6319
+ x: zod.z.number(),
6320
+ y: zod.z.number()
6321
+ });
6322
+ var Vector3Schema = zod.z.object({
6323
+ x: zod.z.number(),
6324
+ y: zod.z.number(),
6325
+ z: zod.z.number()
6326
+ });
6327
+ var Vector4Schema = zod.z.object({
6328
+ x: zod.z.number(),
6329
+ y: zod.z.number(),
6330
+ z: zod.z.number(),
6331
+ w: zod.z.number()
6332
+ });
6037
6333
 
6038
6334
  exports.AdminPageTitle = AdminPageTitle;
6039
6335
  exports.AsyncSaveButton = AsyncSaveButton;
@@ -6061,6 +6357,7 @@ exports.InfoBox = InfoBox;
6061
6357
  exports.InputContainer = InputContainer;
6062
6358
  exports.LevelBanner = LevelBanner;
6063
6359
  exports.LevelPanel = LevelPanel;
6360
+ exports.MissingItemsBanner = MissingItemsBanner;
6064
6361
  exports.Modal = Modal;
6065
6362
  exports.ModalContext = ModalContext;
6066
6363
  exports.ModalProvider = ModalProvider;
@@ -6079,8 +6376,11 @@ exports.SelectItem = SelectItem;
6079
6376
  exports.TestBed = TestBed;
6080
6377
  exports.Title = Title;
6081
6378
  exports.TornEdgeSVGFilter = TornEdgeSVGFilter;
6379
+ exports.Vector2Schema = Vector2Schema;
6380
+ exports.Vector3Schema = Vector3Schema;
6082
6381
  exports.Vector4DeleteButton = Vector4DeleteButton;
6083
6382
  exports.Vector4Display = Vector4Display;
6383
+ exports.Vector4Schema = Vector4Schema;
6084
6384
  exports.WorldPositionGotoButton = WorldPositionGotoButton;
6085
6385
  exports.WorldPositionSetButton = WorldPositionSetButton;
6086
6386
  exports.colorWithAlpha = colorWithAlpha;
@@ -6127,6 +6427,7 @@ exports.useFormFields = useFormFields;
6127
6427
  exports.useFrameworkGroups = useFrameworkGroups;
6128
6428
  exports.useItems = useItems;
6129
6429
  exports.useItemsList = useItemsList;
6430
+ exports.useMissingItemsAudit = useMissingItemsAudit;
6130
6431
  exports.useModal = useModal;
6131
6432
  exports.useModalActions = useModalActions;
6132
6433
  exports.useNavigation = useNavigation;