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.
@@ -1,10 +1,10 @@
1
1
  import { Flex, Text, Image, TextInput, Select, Box, useMantineTheme, Tooltip, alpha, Progress, RingProgress, Portal, Button, NumberInput, MultiSelect, Loader, ActionIcon, Stack, Group, JsonInput } from '@mantine/core';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import { createContext, useContext, useRef, useState, useEffect, useMemo } from 'react';
3
+ import { createContext, useContext, useRef, useState, useEffect, useCallback, useMemo } from 'react';
4
4
  import { create, useStore, createStore } from 'zustand';
5
5
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6
6
  import { motion, AnimatePresence, useMotionValue } from 'framer-motion';
7
- 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';
7
+ 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';
8
8
  import clickSoundUrl from '../click_sound-PNCRRTM4.mp3';
9
9
  import hoverSoundUrl from '../hover_sound-NBUA222C.mp3';
10
10
  import { notifications } from '@mantine/notifications';
@@ -1229,11 +1229,11 @@ var colorNames = {
1229
1229
  Yellow: { r: 255, g: 255, b: 0 },
1230
1230
  YellowGreen: { r: 154, g: 205, b: 50 }
1231
1231
  };
1232
- function colorWithAlpha(color, alpha9) {
1232
+ function colorWithAlpha(color, alpha10) {
1233
1233
  const lowerCasedColor = color.toLowerCase();
1234
1234
  if (colorNames[lowerCasedColor]) {
1235
1235
  const rgb = colorNames[lowerCasedColor];
1236
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha9})`;
1236
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha10})`;
1237
1237
  }
1238
1238
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1239
1239
  const hex = color.slice(1);
@@ -1241,12 +1241,12 @@ function colorWithAlpha(color, alpha9) {
1241
1241
  const r = bigint >> 16 & 255;
1242
1242
  const g = bigint >> 8 & 255;
1243
1243
  const b = bigint & 255;
1244
- return `rgba(${r}, ${g}, ${b}, ${alpha9})`;
1244
+ return `rgba(${r}, ${g}, ${b}, ${alpha10})`;
1245
1245
  }
1246
1246
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1247
1247
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1248
1248
  if (result) {
1249
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha9})`;
1249
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha10})`;
1250
1250
  }
1251
1251
  }
1252
1252
  return color;
@@ -1292,7 +1292,11 @@ async function fetchNui(eventName, data, mockData) {
1292
1292
  return {};
1293
1293
  }
1294
1294
  const overrideResourceName = useSettings.getState().overideResourceName;
1295
- const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName ? overrideResourceName : "dirk-cfx-react";
1295
+ const hasResourceContext = typeof window.GetParentResourceName === "function" || !!overrideResourceName;
1296
+ if (!hasResourceContext) {
1297
+ return mockData ?? {};
1298
+ }
1299
+ const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName;
1296
1300
  try {
1297
1301
  const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1298
1302
  return await resp.json();
@@ -2465,9 +2469,25 @@ function SegmentedProgress(props) {
2465
2469
  }
2466
2470
  );
2467
2471
  }
2472
+ function getSizePreset(size, themeMdFontSize) {
2473
+ switch (size) {
2474
+ case "xs":
2475
+ return { iconFontSize: "1.2vh", iconPadding: "xxs", titleSize: "xxs", titleLineHeight: "1.2vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
2476
+ case "sm":
2477
+ return { iconFontSize: "1.6vh", iconPadding: "xxs", titleSize: "xs", titleLineHeight: "1.6vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
2478
+ case "lg":
2479
+ return { iconFontSize: "2.6vh", iconPadding: "sm", titleSize: "md", titleLineHeight: "2.6vh", descriptionSize: "sm", innerGap: "sm", bottomPad: "sm" };
2480
+ case "xl":
2481
+ return { iconFontSize: "3.2vh", iconPadding: "sm", titleSize: "lg", titleLineHeight: "3.2vh", descriptionSize: "md", innerGap: "md", bottomPad: "md" };
2482
+ case "md":
2483
+ default:
2484
+ return { iconFontSize: themeMdFontSize, iconPadding: "xs", titleSize: "sm", titleLineHeight: themeMdFontSize, descriptionSize: "xs", innerGap: "sm", bottomPad: "sm" };
2485
+ }
2486
+ }
2468
2487
  function Title(props) {
2469
2488
  const game = useSettings((state) => state.game);
2470
2489
  const theme = useMantineTheme();
2490
+ const preset = getSizePreset(props.size ?? "md", theme.fontSizes.md);
2471
2491
  return /* @__PURE__ */ jsx(
2472
2492
  Flex,
2473
2493
  {
@@ -2476,71 +2496,50 @@ function Title(props) {
2476
2496
  gap: "xs",
2477
2497
  w: props.w || "100%",
2478
2498
  p: props.p || "unset",
2479
- pb: !props.p ? "sm" : props.p,
2499
+ pb: !props.p ? preset.bottomPad : props.p,
2480
2500
  style: {
2481
2501
  userSelect: "none",
2482
2502
  borderBottom: props.removeBorder ? "none" : `0.3vh solid ${props.borderColor || colorWithAlpha(theme.colors[theme.primaryColor][9], 0.5)}`
2483
2503
  },
2484
- children: /* @__PURE__ */ jsxs(
2485
- Flex,
2486
- {
2487
- align: "center",
2488
- justify: "center",
2489
- children: [
2490
- /* @__PURE__ */ jsxs(
2491
- Flex,
2504
+ children: /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "center", children: [
2505
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: preset.innerGap, pr: "xs", children: [
2506
+ /* @__PURE__ */ jsx(
2507
+ BorderedIcon,
2508
+ {
2509
+ icon: props.icon,
2510
+ fontSize: preset.iconFontSize,
2511
+ color: props.iconColor,
2512
+ p: preset.iconPadding
2513
+ }
2514
+ ),
2515
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "0.25vh", children: [
2516
+ /* @__PURE__ */ jsx(
2517
+ Text,
2492
2518
  {
2493
- align: "center",
2494
- gap: "sm",
2495
- pr: "xs",
2496
- children: [
2497
- /* @__PURE__ */ jsx(
2498
- BorderedIcon,
2499
- {
2500
- icon: props.icon,
2501
- fontSize: theme.fontSizes.md,
2502
- color: props.iconColor
2503
- }
2504
- ),
2505
- /* @__PURE__ */ jsxs(
2506
- Flex,
2507
- {
2508
- direction: "column",
2509
- gap: "0.25vh",
2510
- children: [
2511
- /* @__PURE__ */ jsx(Text, { p: "0", size: "sm", style: {
2512
- lineHeight: theme.fontSizes.md,
2513
- fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
2514
- letterSpacing: "0.05em",
2515
- textTransform: "uppercase"
2516
- }, children: props.title }),
2517
- /* @__PURE__ */ jsx(
2518
- Text,
2519
- {
2520
- size: "xs",
2521
- c: "grey",
2522
- style: { whiteSpace: "normal", wordWrap: "break-word" },
2523
- children: props.description
2524
- }
2525
- )
2526
- ]
2527
- }
2528
- )
2529
- ]
2519
+ p: "0",
2520
+ size: preset.titleSize,
2521
+ style: {
2522
+ lineHeight: preset.titleLineHeight,
2523
+ fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
2524
+ letterSpacing: "0.05em",
2525
+ textTransform: "uppercase"
2526
+ },
2527
+ children: props.title
2530
2528
  }
2531
2529
  ),
2532
2530
  /* @__PURE__ */ jsx(
2533
- Flex,
2531
+ Text,
2534
2532
  {
2535
- ml: "auto",
2536
- align: "center",
2537
- gap: "xs",
2538
- children: props.rightSection
2533
+ size: preset.descriptionSize,
2534
+ c: "grey",
2535
+ style: { whiteSpace: "normal", wordWrap: "break-word" },
2536
+ children: props.description
2539
2537
  }
2540
2538
  )
2541
- ]
2542
- }
2543
- )
2539
+ ] })
2540
+ ] }),
2541
+ /* @__PURE__ */ jsx(Flex, { ml: "auto", align: "center", gap: "xs", children: props.rightSection })
2542
+ ] })
2544
2543
  }
2545
2544
  );
2546
2545
  }
@@ -3198,6 +3197,246 @@ function ConfirmModal({
3198
3197
  }
3199
3198
  ) });
3200
3199
  }
3200
+ var TABS = [
3201
+ { id: "ox", label: "ox_inventory" },
3202
+ { id: "qb", label: "qb-inventory" },
3203
+ { id: "esx", label: "ESX legacy SQL" }
3204
+ ];
3205
+ var useMissingItemsAudit = create((set, get) => ({
3206
+ data: null,
3207
+ loaded: false,
3208
+ inFlight: false,
3209
+ refresh: async () => {
3210
+ if (get().inFlight) return;
3211
+ set({ inFlight: true });
3212
+ try {
3213
+ const res = await fetchNui("GET_MISSING_ITEMS", void 0, {
3214
+ success: true,
3215
+ data: { missing: [], snippets: { ox: "", qb: "", esx: "" } }
3216
+ });
3217
+ if (res?.success && res.data) {
3218
+ set({ data: res.data, loaded: true });
3219
+ } else {
3220
+ set({ loaded: true });
3221
+ }
3222
+ } catch {
3223
+ set({ loaded: true });
3224
+ } finally {
3225
+ set({ inFlight: false });
3226
+ }
3227
+ }
3228
+ }));
3229
+ function MissingItemsBanner() {
3230
+ const theme = useMantineTheme();
3231
+ const audit = useMissingItemsAudit((s) => s.data);
3232
+ const loaded = useMissingItemsAudit((s) => s.loaded);
3233
+ const inFlight = useMissingItemsAudit((s) => s.inFlight);
3234
+ const refresh = useMissingItemsAudit((s) => s.refresh);
3235
+ const [expanded, setExpanded] = useState(false);
3236
+ const [activeTab, setActiveTab] = useState("ox");
3237
+ const [hoveredTab, setHoveredTab] = useState(null);
3238
+ const [copied, setCopied] = useState(null);
3239
+ useEffect(() => {
3240
+ if (!loaded) refresh();
3241
+ }, [loaded, refresh]);
3242
+ const handleCopy = useCallback((tab) => {
3243
+ if (!audit) return;
3244
+ const text = audit.snippets[tab] ?? "";
3245
+ navigator.clipboard.writeText(text).then(() => {
3246
+ setCopied(tab);
3247
+ setTimeout(() => setCopied((c) => c === tab ? null : c), 1500);
3248
+ }).catch(() => {
3249
+ });
3250
+ }, [audit]);
3251
+ const handleRefresh = useCallback((e) => {
3252
+ e.stopPropagation();
3253
+ refresh();
3254
+ }, [refresh]);
3255
+ if (!audit || audit.missing.length === 0) return null;
3256
+ const warnColor = "#f59e0b";
3257
+ const names = audit.missing.map((m) => m.name);
3258
+ return /* @__PURE__ */ jsxs(
3259
+ "div",
3260
+ {
3261
+ style: {
3262
+ background: alpha(warnColor, 0.06),
3263
+ border: `0.1vh solid ${alpha(warnColor, 0.35)}`,
3264
+ borderLeft: `0.3vh solid ${warnColor}`,
3265
+ borderRadius: theme.radius.xs,
3266
+ margin: "0.6vh 1vh",
3267
+ overflow: "hidden"
3268
+ },
3269
+ children: [
3270
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "0.8vh", p: "0.8vh 1vh", style: { cursor: "pointer" }, onClick: () => setExpanded((e) => !e), children: [
3271
+ /* @__PURE__ */ jsx(AlertTriangle, { size: "1.8vh", color: warnColor, strokeWidth: 2.5 }),
3272
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
3273
+ /* @__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` }),
3274
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.5)", lineClamp: 1, style: { fontFamily: "monospace" }, children: names.join(", ") })
3275
+ ] }),
3276
+ /* @__PURE__ */ jsx(
3277
+ "button",
3278
+ {
3279
+ onClick: handleRefresh,
3280
+ disabled: inFlight,
3281
+ style: {
3282
+ background: "transparent",
3283
+ border: "none",
3284
+ padding: "0.3vh",
3285
+ cursor: inFlight ? "wait" : "pointer",
3286
+ display: "flex",
3287
+ alignItems: "center",
3288
+ justifyContent: "center",
3289
+ opacity: inFlight ? 0.4 : 0.7
3290
+ },
3291
+ title: "Re-check",
3292
+ children: /* @__PURE__ */ jsx(
3293
+ motion.span,
3294
+ {
3295
+ animate: { rotate: inFlight ? 360 : 0 },
3296
+ transition: inFlight ? { duration: 1, repeat: Infinity, ease: "linear" } : { duration: 0 },
3297
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3298
+ children: /* @__PURE__ */ jsx(RefreshCw, { size: "1.5vh", color: alpha(warnColor, 0.7) })
3299
+ }
3300
+ )
3301
+ }
3302
+ ),
3303
+ /* @__PURE__ */ jsx(
3304
+ motion.div,
3305
+ {
3306
+ animate: { rotate: expanded ? 180 : 0 },
3307
+ transition: { duration: 0.18 },
3308
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3309
+ children: /* @__PURE__ */ jsx(ChevronDown, { size: "1.8vh", color: alpha(warnColor, 0.7) })
3310
+ }
3311
+ )
3312
+ ] }),
3313
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: expanded && /* @__PURE__ */ jsxs(
3314
+ motion.div,
3315
+ {
3316
+ initial: { height: 0, opacity: 0 },
3317
+ animate: { height: "auto", opacity: 1 },
3318
+ exit: { height: 0, opacity: 0 },
3319
+ transition: { duration: 0.18, ease: "easeOut" },
3320
+ style: { overflow: "hidden", borderTop: `0.1vh solid ${alpha(warnColor, 0.18)}` },
3321
+ children: [
3322
+ /* @__PURE__ */ jsx(Flex, { gap: "0", style: { borderBottom: `0.1vh solid ${alpha(warnColor, 0.18)}` }, children: TABS.map((tab) => {
3323
+ const active = tab.id === activeTab;
3324
+ const hovered = hoveredTab === tab.id;
3325
+ let bg = "transparent";
3326
+ if (active) bg = alpha(warnColor, 0.12);
3327
+ else if (hovered) bg = alpha(warnColor, 0.08);
3328
+ return /* @__PURE__ */ jsx(
3329
+ "button",
3330
+ {
3331
+ onClick: (e) => {
3332
+ e.stopPropagation();
3333
+ setActiveTab(tab.id);
3334
+ },
3335
+ onMouseEnter: () => setHoveredTab(tab.id),
3336
+ onMouseLeave: () => setHoveredTab((h) => h === tab.id ? null : h),
3337
+ style: {
3338
+ flex: 1,
3339
+ background: bg,
3340
+ border: "none",
3341
+ borderBottom: active ? `0.2vh solid ${warnColor}` : "0.2vh solid transparent",
3342
+ padding: "0.3vh 1vh",
3343
+ cursor: active ? "default" : "pointer",
3344
+ color: active ? warnColor : "rgba(255,255,255,0.5)",
3345
+ fontFamily: "Akrobat Bold",
3346
+ fontSize: "var(--mantine-font-size-xxs)",
3347
+ letterSpacing: "0.07em",
3348
+ textTransform: "uppercase",
3349
+ transition: "background 0.12s"
3350
+ },
3351
+ children: tab.label
3352
+ },
3353
+ tab.id
3354
+ );
3355
+ }) }),
3356
+ /* @__PURE__ */ jsx(
3357
+ CodeView,
3358
+ {
3359
+ code: audit.snippets[activeTab] ?? "",
3360
+ copied: copied === activeTab,
3361
+ onCopy: (e) => {
3362
+ e.stopPropagation();
3363
+ handleCopy(activeTab);
3364
+ },
3365
+ warnColor
3366
+ }
3367
+ )
3368
+ ]
3369
+ },
3370
+ "expanded"
3371
+ ) })
3372
+ ]
3373
+ }
3374
+ );
3375
+ }
3376
+ function CodeView({
3377
+ code,
3378
+ copied,
3379
+ onCopy,
3380
+ warnColor
3381
+ }) {
3382
+ const theme = useMantineTheme();
3383
+ const [hovered, setHovered] = useState(false);
3384
+ const lines = code === "" ? [""] : code.split("\n");
3385
+ const lineNumWidth = String(lines.length).length;
3386
+ const copyBg = copied ? alpha("#22c55e", 0.15) : hovered ? alpha(warnColor, 0.18) : alpha(warnColor, 0.1);
3387
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
3388
+ /* @__PURE__ */ jsxs(
3389
+ "button",
3390
+ {
3391
+ onClick: onCopy,
3392
+ onMouseEnter: () => setHovered(true),
3393
+ onMouseLeave: () => setHovered(false),
3394
+ style: {
3395
+ position: "absolute",
3396
+ top: "0.6vh",
3397
+ right: "0.8vh",
3398
+ zIndex: 2,
3399
+ background: copyBg,
3400
+ border: `0.1vh solid ${alpha(copied ? "#22c55e" : warnColor, 0.35)}`,
3401
+ borderRadius: theme.radius.xs,
3402
+ padding: "0.4vh 0.7vh",
3403
+ cursor: "pointer",
3404
+ display: "flex",
3405
+ alignItems: "center",
3406
+ gap: "0.4vh",
3407
+ transition: "background 0.12s"
3408
+ },
3409
+ children: [
3410
+ copied ? /* @__PURE__ */ jsx(Check, { size: "1.4vh", color: "#22c55e" }) : /* @__PURE__ */ jsx(Copy, { size: "1.4vh", color: warnColor }),
3411
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: copied ? "#22c55e" : warnColor, children: copied ? "Copied" : "Copy" })
3412
+ ]
3413
+ }
3414
+ ),
3415
+ /* @__PURE__ */ jsx("div", { style: {
3416
+ background: alpha(theme.colors.dark[9], 0.6),
3417
+ maxHeight: "40vh",
3418
+ overflowY: "auto",
3419
+ overflowX: "auto",
3420
+ padding: "0.6vh 0"
3421
+ }, 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: [
3422
+ /* @__PURE__ */ jsx("td", { style: {
3423
+ width: `${lineNumWidth + 2}ch`,
3424
+ textAlign: "right",
3425
+ padding: "0 0.8vh 0 1vh",
3426
+ color: "rgba(255,255,255,0.25)",
3427
+ userSelect: "none",
3428
+ whiteSpace: "nowrap",
3429
+ verticalAlign: "top"
3430
+ }, children: i + 1 }),
3431
+ /* @__PURE__ */ jsx("td", { style: {
3432
+ padding: "0 1vh",
3433
+ color: "rgba(255,255,255,0.85)",
3434
+ whiteSpace: "pre",
3435
+ verticalAlign: "top"
3436
+ }, children: line || "\u200B" })
3437
+ ] }, i)) }) }) })
3438
+ ] });
3439
+ }
3201
3440
  function getNested(obj, path) {
3202
3441
  return path.split(".").reduce((acc, key) => acc ? acc[key] : void 0, obj);
3203
3442
  }
@@ -3800,7 +4039,8 @@ function ConfigPanelInner({
3800
4039
  resetConfirmText,
3801
4040
  defaultConfig,
3802
4041
  width,
3803
- height
4042
+ height,
4043
+ suppressMissingItemsBanner
3804
4044
  }) {
3805
4045
  const { updateConfig, resetConfig, getHistory } = getScriptConfigInstance();
3806
4046
  const form = useForm();
@@ -3920,8 +4160,8 @@ function ConfigPanelInner({
3920
4160
  children: /* @__PURE__ */ jsx(ArrowLeft, { size: "1.4vh", color })
3921
4161
  }
3922
4162
  ),
3923
- /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { minWidth: 0, lineHeight: 1 }, children: [
3924
- /* @__PURE__ */ jsx(Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4163
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { minWidth: 0, flex: 1, lineHeight: 1, overflow: "hidden" }, children: [
4164
+ /* @__PURE__ */ jsx(Text, { size: "md", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
3925
4165
  subtitle && /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.08em", c: color, truncate: true, children: subtitle })
3926
4166
  ] })
3927
4167
  ] }),
@@ -4023,18 +4263,21 @@ function ConfigPanelInner({
4023
4263
  ] })
4024
4264
  ] })
4025
4265
  ] }),
4026
- /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
4027
- motion.div,
4028
- {
4029
- initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4030
- animate: { opacity: 1, y: 0 },
4031
- exit: { opacity: 0, y: -4 },
4032
- transition: { duration: 0.15 },
4033
- style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
4034
- children: children(activeTab)
4035
- },
4036
- activeTab
4037
- ) })
4266
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
4267
+ !suppressMissingItemsBanner && /* @__PURE__ */ jsx(MissingItemsBanner, {}),
4268
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
4269
+ motion.div,
4270
+ {
4271
+ initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4272
+ animate: { opacity: 1, y: 0 },
4273
+ exit: { opacity: 0, y: -4 },
4274
+ transition: { duration: 0.15 },
4275
+ style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
4276
+ children: children(activeTab)
4277
+ },
4278
+ activeTab
4279
+ ) })
4280
+ ] })
4038
4281
  ]
4039
4282
  }
4040
4283
  )
@@ -4076,6 +4319,7 @@ function ConfigPanel(props) {
4076
4319
  if (result?.success) {
4077
4320
  form.reinitialize(cloneConfig(form.values));
4078
4321
  configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
4322
+ useMissingItemsAudit.getState().refresh();
4079
4323
  notifications.show({
4080
4324
  color: "green",
4081
4325
  title: locale("ConfigSaveSuccessTitle"),
@@ -4113,6 +4357,7 @@ function ConfigPanel(props) {
4113
4357
  }
4114
4358
  ) });
4115
4359
  }
4360
+ var MISSING_COLOR = "#f59e0b";
4116
4361
  function LazyImage({ src, style }) {
4117
4362
  const [visible, setVisible] = useState(false);
4118
4363
  const ref = useRef(null);
@@ -4130,14 +4375,19 @@ function LazyImage({ src, style }) {
4130
4375
  }
4131
4376
  function SelectItem(props) {
4132
4377
  const invItems = useItems();
4378
+ const isMissing = !!props.value && !invItems[props.value];
4133
4379
  const formattedItems = useMemo(() => {
4134
4380
  const seen = /* @__PURE__ */ new Set();
4135
- return useItemsList(props.excludeItemNames ?? []).filter((item) => {
4381
+ const list = useItemsList(props.excludeItemNames ?? []).filter((item) => {
4136
4382
  if (seen.has(item.name)) return false;
4137
4383
  seen.add(item.name);
4138
4384
  return true;
4139
4385
  }).map((item) => ({ value: item.name, label: item.label, image: item.image }));
4140
- }, [invItems, props.excludeItemNames]);
4386
+ if (isMissing) {
4387
+ list.unshift({ value: props.value, label: props.value, image: "" });
4388
+ }
4389
+ return list;
4390
+ }, [invItems, props.excludeItemNames, props.value, isMissing]);
4141
4391
  return /* @__PURE__ */ jsx(
4142
4392
  Select,
4143
4393
  {
@@ -4152,10 +4402,11 @@ function SelectItem(props) {
4152
4402
  data: formattedItems,
4153
4403
  allowDeselect: false,
4154
4404
  searchable: true,
4405
+ styles: isMissing ? { input: { color: MISSING_COLOR } } : void 0,
4155
4406
  comboboxProps: { withinPortal: true, zIndex: 2e3 },
4156
4407
  leftSectionWidth: "4vh",
4157
4408
  leftSectionPointerEvents: "none",
4158
- leftSection: props.value ? /* @__PURE__ */ jsx(
4409
+ 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(
4159
4410
  Image,
4160
4411
  {
4161
4412
  fallbackSrc: "/placeholder.png",
@@ -4166,19 +4417,37 @@ function SelectItem(props) {
4166
4417
  }
4167
4418
  ) : null,
4168
4419
  nothingFoundMessage: locale("NoItemsFound"),
4169
- renderOption: (item) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", w: "100%", children: [
4170
- /* @__PURE__ */ jsx(
4171
- LazyImage,
4172
- {
4173
- src: invItems[item.option.value]?.image || "",
4174
- style: { aspectRatio: "1 / 1" }
4175
- }
4176
- ),
4177
- /* @__PURE__ */ jsxs(Flex, { direction: "column", children: [
4178
- /* @__PURE__ */ jsx(Text, { size: "sm", children: item.option.label }),
4179
- /* @__PURE__ */ jsx(Text, { size: "xxs", c: "dimmed", children: item.option.value })
4180
- ] })
4181
- ] })
4420
+ renderOption: (item) => {
4421
+ const optionMissing = !invItems[item.option.value] && item.option.value === props.value;
4422
+ if (optionMissing) {
4423
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", w: "100%", children: [
4424
+ /* @__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 }) }),
4425
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
4426
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: MISSING_COLOR, style: { fontFamily: "monospace" }, children: item.option.value }),
4427
+ /* @__PURE__ */ jsx(Text, { size: "xxs", c: "dimmed", children: locale("ItemNotInInventory") })
4428
+ ] }),
4429
+ /* @__PURE__ */ jsx("div", { style: {
4430
+ background: "rgba(245,158,11,0.12)",
4431
+ border: `0.1vh solid ${MISSING_COLOR}59`,
4432
+ borderRadius: "0.3vh",
4433
+ padding: "0.1vh 0.6vh"
4434
+ }, children: /* @__PURE__ */ jsx(Text, { size: "xxs", c: MISSING_COLOR, ff: "Akrobat Bold", tt: "uppercase", lts: "0.06em", children: locale("Missing") }) })
4435
+ ] });
4436
+ }
4437
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", w: "100%", children: [
4438
+ /* @__PURE__ */ jsx(
4439
+ LazyImage,
4440
+ {
4441
+ src: invItems[item.option.value]?.image || "",
4442
+ style: { aspectRatio: "1 / 1" }
4443
+ }
4444
+ ),
4445
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", children: [
4446
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: item.option.label }),
4447
+ /* @__PURE__ */ jsx(Text, { size: "xxs", c: "dimmed", children: item.option.value })
4448
+ ] })
4449
+ ] });
4450
+ }
4182
4451
  }
4183
4452
  );
4184
4453
  }
@@ -4833,6 +5102,17 @@ function AdminPageTitle(props) {
4833
5102
  /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", tt: "uppercase", lts: "0.1em", size: "sm", c: "rgba(255,255,255,0.6)", children: locale(props.title) })
4834
5103
  ] });
4835
5104
  }
5105
+ var placementStyle = (placement) => {
5106
+ switch (placement) {
5107
+ case "top-center":
5108
+ return { top: "1vh", left: "50%", transform: "translateX(-50%)" };
5109
+ case "top-right":
5110
+ return { top: "1vh", right: "1vh" };
5111
+ case "top-left":
5112
+ default:
5113
+ return { top: "1vh", left: "1vh" };
5114
+ }
5115
+ };
4836
5116
  var loadPersistedState = (storageKey) => {
4837
5117
  try {
4838
5118
  const raw = localStorage.getItem(storageKey);
@@ -4851,7 +5131,8 @@ function TestBed({
4851
5131
  items,
4852
5132
  storageKey = "testbed:open-state",
4853
5133
  disablePersistence = false,
4854
- title = "TestBed"
5134
+ title = "TestBed",
5135
+ placement = "top-left"
4855
5136
  }) {
4856
5137
  const [open, setOpen] = useState(false);
4857
5138
  const itemsRef = useRef(items);
@@ -4881,8 +5162,7 @@ function TestBed({
4881
5162
  {
4882
5163
  style: {
4883
5164
  position: "fixed",
4884
- top: "1vh",
4885
- left: "1vh",
5165
+ ...placementStyle(placement),
4886
5166
  zIndex: 2147483647,
4887
5167
  pointerEvents: "auto",
4888
5168
  fontSize: "1.4vh"
@@ -4988,6 +5268,6 @@ function TestBed({
4988
5268
  );
4989
5269
  }
4990
5270
 
4991
- export { AdminPageTitle, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, FiveMKeyBindInput, FloatingParticles, GroupName, GroupRank, GroupSelect, InfoBox, InputContainer, LevelBanner, LevelPanel, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PositionPicker, PromptModal, SegmentedControl, SegmentedProgress, SelectItem, TestBed, Title, Vector4DeleteButton, Vector4Display, WorldPositionGotoButton, WorldPositionSetButton, useModal, useModalActions, useNavigation, useNavigationStore };
5271
+ export { AdminPageTitle, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, FiveMKeyBindInput, FloatingParticles, GroupName, GroupRank, GroupSelect, InfoBox, InputContainer, LevelBanner, LevelPanel, MissingItemsBanner, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PositionPicker, PromptModal, SegmentedControl, SegmentedProgress, SelectItem, TestBed, Title, Vector4DeleteButton, Vector4Display, WorldPositionGotoButton, WorldPositionSetButton, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore };
4992
5272
  //# sourceMappingURL=index.js.map
4993
5273
  //# sourceMappingURL=index.js.map