dirk-cfx-react 1.1.70 → 1.1.72

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,12 +1,13 @@
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
+ import { notifications } from '@mantine/notifications';
10
11
  import { QueryClient, QueryClientProvider, useInfiniteQuery } from '@tanstack/react-query';
11
12
 
12
13
  // src/components/BlipSelect.tsx
@@ -1228,11 +1229,11 @@ var colorNames = {
1228
1229
  Yellow: { r: 255, g: 255, b: 0 },
1229
1230
  YellowGreen: { r: 154, g: 205, b: 50 }
1230
1231
  };
1231
- function colorWithAlpha(color, alpha9) {
1232
+ function colorWithAlpha(color, alpha10) {
1232
1233
  const lowerCasedColor = color.toLowerCase();
1233
1234
  if (colorNames[lowerCasedColor]) {
1234
1235
  const rgb = colorNames[lowerCasedColor];
1235
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha9})`;
1236
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha10})`;
1236
1237
  }
1237
1238
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1238
1239
  const hex = color.slice(1);
@@ -1240,12 +1241,12 @@ function colorWithAlpha(color, alpha9) {
1240
1241
  const r = bigint >> 16 & 255;
1241
1242
  const g = bigint >> 8 & 255;
1242
1243
  const b = bigint & 255;
1243
- return `rgba(${r}, ${g}, ${b}, ${alpha9})`;
1244
+ return `rgba(${r}, ${g}, ${b}, ${alpha10})`;
1244
1245
  }
1245
1246
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1246
1247
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1247
1248
  if (result) {
1248
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha9})`;
1249
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha10})`;
1249
1250
  }
1250
1251
  }
1251
1252
  return color;
@@ -3197,6 +3198,246 @@ function ConfirmModal({
3197
3198
  }
3198
3199
  ) });
3199
3200
  }
3201
+ var TABS = [
3202
+ { id: "ox", label: "ox_inventory" },
3203
+ { id: "qb", label: "qb-inventory" },
3204
+ { id: "esx", label: "ESX legacy SQL" }
3205
+ ];
3206
+ var useMissingItemsAudit = create((set, get) => ({
3207
+ data: null,
3208
+ loaded: false,
3209
+ inFlight: false,
3210
+ refresh: async () => {
3211
+ if (get().inFlight) return;
3212
+ set({ inFlight: true });
3213
+ try {
3214
+ const res = await fetchNui("GET_MISSING_ITEMS", void 0, {
3215
+ success: true,
3216
+ data: { missing: [], snippets: { ox: "", qb: "", esx: "" } }
3217
+ });
3218
+ if (res?.success && res.data) {
3219
+ set({ data: res.data, loaded: true });
3220
+ } else {
3221
+ set({ loaded: true });
3222
+ }
3223
+ } catch {
3224
+ set({ loaded: true });
3225
+ } finally {
3226
+ set({ inFlight: false });
3227
+ }
3228
+ }
3229
+ }));
3230
+ function MissingItemsBanner() {
3231
+ const theme = useMantineTheme();
3232
+ const audit = useMissingItemsAudit((s) => s.data);
3233
+ const loaded = useMissingItemsAudit((s) => s.loaded);
3234
+ const inFlight = useMissingItemsAudit((s) => s.inFlight);
3235
+ const refresh = useMissingItemsAudit((s) => s.refresh);
3236
+ const [expanded, setExpanded] = useState(false);
3237
+ const [activeTab, setActiveTab] = useState("ox");
3238
+ const [hoveredTab, setHoveredTab] = useState(null);
3239
+ const [copied, setCopied] = useState(null);
3240
+ useEffect(() => {
3241
+ if (!loaded) refresh();
3242
+ }, [loaded, refresh]);
3243
+ const handleCopy = useCallback((tab) => {
3244
+ if (!audit) return;
3245
+ const text = audit.snippets[tab] ?? "";
3246
+ navigator.clipboard.writeText(text).then(() => {
3247
+ setCopied(tab);
3248
+ setTimeout(() => setCopied((c) => c === tab ? null : c), 1500);
3249
+ }).catch(() => {
3250
+ });
3251
+ }, [audit]);
3252
+ const handleRefresh = useCallback((e) => {
3253
+ e.stopPropagation();
3254
+ refresh();
3255
+ }, [refresh]);
3256
+ if (!audit || audit.missing.length === 0) return null;
3257
+ const warnColor = "#f59e0b";
3258
+ const names = audit.missing.map((m) => m.name);
3259
+ return /* @__PURE__ */ jsxs(
3260
+ "div",
3261
+ {
3262
+ style: {
3263
+ background: alpha(warnColor, 0.06),
3264
+ border: `0.1vh solid ${alpha(warnColor, 0.35)}`,
3265
+ borderLeft: `0.3vh solid ${warnColor}`,
3266
+ borderRadius: theme.radius.xs,
3267
+ margin: "0.6vh 1vh",
3268
+ overflow: "hidden"
3269
+ },
3270
+ children: [
3271
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "0.8vh", p: "0.8vh 1vh", style: { cursor: "pointer" }, onClick: () => setExpanded((e) => !e), children: [
3272
+ /* @__PURE__ */ jsx(AlertTriangle, { size: "1.8vh", color: warnColor, strokeWidth: 2.5 }),
3273
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
3274
+ /* @__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` }),
3275
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.5)", lineClamp: 1, style: { fontFamily: "monospace" }, children: names.join(", ") })
3276
+ ] }),
3277
+ /* @__PURE__ */ jsx(
3278
+ "button",
3279
+ {
3280
+ onClick: handleRefresh,
3281
+ disabled: inFlight,
3282
+ style: {
3283
+ background: "transparent",
3284
+ border: "none",
3285
+ padding: "0.3vh",
3286
+ cursor: inFlight ? "wait" : "pointer",
3287
+ display: "flex",
3288
+ alignItems: "center",
3289
+ justifyContent: "center",
3290
+ opacity: inFlight ? 0.4 : 0.7
3291
+ },
3292
+ title: "Re-check",
3293
+ children: /* @__PURE__ */ jsx(
3294
+ motion.span,
3295
+ {
3296
+ animate: { rotate: inFlight ? 360 : 0 },
3297
+ transition: inFlight ? { duration: 1, repeat: Infinity, ease: "linear" } : { duration: 0 },
3298
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3299
+ children: /* @__PURE__ */ jsx(RefreshCw, { size: "1.5vh", color: alpha(warnColor, 0.7) })
3300
+ }
3301
+ )
3302
+ }
3303
+ ),
3304
+ /* @__PURE__ */ jsx(
3305
+ motion.div,
3306
+ {
3307
+ animate: { rotate: expanded ? 180 : 0 },
3308
+ transition: { duration: 0.18 },
3309
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
3310
+ children: /* @__PURE__ */ jsx(ChevronDown, { size: "1.8vh", color: alpha(warnColor, 0.7) })
3311
+ }
3312
+ )
3313
+ ] }),
3314
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: expanded && /* @__PURE__ */ jsxs(
3315
+ motion.div,
3316
+ {
3317
+ initial: { height: 0, opacity: 0 },
3318
+ animate: { height: "auto", opacity: 1 },
3319
+ exit: { height: 0, opacity: 0 },
3320
+ transition: { duration: 0.18, ease: "easeOut" },
3321
+ style: { overflow: "hidden", borderTop: `0.1vh solid ${alpha(warnColor, 0.18)}` },
3322
+ children: [
3323
+ /* @__PURE__ */ jsx(Flex, { gap: "0", style: { borderBottom: `0.1vh solid ${alpha(warnColor, 0.18)}` }, children: TABS.map((tab) => {
3324
+ const active = tab.id === activeTab;
3325
+ const hovered = hoveredTab === tab.id;
3326
+ let bg = "transparent";
3327
+ if (active) bg = alpha(warnColor, 0.12);
3328
+ else if (hovered) bg = alpha(warnColor, 0.08);
3329
+ return /* @__PURE__ */ jsx(
3330
+ "button",
3331
+ {
3332
+ onClick: (e) => {
3333
+ e.stopPropagation();
3334
+ setActiveTab(tab.id);
3335
+ },
3336
+ onMouseEnter: () => setHoveredTab(tab.id),
3337
+ onMouseLeave: () => setHoveredTab((h) => h === tab.id ? null : h),
3338
+ style: {
3339
+ flex: 1,
3340
+ background: bg,
3341
+ border: "none",
3342
+ borderBottom: active ? `0.2vh solid ${warnColor}` : "0.2vh solid transparent",
3343
+ padding: "0.3vh 1vh",
3344
+ cursor: active ? "default" : "pointer",
3345
+ color: active ? warnColor : "rgba(255,255,255,0.5)",
3346
+ fontFamily: "Akrobat Bold",
3347
+ fontSize: "var(--mantine-font-size-xxs)",
3348
+ letterSpacing: "0.07em",
3349
+ textTransform: "uppercase",
3350
+ transition: "background 0.12s"
3351
+ },
3352
+ children: tab.label
3353
+ },
3354
+ tab.id
3355
+ );
3356
+ }) }),
3357
+ /* @__PURE__ */ jsx(
3358
+ CodeView,
3359
+ {
3360
+ code: audit.snippets[activeTab] ?? "",
3361
+ copied: copied === activeTab,
3362
+ onCopy: (e) => {
3363
+ e.stopPropagation();
3364
+ handleCopy(activeTab);
3365
+ },
3366
+ warnColor
3367
+ }
3368
+ )
3369
+ ]
3370
+ },
3371
+ "expanded"
3372
+ ) })
3373
+ ]
3374
+ }
3375
+ );
3376
+ }
3377
+ function CodeView({
3378
+ code,
3379
+ copied,
3380
+ onCopy,
3381
+ warnColor
3382
+ }) {
3383
+ const theme = useMantineTheme();
3384
+ const [hovered, setHovered] = useState(false);
3385
+ const lines = code === "" ? [""] : code.split("\n");
3386
+ const lineNumWidth = String(lines.length).length;
3387
+ const copyBg = copied ? alpha("#22c55e", 0.15) : hovered ? alpha(warnColor, 0.18) : alpha(warnColor, 0.1);
3388
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
3389
+ /* @__PURE__ */ jsxs(
3390
+ "button",
3391
+ {
3392
+ onClick: onCopy,
3393
+ onMouseEnter: () => setHovered(true),
3394
+ onMouseLeave: () => setHovered(false),
3395
+ style: {
3396
+ position: "absolute",
3397
+ top: "0.6vh",
3398
+ right: "0.8vh",
3399
+ zIndex: 2,
3400
+ background: copyBg,
3401
+ border: `0.1vh solid ${alpha(copied ? "#22c55e" : warnColor, 0.35)}`,
3402
+ borderRadius: theme.radius.xs,
3403
+ padding: "0.4vh 0.7vh",
3404
+ cursor: "pointer",
3405
+ display: "flex",
3406
+ alignItems: "center",
3407
+ gap: "0.4vh",
3408
+ transition: "background 0.12s"
3409
+ },
3410
+ children: [
3411
+ copied ? /* @__PURE__ */ jsx(Check, { size: "1.4vh", color: "#22c55e" }) : /* @__PURE__ */ jsx(Copy, { size: "1.4vh", color: warnColor }),
3412
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: copied ? "#22c55e" : warnColor, children: copied ? "Copied" : "Copy" })
3413
+ ]
3414
+ }
3415
+ ),
3416
+ /* @__PURE__ */ jsx("div", { style: {
3417
+ background: alpha(theme.colors.dark[9], 0.6),
3418
+ maxHeight: "40vh",
3419
+ overflowY: "auto",
3420
+ overflowX: "auto",
3421
+ padding: "0.6vh 0"
3422
+ }, 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: [
3423
+ /* @__PURE__ */ jsx("td", { style: {
3424
+ width: `${lineNumWidth + 2}ch`,
3425
+ textAlign: "right",
3426
+ padding: "0 0.8vh 0 1vh",
3427
+ color: "rgba(255,255,255,0.25)",
3428
+ userSelect: "none",
3429
+ whiteSpace: "nowrap",
3430
+ verticalAlign: "top"
3431
+ }, children: i + 1 }),
3432
+ /* @__PURE__ */ jsx("td", { style: {
3433
+ padding: "0 1vh",
3434
+ color: "rgba(255,255,255,0.85)",
3435
+ whiteSpace: "pre",
3436
+ verticalAlign: "top"
3437
+ }, children: line || "\u200B" })
3438
+ ] }, i)) }) }) })
3439
+ ] });
3440
+ }
3200
3441
  function getNested(obj, path) {
3201
3442
  return path.split(".").reduce((acc, key) => acc ? acc[key] : void 0, obj);
3202
3443
  }
@@ -3799,7 +4040,8 @@ function ConfigPanelInner({
3799
4040
  resetConfirmText,
3800
4041
  defaultConfig,
3801
4042
  width,
3802
- height
4043
+ height,
4044
+ suppressMissingItemsBanner
3803
4045
  }) {
3804
4046
  const { updateConfig, resetConfig, getHistory } = getScriptConfigInstance();
3805
4047
  const form = useForm();
@@ -4022,18 +4264,21 @@ function ConfigPanelInner({
4022
4264
  ] })
4023
4265
  ] })
4024
4266
  ] }),
4025
- /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
4026
- motion.div,
4027
- {
4028
- initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4029
- animate: { opacity: 1, y: 0 },
4030
- exit: { opacity: 0, y: -4 },
4031
- transition: { duration: 0.15 },
4032
- style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
4033
- children: children(activeTab)
4034
- },
4035
- activeTab
4036
- ) })
4267
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: [
4268
+ !suppressMissingItemsBanner && /* @__PURE__ */ jsx(MissingItemsBanner, {}),
4269
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
4270
+ motion.div,
4271
+ {
4272
+ initial: firstMountRef.current ? (firstMountRef.current = false, false) : { opacity: 0, y: 4 },
4273
+ animate: { opacity: 1, y: 0 },
4274
+ exit: { opacity: 0, y: -4 },
4275
+ transition: { duration: 0.15 },
4276
+ style: { flex: 1, display: "flex", flexDirection: "column", overflowY: "auto" },
4277
+ children: children(activeTab)
4278
+ },
4279
+ activeTab
4280
+ ) })
4281
+ ] })
4037
4282
  ]
4038
4283
  }
4039
4284
  )
@@ -4075,12 +4320,26 @@ function ConfigPanel(props) {
4075
4320
  if (result?.success) {
4076
4321
  form.reinitialize(cloneConfig(form.values));
4077
4322
  configPanelQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
4323
+ useMissingItemsAudit.getState().refresh();
4324
+ notifications.show({
4325
+ color: "green",
4326
+ title: locale("ConfigSaveSuccessTitle"),
4327
+ message: locale("ConfigSaveSuccessBody"),
4328
+ autoClose: 3e3
4329
+ });
4078
4330
  return;
4079
4331
  }
4080
4332
  form.reinitialize(cloneConfig(store.getState()));
4081
- if (result?._error) {
4082
- console.warn(`[ConfigPanel] config save failed: ${result._error}`);
4083
- }
4333
+ const err = result?._error || "Unknown";
4334
+ console.warn(`[ConfigPanel] config save failed: ${err}`);
4335
+ const titleKey = err === "NoPermission" ? "ConfigSaveNoPermissionTitle" : err === "VersionConflict" ? "ConfigSaveVersionConflictTitle" : err === "NotReady" ? "ConfigSaveNotReadyTitle" : "ConfigSaveFailedTitle";
4336
+ const bodyKey = err === "NoPermission" ? "ConfigSaveNoPermissionBody" : err === "VersionConflict" ? "ConfigSaveVersionConflictBody" : err === "NotReady" ? "ConfigSaveNotReadyBody" : "ConfigSaveFailedBody";
4337
+ notifications.show({
4338
+ color: "red",
4339
+ title: locale(titleKey),
4340
+ message: locale(bodyKey, err),
4341
+ autoClose: 6e3
4342
+ });
4084
4343
  } finally {
4085
4344
  setIsSaving(false);
4086
4345
  }
@@ -4099,6 +4358,7 @@ function ConfigPanel(props) {
4099
4358
  }
4100
4359
  ) });
4101
4360
  }
4361
+ var MISSING_COLOR = "#f59e0b";
4102
4362
  function LazyImage({ src, style }) {
4103
4363
  const [visible, setVisible] = useState(false);
4104
4364
  const ref = useRef(null);
@@ -4116,14 +4376,19 @@ function LazyImage({ src, style }) {
4116
4376
  }
4117
4377
  function SelectItem(props) {
4118
4378
  const invItems = useItems();
4379
+ const isMissing = !!props.value && !invItems[props.value];
4119
4380
  const formattedItems = useMemo(() => {
4120
4381
  const seen = /* @__PURE__ */ new Set();
4121
- return useItemsList(props.excludeItemNames ?? []).filter((item) => {
4382
+ const list = useItemsList(props.excludeItemNames ?? []).filter((item) => {
4122
4383
  if (seen.has(item.name)) return false;
4123
4384
  seen.add(item.name);
4124
4385
  return true;
4125
4386
  }).map((item) => ({ value: item.name, label: item.label, image: item.image }));
4126
- }, [invItems, props.excludeItemNames]);
4387
+ if (isMissing) {
4388
+ list.unshift({ value: props.value, label: props.value, image: "" });
4389
+ }
4390
+ return list;
4391
+ }, [invItems, props.excludeItemNames, props.value, isMissing]);
4127
4392
  return /* @__PURE__ */ jsx(
4128
4393
  Select,
4129
4394
  {
@@ -4138,10 +4403,11 @@ function SelectItem(props) {
4138
4403
  data: formattedItems,
4139
4404
  allowDeselect: false,
4140
4405
  searchable: true,
4406
+ styles: isMissing ? { input: { color: MISSING_COLOR } } : void 0,
4141
4407
  comboboxProps: { withinPortal: true, zIndex: 2e3 },
4142
4408
  leftSectionWidth: "4vh",
4143
4409
  leftSectionPointerEvents: "none",
4144
- leftSection: props.value ? /* @__PURE__ */ jsx(
4410
+ 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(
4145
4411
  Image,
4146
4412
  {
4147
4413
  fallbackSrc: "/placeholder.png",
@@ -4152,19 +4418,37 @@ function SelectItem(props) {
4152
4418
  }
4153
4419
  ) : null,
4154
4420
  nothingFoundMessage: locale("NoItemsFound"),
4155
- renderOption: (item) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", w: "100%", children: [
4156
- /* @__PURE__ */ jsx(
4157
- LazyImage,
4158
- {
4159
- src: invItems[item.option.value]?.image || "",
4160
- style: { aspectRatio: "1 / 1" }
4161
- }
4162
- ),
4163
- /* @__PURE__ */ jsxs(Flex, { direction: "column", children: [
4164
- /* @__PURE__ */ jsx(Text, { size: "sm", children: item.option.label }),
4165
- /* @__PURE__ */ jsx(Text, { size: "xxs", c: "dimmed", children: item.option.value })
4166
- ] })
4167
- ] })
4421
+ renderOption: (item) => {
4422
+ const optionMissing = !invItems[item.option.value] && item.option.value === props.value;
4423
+ if (optionMissing) {
4424
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", w: "100%", children: [
4425
+ /* @__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 }) }),
4426
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { flex: 1, minWidth: 0 }, children: [
4427
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: MISSING_COLOR, style: { fontFamily: "monospace" }, children: item.option.value }),
4428
+ /* @__PURE__ */ jsx(Text, { size: "xxs", c: "dimmed", children: locale("ItemNotInInventory") })
4429
+ ] }),
4430
+ /* @__PURE__ */ jsx("div", { style: {
4431
+ background: "rgba(245,158,11,0.12)",
4432
+ border: `0.1vh solid ${MISSING_COLOR}59`,
4433
+ borderRadius: "0.3vh",
4434
+ padding: "0.1vh 0.6vh"
4435
+ }, children: /* @__PURE__ */ jsx(Text, { size: "xxs", c: MISSING_COLOR, ff: "Akrobat Bold", tt: "uppercase", lts: "0.06em", children: locale("Missing") }) })
4436
+ ] });
4437
+ }
4438
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", w: "100%", children: [
4439
+ /* @__PURE__ */ jsx(
4440
+ LazyImage,
4441
+ {
4442
+ src: invItems[item.option.value]?.image || "",
4443
+ style: { aspectRatio: "1 / 1" }
4444
+ }
4445
+ ),
4446
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", children: [
4447
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: item.option.label }),
4448
+ /* @__PURE__ */ jsx(Text, { size: "xxs", c: "dimmed", children: item.option.value })
4449
+ ] })
4450
+ ] });
4451
+ }
4168
4452
  }
4169
4453
  );
4170
4454
  }
@@ -4381,6 +4665,148 @@ function PickerButton({
4381
4665
  }
4382
4666
  ) });
4383
4667
  }
4668
+ function fmtV4(n) {
4669
+ return Number.isFinite(n) ? n.toFixed(2) : "0.00";
4670
+ }
4671
+ function Vector4Display({ value }) {
4672
+ const theme = useMantineTheme();
4673
+ return /* @__PURE__ */ jsx(
4674
+ Flex,
4675
+ {
4676
+ align: "center",
4677
+ gap: "xs",
4678
+ p: "xs",
4679
+ style: {
4680
+ background: alpha(theme.colors.dark[5], 0.35),
4681
+ border: "0.1vh solid rgba(255,255,255,0.05)",
4682
+ borderRadius: theme.radius.xs
4683
+ },
4684
+ children: /* @__PURE__ */ jsxs(
4685
+ Text,
4686
+ {
4687
+ ff: "monospace",
4688
+ size: "xxs",
4689
+ c: "rgba(255,255,255,0.85)",
4690
+ style: { flex: 1, letterSpacing: "0.02em", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" },
4691
+ children: [
4692
+ "vector4(",
4693
+ fmtV4(value.x),
4694
+ ", ",
4695
+ fmtV4(value.y),
4696
+ ", ",
4697
+ fmtV4(value.z),
4698
+ ", ",
4699
+ fmtV4(value.w),
4700
+ ")"
4701
+ ]
4702
+ }
4703
+ )
4704
+ }
4705
+ );
4706
+ }
4707
+ function Vector4ActionButton({
4708
+ icon,
4709
+ label,
4710
+ tooltip,
4711
+ onClick,
4712
+ color,
4713
+ compact
4714
+ }) {
4715
+ const theme = useMantineTheme();
4716
+ return /* @__PURE__ */ jsx(Tooltip, { label: tooltip, position: "top", withArrow: true, withinPortal: true, zIndex: 2e3, children: /* @__PURE__ */ jsxs(
4717
+ motion.button,
4718
+ {
4719
+ onClick,
4720
+ whileHover: { background: alpha(color, 0.18) },
4721
+ whileTap: { scale: 0.95 },
4722
+ style: {
4723
+ background: alpha(color, 0.1),
4724
+ border: `0.1vh solid ${alpha(color, 0.35)}`,
4725
+ borderRadius: theme.radius.xs,
4726
+ padding: compact ? "0.25vh 0.6vh" : "0.5vh 0.8vh",
4727
+ cursor: "pointer",
4728
+ display: "flex",
4729
+ alignItems: "center",
4730
+ gap: compact ? "0.3vh" : "0.4vh"
4731
+ },
4732
+ children: [
4733
+ icon,
4734
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.06em", c: color, children: label })
4735
+ ]
4736
+ }
4737
+ ) });
4738
+ }
4739
+ function WorldPositionSetButton({
4740
+ value,
4741
+ onChange,
4742
+ compact
4743
+ }) {
4744
+ const theme = useMantineTheme();
4745
+ const color = theme.colors[theme.primaryColor][5];
4746
+ const grab = async () => {
4747
+ try {
4748
+ const resp = await fetchNui("GET_POSITION", {}, value);
4749
+ if (!resp || typeof resp !== "object") return;
4750
+ onChange({
4751
+ x: Number(resp.x ?? 0),
4752
+ y: Number(resp.y ?? 0),
4753
+ z: Number(resp.z ?? 0),
4754
+ w: Number(resp.w ?? 0)
4755
+ });
4756
+ } catch {
4757
+ }
4758
+ };
4759
+ return /* @__PURE__ */ jsx(
4760
+ Vector4ActionButton,
4761
+ {
4762
+ icon: /* @__PURE__ */ jsx(Crosshair, { size: compact ? "1.1vh" : "1.3vh", color }),
4763
+ label: locale("Set"),
4764
+ tooltip: locale("SetWorldTooltip"),
4765
+ onClick: grab,
4766
+ color,
4767
+ compact
4768
+ }
4769
+ );
4770
+ }
4771
+ function WorldPositionGotoButton({
4772
+ value,
4773
+ compact
4774
+ }) {
4775
+ const theme = useMantineTheme();
4776
+ const color = theme.colors[theme.primaryColor][5];
4777
+ const goto = () => {
4778
+ fetchNui("GOTO_POSITION", value).catch(() => {
4779
+ });
4780
+ };
4781
+ return /* @__PURE__ */ jsx(
4782
+ Vector4ActionButton,
4783
+ {
4784
+ icon: /* @__PURE__ */ jsx(MapPin, { size: compact ? "1.1vh" : "1.3vh", color }),
4785
+ label: locale("Goto"),
4786
+ tooltip: locale("GotoWorldTooltip"),
4787
+ onClick: goto,
4788
+ color,
4789
+ compact
4790
+ }
4791
+ );
4792
+ }
4793
+ function Vector4DeleteButton({
4794
+ onClick,
4795
+ compact
4796
+ }) {
4797
+ const color = "#ef4444";
4798
+ return /* @__PURE__ */ jsx(
4799
+ Vector4ActionButton,
4800
+ {
4801
+ icon: /* @__PURE__ */ jsx(Trash2, { size: compact ? "1.1vh" : "1.3vh", color }),
4802
+ label: locale("Delete"),
4803
+ tooltip: locale("Delete"),
4804
+ onClick,
4805
+ color,
4806
+ compact
4807
+ }
4808
+ );
4809
+ }
4384
4810
  var GroupSelectContext = createContext(null);
4385
4811
  function GroupSelect({
4386
4812
  value,
@@ -4832,6 +5258,6 @@ function TestBed({
4832
5258
  );
4833
5259
  }
4834
5260
 
4835
- 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, useModal, useModalActions, useNavigation, useNavigationStore };
5261
+ 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 };
4836
5262
  //# sourceMappingURL=index.js.map
4837
5263
  //# sourceMappingURL=index.js.map