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.
@@ -1236,11 +1236,11 @@ var colorNames = {
1236
1236
  Yellow: { r: 255, g: 255, b: 0 },
1237
1237
  YellowGreen: { r: 154, g: 205, b: 50 }
1238
1238
  };
1239
- function colorWithAlpha(color, alpha9) {
1239
+ function colorWithAlpha(color, alpha10) {
1240
1240
  const lowerCasedColor = color.toLowerCase();
1241
1241
  if (colorNames[lowerCasedColor]) {
1242
1242
  const rgb = colorNames[lowerCasedColor];
1243
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha9})`;
1243
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha10})`;
1244
1244
  }
1245
1245
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1246
1246
  const hex = color.slice(1);
@@ -1248,12 +1248,12 @@ function colorWithAlpha(color, alpha9) {
1248
1248
  const r = bigint >> 16 & 255;
1249
1249
  const g = bigint >> 8 & 255;
1250
1250
  const b = bigint & 255;
1251
- return `rgba(${r}, ${g}, ${b}, ${alpha9})`;
1251
+ return `rgba(${r}, ${g}, ${b}, ${alpha10})`;
1252
1252
  }
1253
1253
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1254
1254
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1255
1255
  if (result) {
1256
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha9})`;
1256
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha10})`;
1257
1257
  }
1258
1258
  }
1259
1259
  return color;
@@ -1299,7 +1299,11 @@ async function fetchNui(eventName, data, mockData) {
1299
1299
  return {};
1300
1300
  }
1301
1301
  const overrideResourceName = useSettings.getState().overideResourceName;
1302
- const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName ? overrideResourceName : "dirk-cfx-react";
1302
+ const hasResourceContext = typeof window.GetParentResourceName === "function" || !!overrideResourceName;
1303
+ if (!hasResourceContext) {
1304
+ return mockData ?? {};
1305
+ }
1306
+ const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName;
1303
1307
  try {
1304
1308
  const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1305
1309
  return await resp.json();
@@ -2472,9 +2476,25 @@ function SegmentedProgress(props) {
2472
2476
  }
2473
2477
  );
2474
2478
  }
2479
+ function getSizePreset(size, themeMdFontSize) {
2480
+ switch (size) {
2481
+ case "xs":
2482
+ return { iconFontSize: "1.2vh", iconPadding: "xxs", titleSize: "xxs", titleLineHeight: "1.2vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
2483
+ case "sm":
2484
+ return { iconFontSize: "1.6vh", iconPadding: "xxs", titleSize: "xs", titleLineHeight: "1.6vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
2485
+ case "lg":
2486
+ return { iconFontSize: "2.6vh", iconPadding: "sm", titleSize: "md", titleLineHeight: "2.6vh", descriptionSize: "sm", innerGap: "sm", bottomPad: "sm" };
2487
+ case "xl":
2488
+ return { iconFontSize: "3.2vh", iconPadding: "sm", titleSize: "lg", titleLineHeight: "3.2vh", descriptionSize: "md", innerGap: "md", bottomPad: "md" };
2489
+ case "md":
2490
+ default:
2491
+ return { iconFontSize: themeMdFontSize, iconPadding: "xs", titleSize: "sm", titleLineHeight: themeMdFontSize, descriptionSize: "xs", innerGap: "sm", bottomPad: "sm" };
2492
+ }
2493
+ }
2475
2494
  function Title(props) {
2476
2495
  const game = useSettings((state) => state.game);
2477
2496
  const theme = core.useMantineTheme();
2497
+ const preset = getSizePreset(props.size ?? "md", theme.fontSizes.md);
2478
2498
  return /* @__PURE__ */ jsxRuntime.jsx(
2479
2499
  core.Flex,
2480
2500
  {
@@ -2483,71 +2503,50 @@ function Title(props) {
2483
2503
  gap: "xs",
2484
2504
  w: props.w || "100%",
2485
2505
  p: props.p || "unset",
2486
- pb: !props.p ? "sm" : props.p,
2506
+ pb: !props.p ? preset.bottomPad : props.p,
2487
2507
  style: {
2488
2508
  userSelect: "none",
2489
2509
  borderBottom: props.removeBorder ? "none" : `0.3vh solid ${props.borderColor || colorWithAlpha(theme.colors[theme.primaryColor][9], 0.5)}`
2490
2510
  },
2491
- children: /* @__PURE__ */ jsxRuntime.jsxs(
2492
- core.Flex,
2493
- {
2494
- align: "center",
2495
- justify: "center",
2496
- children: [
2497
- /* @__PURE__ */ jsxRuntime.jsxs(
2498
- core.Flex,
2511
+ children: /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", justify: "center", children: [
2512
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: preset.innerGap, pr: "xs", children: [
2513
+ /* @__PURE__ */ jsxRuntime.jsx(
2514
+ BorderedIcon,
2515
+ {
2516
+ icon: props.icon,
2517
+ fontSize: preset.iconFontSize,
2518
+ color: props.iconColor,
2519
+ p: preset.iconPadding
2520
+ }
2521
+ ),
2522
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "0.25vh", children: [
2523
+ /* @__PURE__ */ jsxRuntime.jsx(
2524
+ core.Text,
2499
2525
  {
2500
- align: "center",
2501
- gap: "sm",
2502
- pr: "xs",
2503
- children: [
2504
- /* @__PURE__ */ jsxRuntime.jsx(
2505
- BorderedIcon,
2506
- {
2507
- icon: props.icon,
2508
- fontSize: theme.fontSizes.md,
2509
- color: props.iconColor
2510
- }
2511
- ),
2512
- /* @__PURE__ */ jsxRuntime.jsxs(
2513
- core.Flex,
2514
- {
2515
- direction: "column",
2516
- gap: "0.25vh",
2517
- children: [
2518
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { p: "0", size: "sm", style: {
2519
- lineHeight: theme.fontSizes.md,
2520
- fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
2521
- letterSpacing: "0.05em",
2522
- textTransform: "uppercase"
2523
- }, children: props.title }),
2524
- /* @__PURE__ */ jsxRuntime.jsx(
2525
- core.Text,
2526
- {
2527
- size: "xs",
2528
- c: "grey",
2529
- style: { whiteSpace: "normal", wordWrap: "break-word" },
2530
- children: props.description
2531
- }
2532
- )
2533
- ]
2534
- }
2535
- )
2536
- ]
2526
+ p: "0",
2527
+ size: preset.titleSize,
2528
+ style: {
2529
+ lineHeight: preset.titleLineHeight,
2530
+ fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
2531
+ letterSpacing: "0.05em",
2532
+ textTransform: "uppercase"
2533
+ },
2534
+ children: props.title
2537
2535
  }
2538
2536
  ),
2539
2537
  /* @__PURE__ */ jsxRuntime.jsx(
2540
- core.Flex,
2538
+ core.Text,
2541
2539
  {
2542
- ml: "auto",
2543
- align: "center",
2544
- gap: "xs",
2545
- children: props.rightSection
2540
+ size: preset.descriptionSize,
2541
+ c: "grey",
2542
+ style: { whiteSpace: "normal", wordWrap: "break-word" },
2543
+ children: props.description
2546
2544
  }
2547
2545
  )
2548
- ]
2549
- }
2550
- )
2546
+ ] })
2547
+ ] }),
2548
+ /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { ml: "auto", align: "center", gap: "xs", children: props.rightSection })
2549
+ ] })
2551
2550
  }
2552
2551
  );
2553
2552
  }
@@ -3205,6 +3204,246 @@ function ConfirmModal({
3205
3204
  }
3206
3205
  ) });
3207
3206
  }
3207
+ var TABS = [
3208
+ { id: "ox", label: "ox_inventory" },
3209
+ { id: "qb", label: "qb-inventory" },
3210
+ { id: "esx", label: "ESX legacy SQL" }
3211
+ ];
3212
+ var useMissingItemsAudit = zustand.create((set, get) => ({
3213
+ data: null,
3214
+ loaded: false,
3215
+ inFlight: false,
3216
+ refresh: async () => {
3217
+ if (get().inFlight) return;
3218
+ set({ inFlight: true });
3219
+ try {
3220
+ const res = await fetchNui("GET_MISSING_ITEMS", void 0, {
3221
+ success: true,
3222
+ data: { missing: [], snippets: { ox: "", qb: "", esx: "" } }
3223
+ });
3224
+ if (res?.success && res.data) {
3225
+ set({ data: res.data, loaded: true });
3226
+ } else {
3227
+ set({ loaded: true });
3228
+ }
3229
+ } catch {
3230
+ set({ loaded: true });
3231
+ } finally {
3232
+ set({ inFlight: false });
3233
+ }
3234
+ }
3235
+ }));
3236
+ function MissingItemsBanner() {
3237
+ const theme = core.useMantineTheme();
3238
+ const audit = useMissingItemsAudit((s) => s.data);
3239
+ const loaded = useMissingItemsAudit((s) => s.loaded);
3240
+ const inFlight = useMissingItemsAudit((s) => s.inFlight);
3241
+ const refresh = useMissingItemsAudit((s) => s.refresh);
3242
+ const [expanded, setExpanded] = react.useState(false);
3243
+ const [activeTab, setActiveTab] = react.useState("ox");
3244
+ const [hoveredTab, setHoveredTab] = react.useState(null);
3245
+ const [copied, setCopied] = react.useState(null);
3246
+ react.useEffect(() => {
3247
+ if (!loaded) refresh();
3248
+ }, [loaded, refresh]);
3249
+ const handleCopy = react.useCallback((tab) => {
3250
+ if (!audit) return;
3251
+ const text = audit.snippets[tab] ?? "";
3252
+ navigator.clipboard.writeText(text).then(() => {
3253
+ setCopied(tab);
3254
+ setTimeout(() => setCopied((c) => c === tab ? null : c), 1500);
3255
+ }).catch(() => {
3256
+ });
3257
+ }, [audit]);
3258
+ const handleRefresh = react.useCallback((e) => {
3259
+ e.stopPropagation();
3260
+ refresh();
3261
+ }, [refresh]);
3262
+ if (!audit || audit.missing.length === 0) return null;
3263
+ const warnColor = "#f59e0b";
3264
+ const names = audit.missing.map((m) => m.name);
3265
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3266
+ "div",
3267
+ {
3268
+ style: {
3269
+ background: core.alpha(warnColor, 0.06),
3270
+ border: `0.1vh solid ${core.alpha(warnColor, 0.35)}`,
3271
+ borderLeft: `0.3vh solid ${warnColor}`,
3272
+ borderRadius: theme.radius.xs,
3273
+ margin: "0.6vh 1vh",
3274
+ overflow: "hidden"
3275
+ },
3276
+ children: [
3277
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "0.8vh", p: "0.8vh 1vh", style: { cursor: "pointer" }, onClick: () => setExpanded((e) => !e), children: [
3278
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { size: "1.8vh", color: warnColor, strokeWidth: 2.5 }),
3279
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
3280
+ /* @__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` }),
3281
+ /* @__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(", ") })
3282
+ ] }),
3283
+ /* @__PURE__ */ jsxRuntime.jsx(
3284
+ "button",
3285
+ {
3286
+ onClick: handleRefresh,
3287
+ disabled: inFlight,
3288
+ style: {
3289
+ background: "transparent",
3290
+ border: "none",
3291
+ padding: "0.3vh",
3292
+ cursor: inFlight ? "wait" : "pointer",
3293
+ display: "flex",
3294
+ alignItems: "center",
3295
+ justifyContent: "center",
3296
+ opacity: inFlight ? 0.4 : 0.7
3297
+ },
3298
+ title: "Re-check",
3299
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3300
+ framerMotion.motion.span,
3301
+ {
3302
+ animate: { rotate: inFlight ? 360 : 0 },
3303
+ transition: inFlight ? { duration: 1, repeat: Infinity, ease: "linear" } : { duration: 0 },
3304
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3305
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: "1.5vh", color: core.alpha(warnColor, 0.7) })
3306
+ }
3307
+ )
3308
+ }
3309
+ ),
3310
+ /* @__PURE__ */ jsxRuntime.jsx(
3311
+ framerMotion.motion.div,
3312
+ {
3313
+ animate: { rotate: expanded ? 180 : 0 },
3314
+ transition: { duration: 0.18 },
3315
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3316
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: "1.8vh", color: core.alpha(warnColor, 0.7) })
3317
+ }
3318
+ )
3319
+ ] }),
3320
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { initial: false, children: expanded && /* @__PURE__ */ jsxRuntime.jsxs(
3321
+ framerMotion.motion.div,
3322
+ {
3323
+ initial: { height: 0, opacity: 0 },
3324
+ animate: { height: "auto", opacity: 1 },
3325
+ exit: { height: 0, opacity: 0 },
3326
+ transition: { duration: 0.18, ease: "easeOut" },
3327
+ style: { overflow: "hidden", borderTop: `0.1vh solid ${core.alpha(warnColor, 0.18)}` },
3328
+ children: [
3329
+ /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { gap: "0", style: { borderBottom: `0.1vh solid ${core.alpha(warnColor, 0.18)}` }, children: TABS.map((tab) => {
3330
+ const active = tab.id === activeTab;
3331
+ const hovered = hoveredTab === tab.id;
3332
+ let bg = "transparent";
3333
+ if (active) bg = core.alpha(warnColor, 0.12);
3334
+ else if (hovered) bg = core.alpha(warnColor, 0.08);
3335
+ return /* @__PURE__ */ jsxRuntime.jsx(
3336
+ "button",
3337
+ {
3338
+ onClick: (e) => {
3339
+ e.stopPropagation();
3340
+ setActiveTab(tab.id);
3341
+ },
3342
+ onMouseEnter: () => setHoveredTab(tab.id),
3343
+ onMouseLeave: () => setHoveredTab((h) => h === tab.id ? null : h),
3344
+ style: {
3345
+ flex: 1,
3346
+ background: bg,
3347
+ border: "none",
3348
+ borderBottom: active ? `0.2vh solid ${warnColor}` : "0.2vh solid transparent",
3349
+ padding: "0.3vh 1vh",
3350
+ cursor: active ? "default" : "pointer",
3351
+ color: active ? warnColor : "rgba(255,255,255,0.5)",
3352
+ fontFamily: "Akrobat Bold",
3353
+ fontSize: "var(--mantine-font-size-xxs)",
3354
+ letterSpacing: "0.07em",
3355
+ textTransform: "uppercase",
3356
+ transition: "background 0.12s"
3357
+ },
3358
+ children: tab.label
3359
+ },
3360
+ tab.id
3361
+ );
3362
+ }) }),
3363
+ /* @__PURE__ */ jsxRuntime.jsx(
3364
+ CodeView,
3365
+ {
3366
+ code: audit.snippets[activeTab] ?? "",
3367
+ copied: copied === activeTab,
3368
+ onCopy: (e) => {
3369
+ e.stopPropagation();
3370
+ handleCopy(activeTab);
3371
+ },
3372
+ warnColor
3373
+ }
3374
+ )
3375
+ ]
3376
+ },
3377
+ "expanded"
3378
+ ) })
3379
+ ]
3380
+ }
3381
+ );
3382
+ }
3383
+ function CodeView({
3384
+ code,
3385
+ copied,
3386
+ onCopy,
3387
+ warnColor
3388
+ }) {
3389
+ const theme = core.useMantineTheme();
3390
+ const [hovered, setHovered] = react.useState(false);
3391
+ const lines = code === "" ? [""] : code.split("\n");
3392
+ const lineNumWidth = String(lines.length).length;
3393
+ const copyBg = copied ? core.alpha("#22c55e", 0.15) : hovered ? core.alpha(warnColor, 0.18) : core.alpha(warnColor, 0.1);
3394
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
3395
+ /* @__PURE__ */ jsxRuntime.jsxs(
3396
+ "button",
3397
+ {
3398
+ onClick: onCopy,
3399
+ onMouseEnter: () => setHovered(true),
3400
+ onMouseLeave: () => setHovered(false),
3401
+ style: {
3402
+ position: "absolute",
3403
+ top: "0.6vh",
3404
+ right: "0.8vh",
3405
+ zIndex: 2,
3406
+ background: copyBg,
3407
+ border: `0.1vh solid ${core.alpha(copied ? "#22c55e" : warnColor, 0.35)}`,
3408
+ borderRadius: theme.radius.xs,
3409
+ padding: "0.4vh 0.7vh",
3410
+ cursor: "pointer",
3411
+ display: "flex",
3412
+ alignItems: "center",
3413
+ gap: "0.4vh",
3414
+ transition: "background 0.12s"
3415
+ },
3416
+ children: [
3417
+ copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: "1.4vh", color: "#22c55e" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { size: "1.4vh", color: warnColor }),
3418
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: copied ? "#22c55e" : warnColor, children: copied ? "Copied" : "Copy" })
3419
+ ]
3420
+ }
3421
+ ),
3422
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
3423
+ background: core.alpha(theme.colors.dark[9], 0.6),
3424
+ maxHeight: "40vh",
3425
+ overflowY: "auto",
3426
+ overflowX: "auto",
3427
+ padding: "0.6vh 0"
3428
+ }, 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: [
3429
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: {
3430
+ width: `${lineNumWidth + 2}ch`,
3431
+ textAlign: "right",
3432
+ padding: "0 0.8vh 0 1vh",
3433
+ color: "rgba(255,255,255,0.25)",
3434
+ userSelect: "none",
3435
+ whiteSpace: "nowrap",
3436
+ verticalAlign: "top"
3437
+ }, children: i + 1 }),
3438
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: {
3439
+ padding: "0 1vh",
3440
+ color: "rgba(255,255,255,0.85)",
3441
+ whiteSpace: "pre",
3442
+ verticalAlign: "top"
3443
+ }, children: line || "\u200B" })
3444
+ ] }, i)) }) }) })
3445
+ ] });
3446
+ }
3208
3447
  function getNested(obj, path) {
3209
3448
  return path.split(".").reduce((acc, key) => acc ? acc[key] : void 0, obj);
3210
3449
  }
@@ -3807,7 +4046,8 @@ function ConfigPanelInner({
3807
4046
  resetConfirmText,
3808
4047
  defaultConfig,
3809
4048
  width,
3810
- height
4049
+ height,
4050
+ suppressMissingItemsBanner
3811
4051
  }) {
3812
4052
  const { updateConfig, resetConfig, getHistory } = getScriptConfigInstance();
3813
4053
  const form = useForm();
@@ -3927,8 +4167,8 @@ function ConfigPanelInner({
3927
4167
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { size: "1.4vh", color })
3928
4168
  }
3929
4169
  ),
3930
- /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { minWidth: 0, lineHeight: 1 }, children: [
3931
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4170
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { minWidth: 0, flex: 1, lineHeight: 1, overflow: "hidden" }, children: [
4171
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "md", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
3932
4172
  subtitle && /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.08em", c: color, truncate: true, children: subtitle })
3933
4173
  ] })
3934
4174
  ] }),
@@ -4030,18 +4270,21 @@ function ConfigPanelInner({
4030
4270
  ] })
4031
4271
  ] })
4032
4272
  ] }),
4033
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
4034
- framerMotion.motion.div,
4035
- {
4036
- initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4037
- animate: { opacity: 1, y: 0 },
4038
- exit: { opacity: 0, y: -4 },
4039
- transition: { duration: 0.15 },
4040
- style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
4041
- children: children(activeTab)
4042
- },
4043
- activeTab
4044
- ) })
4273
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
4274
+ !suppressMissingItemsBanner && /* @__PURE__ */ jsxRuntime.jsx(MissingItemsBanner, {}),
4275
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
4276
+ framerMotion.motion.div,
4277
+ {
4278
+ initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4279
+ animate: { opacity: 1, y: 0 },
4280
+ exit: { opacity: 0, y: -4 },
4281
+ transition: { duration: 0.15 },
4282
+ style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
4283
+ children: children(activeTab)
4284
+ },
4285
+ activeTab
4286
+ ) })
4287
+ ] })
4045
4288
  ]
4046
4289
  }
4047
4290
  )
@@ -4083,6 +4326,7 @@ function ConfigPanel(props) {
4083
4326
  if (result?.success) {
4084
4327
  form.reinitialize(cloneConfig(form.values));
4085
4328
  configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
4329
+ useMissingItemsAudit.getState().refresh();
4086
4330
  notifications.notifications.show({
4087
4331
  color: "green",
4088
4332
  title: locale("ConfigSaveSuccessTitle"),
@@ -4120,6 +4364,7 @@ function ConfigPanel(props) {
4120
4364
  }
4121
4365
  ) });
4122
4366
  }
4367
+ var MISSING_COLOR = "#f59e0b";
4123
4368
  function LazyImage({ src, style }) {
4124
4369
  const [visible, setVisible] = react.useState(false);
4125
4370
  const ref = react.useRef(null);
@@ -4137,14 +4382,19 @@ function LazyImage({ src, style }) {
4137
4382
  }
4138
4383
  function SelectItem(props) {
4139
4384
  const invItems = useItems();
4385
+ const isMissing = !!props.value && !invItems[props.value];
4140
4386
  const formattedItems = react.useMemo(() => {
4141
4387
  const seen = /* @__PURE__ */ new Set();
4142
- return useItemsList(props.excludeItemNames ?? []).filter((item) => {
4388
+ const list = useItemsList(props.excludeItemNames ?? []).filter((item) => {
4143
4389
  if (seen.has(item.name)) return false;
4144
4390
  seen.add(item.name);
4145
4391
  return true;
4146
4392
  }).map((item) => ({ value: item.name, label: item.label, image: item.image }));
4147
- }, [invItems, props.excludeItemNames]);
4393
+ if (isMissing) {
4394
+ list.unshift({ value: props.value, label: props.value, image: "" });
4395
+ }
4396
+ return list;
4397
+ }, [invItems, props.excludeItemNames, props.value, isMissing]);
4148
4398
  return /* @__PURE__ */ jsxRuntime.jsx(
4149
4399
  core.Select,
4150
4400
  {
@@ -4159,10 +4409,11 @@ function SelectItem(props) {
4159
4409
  data: formattedItems,
4160
4410
  allowDeselect: false,
4161
4411
  searchable: true,
4412
+ styles: isMissing ? { input: { color: MISSING_COLOR } } : void 0,
4162
4413
  comboboxProps: { withinPortal: true, zIndex: 2e3 },
4163
4414
  leftSectionWidth: "4vh",
4164
4415
  leftSectionPointerEvents: "none",
4165
- leftSection: props.value ? /* @__PURE__ */ jsxRuntime.jsx(
4416
+ 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(
4166
4417
  core.Image,
4167
4418
  {
4168
4419
  fallbackSrc: "/placeholder.png",
@@ -4173,19 +4424,37 @@ function SelectItem(props) {
4173
4424
  }
4174
4425
  ) : null,
4175
4426
  nothingFoundMessage: locale("NoItemsFound"),
4176
- renderOption: (item) => /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", w: "100%", children: [
4177
- /* @__PURE__ */ jsxRuntime.jsx(
4178
- LazyImage,
4179
- {
4180
- src: invItems[item.option.value]?.image || "",
4181
- style: { aspectRatio: "1 / 1" }
4182
- }
4183
- ),
4184
- /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", children: [
4185
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "sm", children: item.option.label }),
4186
- /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xxs", c: "dimmed", children: item.option.value })
4187
- ] })
4188
- ] })
4427
+ renderOption: (item) => {
4428
+ const optionMissing = !invItems[item.option.value] && item.option.value === props.value;
4429
+ if (optionMissing) {
4430
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", w: "100%", children: [
4431
+ /* @__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 }) }),
4432
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
4433
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "sm", c: MISSING_COLOR, style: { fontFamily: "monospace" }, children: item.option.value }),
4434
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xxs", c: "dimmed", children: locale("ItemNotInInventory") })
4435
+ ] }),
4436
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
4437
+ background: "rgba(245,158,11,0.12)",
4438
+ border: `0.1vh solid ${MISSING_COLOR}59`,
4439
+ borderRadius: "0.3vh",
4440
+ padding: "0.1vh 0.6vh"
4441
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xxs", c: MISSING_COLOR, ff: "Akrobat Bold", tt: "uppercase", lts: "0.06em", children: locale("Missing") }) })
4442
+ ] });
4443
+ }
4444
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", w: "100%", children: [
4445
+ /* @__PURE__ */ jsxRuntime.jsx(
4446
+ LazyImage,
4447
+ {
4448
+ src: invItems[item.option.value]?.image || "",
4449
+ style: { aspectRatio: "1 / 1" }
4450
+ }
4451
+ ),
4452
+ /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", children: [
4453
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "sm", children: item.option.label }),
4454
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { size: "xxs", c: "dimmed", children: item.option.value })
4455
+ ] })
4456
+ ] });
4457
+ }
4189
4458
  }
4190
4459
  );
4191
4460
  }
@@ -4840,6 +5109,17 @@ function AdminPageTitle(props) {
4840
5109
  /* @__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) })
4841
5110
  ] });
4842
5111
  }
5112
+ var placementStyle = (placement) => {
5113
+ switch (placement) {
5114
+ case "top-center":
5115
+ return { top: "1vh", left: "50%", transform: "translateX(-50%)" };
5116
+ case "top-right":
5117
+ return { top: "1vh", right: "1vh" };
5118
+ case "top-left":
5119
+ default:
5120
+ return { top: "1vh", left: "1vh" };
5121
+ }
5122
+ };
4843
5123
  var loadPersistedState = (storageKey) => {
4844
5124
  try {
4845
5125
  const raw = localStorage.getItem(storageKey);
@@ -4858,7 +5138,8 @@ function TestBed({
4858
5138
  items,
4859
5139
  storageKey = "testbed:open-state",
4860
5140
  disablePersistence = false,
4861
- title = "TestBed"
5141
+ title = "TestBed",
5142
+ placement = "top-left"
4862
5143
  }) {
4863
5144
  const [open, setOpen] = react.useState(false);
4864
5145
  const itemsRef = react.useRef(items);
@@ -4888,8 +5169,7 @@ function TestBed({
4888
5169
  {
4889
5170
  style: {
4890
5171
  position: "fixed",
4891
- top: "1vh",
4892
- left: "1vh",
5172
+ ...placementStyle(placement),
4893
5173
  zIndex: 2147483647,
4894
5174
  pointerEvents: "auto",
4895
5175
  fontSize: "1.4vh"
@@ -5015,6 +5295,7 @@ exports.InfoBox = InfoBox;
5015
5295
  exports.InputContainer = InputContainer;
5016
5296
  exports.LevelBanner = LevelBanner;
5017
5297
  exports.LevelPanel = LevelPanel;
5298
+ exports.MissingItemsBanner = MissingItemsBanner;
5018
5299
  exports.Modal = Modal;
5019
5300
  exports.ModalContext = ModalContext;
5020
5301
  exports.ModalProvider = ModalProvider;
@@ -5036,6 +5317,7 @@ exports.Vector4DeleteButton = Vector4DeleteButton;
5036
5317
  exports.Vector4Display = Vector4Display;
5037
5318
  exports.WorldPositionGotoButton = WorldPositionGotoButton;
5038
5319
  exports.WorldPositionSetButton = WorldPositionSetButton;
5320
+ exports.useMissingItemsAudit = useMissingItemsAudit;
5039
5321
  exports.useModal = useModal;
5040
5322
  exports.useModalActions = useModalActions;
5041
5323
  exports.useNavigation = useNavigation;