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.cjs CHANGED
@@ -1270,11 +1270,11 @@ var colorNames = {
1270
1270
  Yellow: { r: 255, g: 255, b: 0 },
1271
1271
  YellowGreen: { r: 154, g: 205, b: 50 }
1272
1272
  };
1273
- function colorWithAlpha(color, alpha18) {
1273
+ function colorWithAlpha(color, alpha19) {
1274
1274
  const lowerCasedColor = color.toLowerCase();
1275
1275
  if (colorNames[lowerCasedColor]) {
1276
1276
  const rgb = colorNames[lowerCasedColor];
1277
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha18})`;
1277
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha19})`;
1278
1278
  }
1279
1279
  if (/^#([A-Fa-f0-9]{6})$/.test(color)) {
1280
1280
  const hex = color.slice(1);
@@ -1282,12 +1282,12 @@ function colorWithAlpha(color, alpha18) {
1282
1282
  const r = bigint >> 16 & 255;
1283
1283
  const g = bigint >> 8 & 255;
1284
1284
  const b = bigint & 255;
1285
- return `rgba(${r}, ${g}, ${b}, ${alpha18})`;
1285
+ return `rgba(${r}, ${g}, ${b}, ${alpha19})`;
1286
1286
  }
1287
1287
  if (/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/.test(color)) {
1288
1288
  const result = color.match(/^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/);
1289
1289
  if (result) {
1290
- return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha18})`;
1290
+ return `rgba(${result[1]}, ${result[2]}, ${result[3]}, ${alpha19})`;
1291
1291
  }
1292
1292
  }
1293
1293
  return color;
@@ -4687,6 +4687,31 @@ function mergeMantineThemeSafe(base, custom, override) {
4687
4687
  }
4688
4688
  };
4689
4689
  }
4690
+ function isPlainObject2(v) {
4691
+ return typeof v === "object" && v !== null && !Array.isArray(v);
4692
+ }
4693
+ function deepMerge(base, patch) {
4694
+ if (!isPlainObject2(base) || !isPlainObject2(patch)) {
4695
+ return patch ?? base;
4696
+ }
4697
+ const out = { ...base };
4698
+ for (const key of Object.keys(patch)) {
4699
+ const patchVal = patch[key];
4700
+ const baseVal = out[key];
4701
+ out[key] = isPlainObject2(baseVal) && isPlainObject2(patchVal) ? deepMerge(baseVal, patchVal) : patchVal;
4702
+ }
4703
+ return out;
4704
+ }
4705
+ function changedTopLevelSections(changedFields) {
4706
+ if (!changedFields || changedFields.length === 0) return [];
4707
+ const sections = /* @__PURE__ */ new Set();
4708
+ for (const path of changedFields) {
4709
+ if (typeof path !== "string" || path.length === 0) continue;
4710
+ const dot = path.indexOf(".");
4711
+ sections.add(dot === -1 ? path : path.slice(0, dot));
4712
+ }
4713
+ return Array.from(sections);
4714
+ }
4690
4715
  var _instance = null;
4691
4716
  function getScriptConfigInstance() {
4692
4717
  if (!_instance) throw new Error("[dirk-cfx-react] createScriptConfig must be called before using ConfigPanel");
@@ -4696,15 +4721,18 @@ function createScriptConfig(defaultValue) {
4696
4721
  const store2 = zustand.create(() => defaultValue);
4697
4722
  let clientVersion = 0;
4698
4723
  const useScriptConfigHooks = () => {
4699
- useNuiEvent("UPDATE_SCRIPT_CONFIG", (data) => {
4700
- if (!data) return;
4701
- if (typeof data.clientVersion === "number") {
4702
- clientVersion = data.clientVersion;
4703
- }
4704
- if (data.config && typeof data.config === "object") {
4705
- store2.setState((prev) => ({ ...prev, ...data.config }));
4724
+ useNuiEvent(
4725
+ "UPDATE_SCRIPT_CONFIG",
4726
+ (data) => {
4727
+ if (!data) return;
4728
+ if (typeof data.clientVersion === "number") {
4729
+ clientVersion = data.clientVersion;
4730
+ }
4731
+ if (data.config && typeof data.config === "object") {
4732
+ store2.setState((prev) => ({ ...prev, ...data.config }));
4733
+ }
4706
4734
  }
4707
- });
4735
+ );
4708
4736
  };
4709
4737
  const fetchScriptConfig = async () => {
4710
4738
  try {
@@ -4720,12 +4748,37 @@ function createScriptConfig(defaultValue) {
4720
4748
  }
4721
4749
  return null;
4722
4750
  };
4723
- const updateScriptConfig = async (newConfig) => {
4751
+ const fetchServerOnlyScriptConfig = async () => {
4752
+ try {
4753
+ const response = await fetchNui(
4754
+ "GET_SERVER_ONLY_SCRIPT_CONFIG"
4755
+ );
4756
+ if (response?.success && response.data) {
4757
+ if (typeof response.data.clientVersion === "number") {
4758
+ clientVersion = response.data.clientVersion;
4759
+ }
4760
+ const sliver = response.data.serverOnly;
4761
+ const merged = deepMerge(store2.getState(), sliver ?? {});
4762
+ store2.setState(() => merged);
4763
+ return merged;
4764
+ }
4765
+ } catch {
4766
+ }
4767
+ return null;
4768
+ };
4769
+ const updateScriptConfig = async (newConfig, changedFields) => {
4724
4770
  store2.setState((prev) => ({ ...prev, ...newConfig }));
4725
- const response = await fetchNui("UPDATE_SCRIPT_CONFIG", {
4726
- data: newConfig,
4727
- expectedVersion: clientVersion
4728
- });
4771
+ const sections = changedTopLevelSections(changedFields);
4772
+ let payload;
4773
+ if (sections.length > 0) {
4774
+ const current = store2.getState();
4775
+ const delta = {};
4776
+ for (const key of sections) delta[key] = current[key];
4777
+ payload = { data: delta, expectedVersion: clientVersion, sectionReplace: true };
4778
+ } else {
4779
+ payload = { data: newConfig, expectedVersion: clientVersion };
4780
+ }
4781
+ const response = await fetchNui("UPDATE_SCRIPT_CONFIG", payload);
4729
4782
  if (response?.meta?.client_version != null) {
4730
4783
  clientVersion = response.meta.client_version;
4731
4784
  }
@@ -4740,10 +4793,7 @@ function createScriptConfig(defaultValue) {
4740
4793
  const resetConfig = async () => {
4741
4794
  const response = await fetchNui("RESET_SCRIPT_CONFIG");
4742
4795
  if (response?.success) {
4743
- const fresh = await fetchScriptConfig();
4744
- if (fresh) {
4745
- store2.setState(() => fresh);
4746
- }
4796
+ await fetchServerOnlyScriptConfig();
4747
4797
  }
4748
4798
  return response;
4749
4799
  };
@@ -4752,9 +4802,10 @@ function createScriptConfig(defaultValue) {
4752
4802
  updateConfig: updateScriptConfig,
4753
4803
  resetConfig,
4754
4804
  getHistory: getScriptConfigHistory,
4755
- fetchConfig: fetchScriptConfig
4805
+ fetchConfig: fetchScriptConfig,
4806
+ fetchServerOnly: fetchServerOnlyScriptConfig
4756
4807
  };
4757
- return { store: store2, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig };
4808
+ return { store: store2, updateScriptConfig, resetConfig, getScriptConfigHistory, useScriptConfigHooks, fetchScriptConfig, fetchServerOnlyScriptConfig };
4758
4809
  }
4759
4810
  var DirkErrorBoundary = class extends React4__default.default.Component {
4760
4811
  constructor() {
@@ -4941,12 +4992,6 @@ function DirkProvider({ children, overideResourceName, themeOverride }) {
4941
4992
  setScTheme(s?.theme ?? null);
4942
4993
  });
4943
4994
  }
4944
- inst.fetchConfig?.().then((full) => {
4945
- if (full && typeof full === "object") {
4946
- setScTheme(full.theme ?? null);
4947
- }
4948
- }).catch(() => {
4949
- });
4950
4995
  } catch {
4951
4996
  }
4952
4997
  return () => {
@@ -5566,11 +5611,11 @@ function cloneConfig(value) {
5566
5611
  return JSON.parse(JSON.stringify(value));
5567
5612
  }
5568
5613
  function ServerOnlyFetcher() {
5569
- const { fetchConfig } = getScriptConfigInstance();
5614
+ const { fetchServerOnly } = getScriptConfigInstance();
5570
5615
  const { reinitialize } = useFormActions();
5571
5616
  React4.useEffect(() => {
5572
5617
  let cancelled = false;
5573
- fetchConfig().then((full) => {
5618
+ fetchServerOnly().then((full) => {
5574
5619
  if (!cancelled && full) reinitialize(full);
5575
5620
  }).catch(() => {
5576
5621
  });
@@ -5594,7 +5639,7 @@ function ConfigPanel(props) {
5594
5639
  if (isSaving) return;
5595
5640
  setIsSaving(true);
5596
5641
  try {
5597
- const result = await updateConfig(form.values);
5642
+ const result = await updateConfig(form.values, form.changedFields);
5598
5643
  if (result?.success) {
5599
5644
  form.reinitialize(cloneConfig(form.values));
5600
5645
  dirkQueryClient.invalidateQueries({ queryKey: ["scriptConfigHistory"] });
@@ -7021,6 +7066,7 @@ function formatLabel(p) {
7021
7066
  }
7022
7067
  return parts.join(" \xB7 ");
7023
7068
  }
7069
+ var COMMON_ACE_GROUPS = ["group.admin", "group.mod", "group.superadmin"];
7024
7070
  var t = (key, fallback) => {
7025
7071
  const v = locale(key);
7026
7072
  return v === key ? fallback : v;
@@ -7043,7 +7089,6 @@ function AccessOverrideSection({
7043
7089
  schemaKey = "access",
7044
7090
  title,
7045
7091
  description: description2,
7046
- groupType,
7047
7092
  includeOffline = true
7048
7093
  }) {
7049
7094
  const mantineTheme = core.useMantineTheme();
@@ -7058,14 +7103,19 @@ function AccessOverrideSection({
7058
7103
  const [addingGroup, setAddingGroup] = React4.useState(false);
7059
7104
  const [addingPlayer, setAddingPlayer] = React4.useState(false);
7060
7105
  const commitGroup = (name) => {
7061
- if (!name) return;
7062
- set("groups", [...value.groups, name]);
7106
+ const g = name.trim();
7107
+ if (!g) return;
7108
+ if (!value.groups.includes(g)) set("groups", [...value.groups, g]);
7063
7109
  setAddingGroup(false);
7064
7110
  };
7065
7111
  const setGroup = (index, name) => {
7066
7112
  const next = [...value.groups];
7067
7113
  next[index] = name;
7068
- set("groups", next.filter((g) => g !== ""));
7114
+ set("groups", next);
7115
+ };
7116
+ const quickAddGroup = (name) => {
7117
+ if (value.groups.includes(name)) return;
7118
+ set("groups", [...value.groups, name]);
7069
7119
  };
7070
7120
  const removeGroup = (index) => set("groups", value.groups.filter((_, i) => i !== index));
7071
7121
  const commitIdentifier = (id) => {
@@ -7097,19 +7147,68 @@ function AccessOverrideSection({
7097
7147
  ),
7098
7148
  /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.4)", children: description2 || t(
7099
7149
  "AccessOverrideDesc",
7100
- "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."
7150
+ "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."
7101
7151
  ) }),
7102
- /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { icon: lucideReact.Users, label: t("AccessGroups", "Groups") }),
7152
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { icon: lucideReact.Users, label: t("AccessGroups", "Admin groups (ACE)") }),
7153
+ /* @__PURE__ */ jsxRuntime.jsx(core.Flex, { gap: "xs", wrap: "wrap", children: COMMON_ACE_GROUPS.map((g) => {
7154
+ const added = value.groups.includes(g);
7155
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7156
+ core.Flex,
7157
+ {
7158
+ align: "center",
7159
+ gap: "xxs",
7160
+ onClick: added ? void 0 : () => quickAddGroup(g),
7161
+ role: "button",
7162
+ tabIndex: added ? -1 : 0,
7163
+ "aria-disabled": added,
7164
+ onKeyDown: (e) => {
7165
+ if (added) return;
7166
+ if (e.key === "Enter" || e.key === " ") {
7167
+ e.preventDefault();
7168
+ quickAddGroup(g);
7169
+ }
7170
+ },
7171
+ px: "xs",
7172
+ py: "xxs",
7173
+ style: {
7174
+ cursor: added ? "default" : "pointer",
7175
+ opacity: added ? 0.4 : 1,
7176
+ background: core.alpha(color, 0.12),
7177
+ border: `0.1vh solid ${core.alpha(color, 0.4)}`,
7178
+ borderRadius: "0.4vh",
7179
+ transition: "background 0.15s, border-color 0.15s"
7180
+ },
7181
+ onMouseEnter: (e) => {
7182
+ if (added) return;
7183
+ e.currentTarget.style.background = core.alpha(color, 0.22);
7184
+ },
7185
+ onMouseLeave: (e) => {
7186
+ if (added) return;
7187
+ e.currentTarget.style.background = core.alpha(color, 0.12);
7188
+ },
7189
+ children: [
7190
+ !added && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: "1.2vh", color }),
7191
+ /* @__PURE__ */ jsxRuntime.jsx(core.Text, { ff: "Akrobat Bold", size: "xxs", lts: "0.04em", c: color, children: g })
7192
+ ]
7193
+ },
7194
+ g
7195
+ );
7196
+ }) }),
7103
7197
  /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { direction: "column", gap: "xxs", children: [
7104
- value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsxRuntime.jsx(EmptyHint, { text: t("AccessNoGroups", "No groups added.") }),
7198
+ value.groups.length === 0 && !addingGroup && /* @__PURE__ */ jsxRuntime.jsx(
7199
+ EmptyHint,
7200
+ {
7201
+ text: t("AccessNoGroups", "No ACE groups added. e.g. group.admin")
7202
+ }
7203
+ ),
7105
7204
  value.groups.map((name, i) => /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7106
7205
  /* @__PURE__ */ jsxRuntime.jsx(
7107
- GroupName,
7206
+ core.TextInput,
7108
7207
  {
7109
7208
  value: name,
7110
- onChange: (v) => setGroup(i, v),
7111
- type: groupType,
7112
- placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7209
+ onChange: (e) => setGroup(i, e.currentTarget.value),
7210
+ placeholder: t("AccessGroupPlaceholder", "ACE group or permission, e.g. group.admin"),
7211
+ size: "xs",
7113
7212
  style: { flex: 1 }
7114
7213
  }
7115
7214
  ),
@@ -7126,30 +7225,15 @@ function AccessOverrideSection({
7126
7225
  }
7127
7226
  )
7128
7227
  ] }, i)),
7129
- addingGroup && /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7130
- /* @__PURE__ */ jsxRuntime.jsx(
7131
- GroupName,
7132
- {
7133
- value: "",
7134
- onChange: (v) => commitGroup(v),
7135
- type: groupType,
7136
- placeholder: t("AccessPickGroup", "Pick a group\u2026"),
7137
- style: { flex: 1 }
7138
- }
7139
- ),
7140
- /* @__PURE__ */ jsxRuntime.jsx(
7141
- core.ActionIcon,
7142
- {
7143
- variant: "subtle",
7144
- color: "red",
7145
- size: "md",
7146
- onClick: () => setAddingGroup(false),
7147
- title: t("Remove", "Remove"),
7148
- "aria-label": t("Remove", "Remove"),
7149
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7150
- }
7151
- )
7152
- ] })
7228
+ addingGroup && /* @__PURE__ */ jsxRuntime.jsx(
7229
+ AceGroupAddRow,
7230
+ {
7231
+ placeholder: t("AccessGroupPlaceholder", "ACE group or permission, e.g. group.admin"),
7232
+ onCommit: commitGroup,
7233
+ onCancel: () => setAddingGroup(false),
7234
+ removeLabel: t("Remove", "Remove")
7235
+ }
7236
+ )
7153
7237
  ] }),
7154
7238
  !addingGroup && /* @__PURE__ */ jsxRuntime.jsx(AddRowButton, { label: t("AccessAddGroup", "Add group"), onClick: () => setAddingGroup(true) }),
7155
7239
  /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { icon: lucideReact.UserRound, label: t("AccessPlayers", "Players") }),
@@ -7249,6 +7333,53 @@ function AddRowButton({ label: label2, onClick }) {
7249
7333
  }
7250
7334
  );
7251
7335
  }
7336
+ function AceGroupAddRow({
7337
+ placeholder,
7338
+ onCommit,
7339
+ onCancel,
7340
+ removeLabel
7341
+ }) {
7342
+ const [draft, setDraft] = React4.useState("");
7343
+ const commit = () => {
7344
+ if (draft.trim()) onCommit(draft);
7345
+ else onCancel();
7346
+ };
7347
+ return /* @__PURE__ */ jsxRuntime.jsxs(core.Flex, { align: "center", gap: "xs", children: [
7348
+ /* @__PURE__ */ jsxRuntime.jsx(
7349
+ core.TextInput,
7350
+ {
7351
+ value: draft,
7352
+ onChange: (e) => setDraft(e.currentTarget.value),
7353
+ onKeyDown: (e) => {
7354
+ if (e.key === "Enter") {
7355
+ e.preventDefault();
7356
+ commit();
7357
+ } else if (e.key === "Escape") {
7358
+ e.preventDefault();
7359
+ onCancel();
7360
+ }
7361
+ },
7362
+ onBlur: commit,
7363
+ placeholder,
7364
+ size: "xs",
7365
+ autoFocus: true,
7366
+ style: { flex: 1 }
7367
+ }
7368
+ ),
7369
+ /* @__PURE__ */ jsxRuntime.jsx(
7370
+ core.ActionIcon,
7371
+ {
7372
+ variant: "subtle",
7373
+ color: "red",
7374
+ size: "md",
7375
+ onClick: onCancel,
7376
+ title: removeLabel,
7377
+ "aria-label": removeLabel,
7378
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: "1.4vh" })
7379
+ }
7380
+ )
7381
+ ] });
7382
+ }
7252
7383
 
7253
7384
  // src/utils/gtaAnimPostFx.ts
7254
7385
  var GTA_ANIM_POST_FX_GROUP_ORDER = [