dirk-cfx-react 1.1.91 → 1.1.93

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.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { AccessOverrideSection, AccessOverrideSectionProps, AccessOverrideValue, AccountSelect, AccountSelectProps, ActiveTool, AdminPageTitle, AdminPageTitleProps, AnimPostFxSelect, AnimPostFxSelectProps, AsyncSaveButton, BlipColorSelect, BlipColorSelectProps, BlipDisplaySelect, BlipDisplaySelectProps, BlipIconSelect, BlipIconSelectProps, BlipMarker, BlipMarkerProps, BorderedIcon, BorderedIconProps, ButtonProps, ConfigPanel, ConfigPanelProps, ConfirmModal, ConfirmModalProps, ControlMultiSelect, ControlMultiSelectProps, ControlSelect, ControlSelectProps, Counter, DiscordRole, DiscordRoleSelect, DiscordRoleSelectProps, DoorPickerButton, FiveMControls, FiveMKeyBindInput, FloatingParticles, FloatingParticlesProps, FrameworkAccount, GroupName, GroupNameProps, GroupRank, GroupRankProps, GroupSelect, GroupSelectProps, GroupType, GroupValue, InfoBox, InfoBoxProps, InputContainer, InputContainerProps, InstructionKey, InstructionPanel, InstructionPanelProps, LevelBanner, LevelPanel, Map, MapLayer, MapProps, MapStyle, MissingItemsBanner, Modal, ModalContext, ModalProps, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavItem, NavigationContext, NavigationProvider, NavigationStore, ParticleState, PickedDoor, PickedDoorGroup, PlayerSelect, PlayerSelectProps, PositionPicker, PositionPickerProps, ProgressProps, Prompt, PromptButton, PromptModal, ScenarioSelect, ScenarioSelectProps, SegmentProps, SegmentedControl, SegmentedControlProps, SegmentedProgress, SelectItem, SelectItemProps, StoreModalProps, SwitchPanel, SwitchPanelProps, TestBed, TestBedItem, TestBedPlacement, TestBedProps, ThemeOverrideSection, ThemeOverrideSectionProps, Title, TitleProps, TitleSize, Vector4DeleteButton, Vector4Display, Vector4Value, WorldPositionGotoButton, WorldPositionPicker, WorldPositionPickerSlot, WorldPositionSetButton, ZoomControls, blipUrl, blipUrlForSprite, getBlipColor, getBlipEntry, useAdminToolStore, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore, usePickDoor } from './components/index.cjs';
2
2
  export { FrameworkGrade, FrameworkGroup, GTA_CONTROLS, GTA_CONTROL_GROUP_ORDER, GtaControl, GtaControlGroup, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, InitialFetch, InternalEvent, InventoryItem, InventoryItems, SettingsState, SkillSettings, UploadImageProps, colorWithAlpha, copyToClipboard, createSkill, ensureFrameworkGroups, extractDefaults, fetchLuaTable, fetchNui, formatGtaControl, gameToMap, getGtaControl, getImageShape, getItemImageUrl, initialFetches, internalEvent, isEnvBrowser, isProfanity, latPr100, locale, localeStore, mapCenter, mapToGame, noop, numberToRoman, openLink, registerInitialFetch, registerInitialLuaTableFetch, runFetches, selectAllGroups, splitFAString, updatePresignedURL, uploadImage, useAutoFetcher, useFrameworkGroups, useItems, useItemsList, useProfanityStore, useSettings } from './utils/index.cjs';
3
- export { FormProvider, FormState, NuiHandlerSignature, NuiMessageData, ScriptConfigHistoryChange, ScriptConfigHistoryEntry, ScriptConfigHistoryRequest, ScriptConfigHistoryResponse, ScriptConfigInstance, TornEdgeSVGFilter, ValidationRules, ValidatorFn, clearAdminState, createFormStore, createScriptConfig, getScriptConfigInstance, useAdminState, useAudio, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useNuiEvent, useTornEdges, useValidModels } from './hooks/index.cjs';
3
+ export { FormProvider, FormState, NuiHandlerSignature, NuiMessageData, ScriptConfigHistoryChange, ScriptConfigHistoryEntry, ScriptConfigHistoryRequest, ScriptConfigHistoryResponse, ScriptConfigInstance, ServerOnlyScriptConfigResponse, TornEdgeSVGFilter, ValidationRules, ValidatorFn, clearAdminState, createFormStore, createScriptConfig, getScriptConfigInstance, useAdminState, useAudio, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useNuiEvent, useTornEdges, useValidModels } from './hooks/index.cjs';
4
4
  export { P as Player, U as UsePlayersOptions, a as UsePlayersResult, u as usePlayers } from './usePlayers-BlGBBevs.cjs';
5
5
  export { DirkProvider, DirkProviderProps, dirkQueryClient } from './providers/index.cjs';
6
6
  import { z } from 'zod';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { AccessOverrideSection, AccessOverrideSectionProps, AccessOverrideValue, AccountSelect, AccountSelectProps, ActiveTool, AdminPageTitle, AdminPageTitleProps, AnimPostFxSelect, AnimPostFxSelectProps, AsyncSaveButton, BlipColorSelect, BlipColorSelectProps, BlipDisplaySelect, BlipDisplaySelectProps, BlipIconSelect, BlipIconSelectProps, BlipMarker, BlipMarkerProps, BorderedIcon, BorderedIconProps, ButtonProps, ConfigPanel, ConfigPanelProps, ConfirmModal, ConfirmModalProps, ControlMultiSelect, ControlMultiSelectProps, ControlSelect, ControlSelectProps, Counter, DiscordRole, DiscordRoleSelect, DiscordRoleSelectProps, DoorPickerButton, FiveMControls, FiveMKeyBindInput, FloatingParticles, FloatingParticlesProps, FrameworkAccount, GroupName, GroupNameProps, GroupRank, GroupRankProps, GroupSelect, GroupSelectProps, GroupType, GroupValue, InfoBox, InfoBoxProps, InputContainer, InputContainerProps, InstructionKey, InstructionPanel, InstructionPanelProps, LevelBanner, LevelPanel, Map, MapLayer, MapProps, MapStyle, MissingItemsBanner, Modal, ModalContext, ModalProps, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavItem, NavigationContext, NavigationProvider, NavigationStore, ParticleState, PickedDoor, PickedDoorGroup, PlayerSelect, PlayerSelectProps, PositionPicker, PositionPickerProps, ProgressProps, Prompt, PromptButton, PromptModal, ScenarioSelect, ScenarioSelectProps, SegmentProps, SegmentedControl, SegmentedControlProps, SegmentedProgress, SelectItem, SelectItemProps, StoreModalProps, SwitchPanel, SwitchPanelProps, TestBed, TestBedItem, TestBedPlacement, TestBedProps, ThemeOverrideSection, ThemeOverrideSectionProps, Title, TitleProps, TitleSize, Vector4DeleteButton, Vector4Display, Vector4Value, WorldPositionGotoButton, WorldPositionPicker, WorldPositionPickerSlot, WorldPositionSetButton, ZoomControls, blipUrl, blipUrlForSprite, getBlipColor, getBlipEntry, useAdminToolStore, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore, usePickDoor } from './components/index.js';
2
2
  export { FrameworkGrade, FrameworkGroup, GTA_CONTROLS, GTA_CONTROL_GROUP_ORDER, GtaControl, GtaControlGroup, INPUT_MAPPER_KEYS_BY_PRIMARY, INPUT_MAPPER_PRIMARY_OPTIONS, InitialFetch, InternalEvent, InventoryItem, InventoryItems, SettingsState, SkillSettings, UploadImageProps, colorWithAlpha, copyToClipboard, createSkill, ensureFrameworkGroups, extractDefaults, fetchLuaTable, fetchNui, formatGtaControl, gameToMap, getGtaControl, getImageShape, getItemImageUrl, initialFetches, internalEvent, isEnvBrowser, isProfanity, latPr100, locale, localeStore, mapCenter, mapToGame, noop, numberToRoman, openLink, registerInitialFetch, registerInitialLuaTableFetch, runFetches, selectAllGroups, splitFAString, updatePresignedURL, uploadImage, useAutoFetcher, useFrameworkGroups, useItems, useItemsList, useProfanityStore, useSettings } from './utils/index.js';
3
- export { FormProvider, FormState, NuiHandlerSignature, NuiMessageData, ScriptConfigHistoryChange, ScriptConfigHistoryEntry, ScriptConfigHistoryRequest, ScriptConfigHistoryResponse, ScriptConfigInstance, TornEdgeSVGFilter, ValidationRules, ValidatorFn, clearAdminState, createFormStore, createScriptConfig, getScriptConfigInstance, useAdminState, useAudio, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useNuiEvent, useTornEdges, useValidModels } from './hooks/index.js';
3
+ export { FormProvider, FormState, NuiHandlerSignature, NuiMessageData, ScriptConfigHistoryChange, ScriptConfigHistoryEntry, ScriptConfigHistoryRequest, ScriptConfigHistoryResponse, ScriptConfigInstance, ServerOnlyScriptConfigResponse, TornEdgeSVGFilter, ValidationRules, ValidatorFn, clearAdminState, createFormStore, createScriptConfig, getScriptConfigInstance, useAdminState, useAudio, useForm, useFormActions, useFormError, useFormErrors, useFormField, useFormFields, useNuiEvent, useTornEdges, useValidModels } from './hooks/index.js';
4
4
  export { P as Player, U as UsePlayersOptions, a as UsePlayersResult, u as usePlayers } from './usePlayers-BlGBBevs.js';
5
5
  export { DirkProvider, DirkProviderProps, dirkQueryClient } from './providers/index.js';
6
6
  import { z } from 'zod';
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { z } from 'zod';
6
6
  import axios from 'axios';
7
7
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8
8
  import { motion, AnimatePresence, useMotionValue } from 'framer-motion';
9
- import { Info, X, AlertTriangle, Trash2, RefreshCw, ChevronDown, Check, Copy, MapPin, Crosshair, EyeOff, Eye, RotateCcw, FlaskConical, ChevronUp, Palette, ShieldCheck, Users, UserRound, Plus, DoorOpen, Minus, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
9
+ import { Info, X, AlertTriangle, Trash2, RefreshCw, ChevronDown, Check, Copy, MapPin, Crosshair, EyeOff, Eye, RotateCcw, FlaskConical, ChevronUp, Palette, ShieldCheck, Users, Plus, UserRound, DoorOpen, Minus, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
10
10
  import clickSoundUrl from './click_sound-PNCRRTM4.mp3';
11
11
  import hoverSoundUrl from './hover_sound-NBUA222C.mp3';
12
12
  import { notifications } from '@mantine/notifications';
@@ -1261,11 +1261,11 @@ var colorNames = {
1261
1261
  Yellow: { r: 255, g: 255, b: 0 },
1262
1262
  YellowGreen: { r: 154, g: 205, b: 50 }
1263
1263
  };
1264
- function colorWithAlpha(color, alpha18) {
1264
+ function colorWithAlpha(color, alpha19) {
1265
1265
  const lowerCasedColor = color.toLowerCase();
1266
1266
  if (colorNames[lowerCasedColor]) {
1267
1267
  const rgb = colorNames[lowerCasedColor];
1268
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha18})`;
1268
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha19})`;
1269
1269
  }
1270
1270
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1271
1271
  const hex = color.slice(1);
@@ -1273,12 +1273,12 @@ function colorWithAlpha(color, alpha18) {
1273
1273
  const r = bigint >> 16 & 255;
1274
1274
  const g = bigint >> 8 & 255;
1275
1275
  const b = bigint & 255;
1276
- return `rgba(${r}, ${g}, ${b}, ${alpha18})`;
1276
+ return `rgba(${r}, ${g}, ${b}, ${alpha19})`;
1277
1277
  }
1278
1278
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1279
1279
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1280
1280
  if (result) {
1281
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha18})`;
1281
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha19})`;
1282
1282
  }
1283
1283
  }
1284
1284
  return color;
@@ -4678,6 +4678,31 @@ function mergeMantineThemeSafe(base, custom, override) {
4678
4678
  }
4679
4679
  };
4680
4680
  }
4681
+ function isPlainObject2(v) {
4682
+ return typeof v === "object" && v !== null && !Array.isArray(v);
4683
+ }
4684
+ function deepMerge(base, patch) {
4685
+ if (!isPlainObject2(base) || !isPlainObject2(patch)) {
4686
+ return patch ?? base;
4687
+ }
4688
+ const out = { ...base };
4689
+ for (const key of Object.keys(patch)) {
4690
+ const patchVal = patch[key];
4691
+ const baseVal = out[key];
4692
+ out[key] = isPlainObject2(baseVal) && isPlainObject2(patchVal) ? deepMerge(baseVal, patchVal) : patchVal;
4693
+ }
4694
+ return out;
4695
+ }
4696
+ function changedTopLevelSections(changedFields) {
4697
+ if (!changedFields || changedFields.length === 0) return [];
4698
+ const sections = /* @__PURE__ */ new Set();
4699
+ for (const path of changedFields) {
4700
+ if (typeof path !== "string" || path.length === 0) continue;
4701
+ const dot = path.indexOf(".");
4702
+ sections.add(dot === -1 ? path : path.slice(0, dot));
4703
+ }
4704
+ return Array.from(sections);
4705
+ }
4681
4706
  var _instance = null;
4682
4707
  function getScriptConfigInstance() {
4683
4708
  if (!_instance) throw new Error("[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel");
@@ -4687,15 +4712,18 @@ function createScriptConfig(defaultValue) {
4687
4712
  const store2 = create(() => defaultValue);
4688
4713
  let clientVersion = 0;
4689
4714
  const useScriptConfigHooks = () => {
4690
- useNuiEvent("UPDATE_SCRIPT_CONFIG", (data) => {
4691
- if (!data) return;
4692
- if (typeof data.clientVersion === "number") {
4693
- clientVersion = data.clientVersion;
4694
- }
4695
- if (data.config && typeof data.config === "object") {
4696
- store2.setState((prev) => ({ ...prev, ...data.config }));
4715
+ useNuiEvent(
4716
+ "UPDATE_SCRIPT_CONFIG",
4717
+ (data) => {
4718
+ if (!data) return;
4719
+ if (typeof data.clientVersion === "number") {
4720
+ clientVersion = data.clientVersion;
4721
+ }
4722
+ if (data.config && typeof data.config === "object") {
4723
+ store2.setState((prev) => ({ ...prev, ...data.config }));
4724
+ }
4697
4725
  }
4698
- });
4726
+ );
4699
4727
  };
4700
4728
  const fetchScriptConfig = async () => {
4701
4729
  try {
@@ -4711,12 +4739,37 @@ function createScriptConfig(defaultValue) {
4711
4739
  }
4712
4740
  return null;
4713
4741
  };
4714
- const updateScriptConfig = async (newConfig) => {
4742
+ const fetchServerOnlyScriptConfig = async () => {
4743
+ try {
4744
+ const response = await fetchNui(
4745
+ "GET_SERVER_ONLY_SCRIPT_CONFIG"
4746
+ );
4747
+ if (response?.success && response.data) {
4748
+ if (typeof response.data.clientVersion === "number") {
4749
+ clientVersion = response.data.clientVersion;
4750
+ }
4751
+ const sliver = response.data.serverOnly;
4752
+ const merged = deepMerge(store2.getState(), sliver ?? {});
4753
+ store2.setState(() => merged);
4754
+ return merged;
4755
+ }
4756
+ } catch {
4757
+ }
4758
+ return null;
4759
+ };
4760
+ const updateScriptConfig = async (newConfig, changedFields) => {
4715
4761
  store2.setState((prev) => ({ ...prev, ...newConfig }));
4716
- const response = await fetchNui("UPDATE_SCRIPT_CONFIG", {
4717
- data: newConfig,
4718
- expectedVersion: clientVersion
4719
- });
4762
+ const sections = changedTopLevelSections(changedFields);
4763
+ let payload;
4764
+ if (sections.length > 0) {
4765
+ const current = store2.getState();
4766
+ const delta = {};
4767
+ for (const key of sections) delta[key] = current[key];
4768
+ payload = { data: delta, expectedVersion: clientVersion, sectionReplace: true };
4769
+ } else {
4770
+ payload = { data: newConfig, expectedVersion: clientVersion };
4771
+ }
4772
+ const response = await fetchNui("UPDATE_SCRIPT_CONFIG", payload);
4720
4773
  if (response?.meta?.client_version != null) {
4721
4774
  clientVersion = response.meta.client_version;
4722
4775
  }
@@ -4731,10 +4784,7 @@ function createScriptConfig(defaultValue) {
4731
4784
  const resetConfig = async () => {
4732
4785
  const response = await fetchNui("RESET_SCRIPT_CONFIG");
4733
4786
  if (response?.success) {
4734
- const fresh = await fetchScriptConfig();
4735
- if (fresh) {
4736
- store2.setState(() => fresh);
4737
- }
4787
+ await fetchServerOnlyScriptConfig();
4738
4788
  }
4739
4789
  return response;
4740
4790
  };
@@ -4743,9 +4793,10 @@ function createScriptConfig(defaultValue) {
4743
4793
  updateConfig: updateScriptConfig,
4744
4794
  resetConfig,
4745
4795
  getHistory: getScriptConfigHistory,
4746
- fetchConfig: fetchScriptConfig
4796
+ fetchConfig: fetchScriptConfig,
4797
+ fetchServerOnly: fetchServerOnlyScriptConfig
4747
4798
  };
4748
- return { store: store2, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig };
4799
+ return { store: store2, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig, fetchServerOnlyScriptConfig };
4749
4800
  }
4750
4801
  var DirkErrorBoundary = class extends React4.Component {
4751
4802
  constructor() {
@@ -4932,12 +4983,6 @@ function DirkProvider({ children, overideResourceName, themeOverride }) {
4932
4983
  setScTheme(s?.theme ?? null);
4933
4984
  });
4934
4985
  }
4935
- inst.fetchConfig?.().then((full) => {
4936
- if (full && typeof full === "object") {
4937
- setScTheme(full.theme ?? null);
4938
- }
4939
- }).catch(() => {
4940
- });
4941
4986
  } catch {
4942
4987
  }
4943
4988
  return () => {
@@ -5557,11 +5602,11 @@ function cloneConfig(value) {
5557
5602
  return JSON.parse(JSON.stringify(value));
5558
5603
  }
5559
5604
  function ServerOnlyFetcher() {
5560
- const { fetchConfig } = getScriptConfigInstance();
5605
+ const { fetchServerOnly } = getScriptConfigInstance();
5561
5606
  const { reinitialize } = useFormActions();
5562
5607
  useEffect(() => {
5563
5608
  let cancelled = false;
5564
- fetchConfig().then((full) => {
5609
+ fetchServerOnly().then((full) => {
5565
5610
  if (!cancelled && full) reinitialize(full);
5566
5611
  }).catch(() => {
5567
5612
  });
@@ -5585,7 +5630,7 @@ function ConfigPanel(props) {
5585
5630
  if (isSaving) return;
5586
5631
  setIsSaving(true);
5587
5632
  try {
5588
- const result = await updateConfig(form.values);
5633
+ const result = await updateConfig(form.values, form.changedFields);
5589
5634
  if (result?.success) {
5590
5635
  form.reinitialize(cloneConfig(form.values));
5591
5636
  dirkQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
@@ -7012,6 +7057,7 @@ function formatLabel(p) {
7012
7057
  }
7013
7058
  return parts.join(" \xB7 ");
7014
7059
  }
7060
+ var COMMON_ACE_GROUPS = ["group.admin", "group.mod", "group.superadmin"];
7015
7061
  var t = (key, fallback) => {
7016
7062
  const v = locale(key);
7017
7063
  return v === key ? fallback : v;
@@ -7034,7 +7080,6 @@ function AccessOverrideSection({
7034
7080
  schemaKey = "access",
7035
7081
  title,
7036
7082
  description: description2,
7037
- groupType,
7038
7083
  includeOffline = true
7039
7084
  }) {
7040
7085
  const mantineTheme = useMantineTheme();
@@ -7049,14 +7094,19 @@ function AccessOverrideSection({
7049
7094
  const [addingGroup, setAddingGroup] = useState(false);
7050
7095
  const [addingPlayer, setAddingPlayer] = useState(false);
7051
7096
  const commitGroup = (name) => {
7052
- if (!name) return;
7053
- set("groups", [...value.groups, name]);
7097
+ const g = name.trim();
7098
+ if (!g) return;
7099
+ if (!value.groups.includes(g)) set("groups", [...value.groups, g]);
7054
7100
  setAddingGroup(false);
7055
7101
  };
7056
7102
  const setGroup = (index, name) => {
7057
7103
  const next = [...value.groups];
7058
7104
  next[index] = name;
7059
- set("groups", next.filter((g) => g !== ""));
7105
+ set("groups", next);
7106
+ };
7107
+ const quickAddGroup = (name) => {
7108
+ if (value.groups.includes(name)) return;
7109
+ set("groups", [...value.groups, name]);
7060
7110
  };
7061
7111
  const removeGroup = (index) => set("groups", value.groups.filter((_, i) => i !== index));
7062
7112
  const commitIdentifier = (id) => {
@@ -7088,19 +7138,68 @@ function AccessOverrideSection({
7088
7138
  ),
7089
7139
  /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.4)", children: description2 || t(
7090
7140
  "AccessOverrideDesc",
7091
- "Grant access to this resource's admin panel to specific groups and players, in addition to your global dirk_lib admins. Enforcement is server-side."
7141
+ "Grant access to this resource's admin panel to specific ACE groups and players, in addition to your global dirk_lib admins. Enforcement is server-side."
7092
7142
  ) }),
7093
- /* @__PURE__ */ jsx(SectionLabel, { icon: Users, label: t("AccessGroups", "Groups") }),
7143
+ /* @__PURE__ */ jsx(SectionLabel, { icon: Users, label: t("AccessGroups", "Admin groups (ACE)") }),
7144
+ /* @__PURE__ */ jsx(Flex, { gap: "xs", wrap: "wrap", children: COMMON_ACE_GROUPS.map((g) => {
7145
+ const added = value.groups.includes(g);
7146
+ return /* @__PURE__ */ jsxs(
7147
+ Flex,
7148
+ {
7149
+ align: "center",
7150
+ gap: "xxs",
7151
+ onClick: added ? void 0 : () => quickAddGroup(g),
7152
+ role: "button",
7153
+ tabIndex: added ? -1 : 0,
7154
+ "aria-disabled": added,
7155
+ onKeyDown: (e) => {
7156
+ if (added) return;
7157
+ if (e.key === "Enter" || e.key === " ") {
7158
+ e.preventDefault();
7159
+ quickAddGroup(g);
7160
+ }
7161
+ },
7162
+ px: "xs",
7163
+ py: "xxs",
7164
+ style: {
7165
+ cursor: added ? "default" : "pointer",
7166
+ opacity: added ? 0.4 : 1,
7167
+ background: alpha(color, 0.12),
7168
+ border: `0.1vh solid ${alpha(color, 0.4)}`,
7169
+ borderRadius: "0.4vh",
7170
+ transition: "background 0.15s, border-color 0.15s"
7171
+ },
7172
+ onMouseEnter: (e) => {
7173
+ if (added) return;
7174
+ e.currentTarget.style.background = alpha(color, 0.22);
7175
+ },
7176
+ onMouseLeave: (e) => {
7177
+ if (added) return;
7178
+ e.currentTarget.style.background = alpha(color, 0.12);
7179
+ },
7180
+ children: [
7181
+ !added && /* @__PURE__ */ jsx(Plus, { size: "1.2vh", color }),
7182
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", lts: "0.04em", c: color, children: g })
7183
+ ]
7184
+ },
7185
+ g
7186
+ );
7187
+ }) }),
7094
7188
  /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "xxs", children: [
7095
- value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsx(EmptyHint, { text: t("AccessNoGroups", "No groups added.") }),
7189
+ value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsx(
7190
+ EmptyHint,
7191
+ {
7192
+ text: t("AccessNoGroups", "No ACE groups added. e.g. group.admin")
7193
+ }
7194
+ ),
7096
7195
  value.groups.map((name, i) => /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7097
7196
  /* @__PURE__ */ jsx(
7098
- GroupName,
7197
+ TextInput,
7099
7198
  {
7100
7199
  value: name,
7101
- onChange: (v) => setGroup(i, v),
7102
- type: groupType,
7103
- placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7200
+ onChange: (e) => setGroup(i, e.currentTarget.value),
7201
+ placeholder: t("AccessGroupPlaceholder", "ACE group or permission, e.g. group.admin"),
7202
+ size: "xs",
7104
7203
  style: { flex: 1 }
7105
7204
  }
7106
7205
  ),
@@ -7117,30 +7216,15 @@ function AccessOverrideSection({
7117
7216
  }
7118
7217
  )
7119
7218
  ] }, i)),
7120
- addingGroup && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7121
- /* @__PURE__ */ jsx(
7122
- GroupName,
7123
- {
7124
- value: "",
7125
- onChange: (v) => commitGroup(v),
7126
- type: groupType,
7127
- placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7128
- style: { flex: 1 }
7129
- }
7130
- ),
7131
- /* @__PURE__ */ jsx(
7132
- ActionIcon,
7133
- {
7134
- variant: "subtle",
7135
- color: "red",
7136
- size: "md",
7137
- onClick: () => setAddingGroup(false),
7138
- title: t("Remove", "Remove"),
7139
- "aria-label": t("Remove", "Remove"),
7140
- children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7141
- }
7142
- )
7143
- ] })
7219
+ addingGroup && /* @__PURE__ */ jsx(
7220
+ AceGroupAddRow,
7221
+ {
7222
+ placeholder: t("AccessGroupPlaceholder", "ACE group or permission, e.g. group.admin"),
7223
+ onCommit: commitGroup,
7224
+ onCancel: () => setAddingGroup(false),
7225
+ removeLabel: t("Remove", "Remove")
7226
+ }
7227
+ )
7144
7228
  ] }),
7145
7229
  !addingGroup && /* @__PURE__ */ jsx(AddRowButton, { label: t("AccessAddGroup", "Add group"), onClick: () => setAddingGroup(true) }),
7146
7230
  /* @__PURE__ */ jsx(SectionLabel, { icon: UserRound, label: t("AccessPlayers", "Players") }),
@@ -7240,6 +7324,53 @@ function AddRowButton({ label: label2, onClick }) {
7240
7324
  }
7241
7325
  );
7242
7326
  }
7327
+ function AceGroupAddRow({
7328
+ placeholder,
7329
+ onCommit,
7330
+ onCancel,
7331
+ removeLabel
7332
+ }) {
7333
+ const [draft, setDraft] = useState("");
7334
+ const commit = () => {
7335
+ if (draft.trim()) onCommit(draft);
7336
+ else onCancel();
7337
+ };
7338
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", children: [
7339
+ /* @__PURE__ */ jsx(
7340
+ TextInput,
7341
+ {
7342
+ value: draft,
7343
+ onChange: (e) => setDraft(e.currentTarget.value),
7344
+ onKeyDown: (e) => {
7345
+ if (e.key === "Enter") {
7346
+ e.preventDefault();
7347
+ commit();
7348
+ } else if (e.key === "Escape") {
7349
+ e.preventDefault();
7350
+ onCancel();
7351
+ }
7352
+ },
7353
+ onBlur: commit,
7354
+ placeholder,
7355
+ size: "xs",
7356
+ autoFocus: true,
7357
+ style: { flex: 1 }
7358
+ }
7359
+ ),
7360
+ /* @__PURE__ */ jsx(
7361
+ ActionIcon,
7362
+ {
7363
+ variant: "subtle",
7364
+ color: "red",
7365
+ size: "md",
7366
+ onClick: onCancel,
7367
+ title: removeLabel,
7368
+ "aria-label": removeLabel,
7369
+ children: /* @__PURE__ */ jsx(Trash2, { size: "1.4vh" })
7370
+ }
7371
+ )
7372
+ ] });
7373
+ }
7243
7374
 
7244
7375
  // src/utils/gtaAnimPostFx.ts
7245
7376
  var GTA_ANIM_POST_FX_GROUP_ORDER = [