@vespura/vmenu-plugin 0.0.1 → 1.0.9

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/README.md CHANGED
@@ -1,45 +1,45 @@
1
- # @vespura/vmenu-plugin
2
-
3
- The TypeScript and JavaScript plugin API for vMenu Enhanced, the version of vMenu that runs on FiveM Enhanced (GTA V Enhanced).
4
-
5
- A plugin is a normal FiveM resource of your own. With this package, your resource can add menus to vMenu, under a Plugins entry on its main menu. You describe the menu in code and the package talks to vMenu over events for you. vMenu never needs to know about your resource ahead of time.
6
-
7
- Everything the C# plugin API can do is here too. That covers buttons, checkboxes, lists, sliders, dynamic lists, submenus, confirm rows, separators, keys, player actions, translations, permission and setting gates, notifications, text prompts and themes.
8
-
9
- ```ts
10
- import { Gate, Text, VMenuPlugin } from '@vespura/vmenu-plugin/client';
11
-
12
- const plugin = VMenuPlugin.create('My Plugin');
13
-
14
- plugin.translations.add('en', { greet: 'Say hello' });
15
-
16
- const enabled = plugin.settings.bool('Enabled', true, 'Turns my plugin on or off.');
17
-
18
- const greet = plugin.rootMenu.addButton(Text.key('greet'), {
19
- gate: Gate.permission('Greet').and(Gate.setting(enabled)),
20
- });
21
-
22
- greet.onSelected(() => plugin.notify('success', 'Hello!'));
23
-
24
- await plugin.connect();
25
- ```
26
-
27
- Your server script declares the permissions and settings through `@vespura/vmenu-plugin/server`:
28
-
29
- ```ts
30
- import { ServerPluginDeclaration, VMenuServer } from '@vespura/vmenu-plugin/server';
31
-
32
- await VMenuServer.register(
33
- new ServerPluginDeclaration('My Plugin')
34
- .addPermission('Greet', 'Lets someone use the greet button.')
35
- .addBoolSetting('Enabled', true, 'Turns my plugin on or off.'),
36
- );
37
- ```
38
-
39
- The package ships ES modules, so bundle your client and server scripts with a bundler such as esbuild before FiveM loads them. Writing plain JavaScript without a bundler? The vMenu Enhanced release zip also has a single `vmenu.js` file in its `plugin-api` folder, to copy into your resource, with the exact same API on a global `vMenu` object.
40
-
41
- The package version always matches the vMenu Enhanced release it belongs to. A full example resource lives in the [vMenu.ExamplePlugin](https://github.com/TomGrobbe/vMenu.ExamplePlugin) repository, and the documentation is at [docs.vespura.com](https://docs.vespura.com/vmenu/enhanced/plugins/typescript/).
42
-
43
- ## License
44
-
45
- This package is licensed under the **GNU General Public License v3.0 or later**, `GPL-3.0-or-later`, the same license vMenu Enhanced itself uses. A plugin built on it is a work based on vMenu, so the license comes along with your plugin. Running it on your own server asks nothing of you. The moment you hand your plugin to somebody else, you owe them the complete source under this same license. The [C# package's readme](https://www.nuget.org/packages/vMenu.Enhanced.ClientAPI) explains this in more detail. This is a plain language summary, not legal advice.
1
+ # @vespura/vmenu-plugin
2
+
3
+ The TypeScript and JavaScript plugin API for vMenu Enhanced, the version of vMenu that runs on FiveM Enhanced (GTA V Enhanced).
4
+
5
+ A plugin is a normal FiveM resource of your own. With this package, your resource can add menus to vMenu, under a Plugins entry on its main menu. You describe the menu in code and the package talks to vMenu over events for you. vMenu never needs to know about your resource ahead of time.
6
+
7
+ Everything the C# plugin API can do is here too. That covers buttons, checkboxes, lists, sliders, dynamic lists, submenus, confirm rows, separators, keys, player actions, translations, permission and setting gates, notifications, text prompts and themes.
8
+
9
+ ```ts
10
+ import { Gate, NotifyStyle, Text, VMenuPlugin } from '@vespura/vmenu-plugin/client';
11
+
12
+ const plugin = VMenuPlugin.create('My Plugin');
13
+
14
+ plugin.translations.add('en', { greet: 'Say hello' });
15
+
16
+ const enabled = plugin.settings.bool('Enabled', true, 'Turns my plugin on or off.');
17
+
18
+ const greet = plugin.rootMenu.addButton(Text.key('greet'), {
19
+ gate: Gate.permission('Greet').and(Gate.setting(enabled)),
20
+ });
21
+
22
+ greet.onSelected(() => plugin.notify(NotifyStyle.Success, 'Hello!'));
23
+
24
+ await plugin.connect();
25
+ ```
26
+
27
+ Your server script declares the permissions and settings through `@vespura/vmenu-plugin/server`:
28
+
29
+ ```ts
30
+ import { ServerPluginDeclaration, VMenuServer } from '@vespura/vmenu-plugin/server';
31
+
32
+ await VMenuServer.register(
33
+ new ServerPluginDeclaration('My Plugin')
34
+ .addPermission('Greet', 'Lets someone use the greet button.')
35
+ .addBoolSetting('Enabled', true, 'Turns my plugin on or off.'),
36
+ );
37
+ ```
38
+
39
+ The package ships ES modules, so bundle your client and server scripts with a bundler such as esbuild before FiveM loads them. Writing plain JavaScript without a bundler? The vMenu Enhanced release zip also has a single `vmenu.js` file in its `plugin-api` folder, to copy into your resource, with the exact same API on a global `vMenu` object.
40
+
41
+ The package version always matches the vMenu Enhanced release it belongs to. A full example resource lives in the [vMenu.ExamplePlugin](https://github.com/TomGrobbe/vMenu.ExamplePlugin) repository, and the documentation is at [docs.vespura.com](https://docs.vespura.com/vmenu/enhanced/plugins/typescript/).
42
+
43
+ ## License
44
+
45
+ This package is licensed under the **GNU General Public License v3.0 or later**, `GPL-3.0-or-later`, the same license vMenu Enhanced itself uses. A plugin built on it is a work based on vMenu, so the license comes along with your plugin. Running it on your own server asks nothing of you. The moment you hand your plugin to somebody else, you owe them the complete source under this same license. The [C# package's readme](https://www.nuget.org/packages/vMenu.Enhanced.ClientAPI) explains this in more detail. This is a plain language summary, not legal advice.
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var version = true ? "0.0.1" : "0.0.0-local";
2
+ var version = true ? "1.0.9" : "0.0.0-local";
3
3
 
4
4
  // src/text.ts
5
5
  var Text = class _Text {
@@ -24,8 +24,8 @@ var Text = class _Text {
24
24
  if (text === null || text === void 0) {
25
25
  return void 0;
26
26
  }
27
- if (typeof text === "string") {
28
- return { text };
27
+ if (!(text instanceof _Text)) {
28
+ return { text: String(text) };
29
29
  }
30
30
  if (!text.isKey) {
31
31
  return { text: text.value };
@@ -127,6 +127,10 @@ function formatFloat(value) {
127
127
  const text = value.toFixed(4).replace(/0+$/, "");
128
128
  return text.endsWith(".") ? `${text}0` : text;
129
129
  }
130
+ function int(value) {
131
+ const number = Math.trunc(Number(value));
132
+ return Number.isFinite(number) ? number : 0;
133
+ }
130
134
 
131
135
  // src/protocol.ts
132
136
  var PROTOCOL_VERSION = 2;
@@ -673,6 +677,7 @@ var PluginList = class extends PluginItem {
673
677
  return this.node.selectedIndex ?? 0;
674
678
  }
675
679
  set selectedIndex(value) {
680
+ value = int(value);
676
681
  if (this.selectedIndex === value) {
677
682
  return;
678
683
  }
@@ -682,6 +687,7 @@ var PluginList = class extends PluginItem {
682
687
  /** Replaces the options, optionally moving the selection at the same time. */
683
688
  setOptions(options, selectedIndex) {
684
689
  const refs = Text.toRefs(options);
690
+ selectedIndex = selectedIndex === void 0 ? void 0 : int(selectedIndex);
685
691
  if (sameTexts(this.node.options, refs) && (selectedIndex === void 0 || selectedIndex === this.selectedIndex)) {
686
692
  return;
687
693
  }
@@ -764,6 +770,7 @@ var PluginSlider = class extends PluginItem {
764
770
  return this.node.position ?? this.min;
765
771
  }
766
772
  set position(value) {
773
+ value = int(value);
767
774
  if (this.position === value) {
768
775
  return;
769
776
  }
@@ -806,6 +813,7 @@ var PluginDynamicList = class extends PluginItem {
806
813
  return this.node.value ?? "";
807
814
  }
808
815
  set value(value) {
816
+ value = String(value);
809
817
  if (this.value === value) {
810
818
  return;
811
819
  }
@@ -823,7 +831,7 @@ var PluginDynamicList = class extends PluginItem {
823
831
  case CallbackTypes.DynamicChanging:
824
832
  if (typeof callback.left === "boolean") {
825
833
  const next = this.changeRequested?.(callback.currentValue ?? "", callback.left);
826
- if (typeof next === "string") {
834
+ if (next !== null && next !== void 0) {
827
835
  this.value = next;
828
836
  }
829
837
  }
@@ -1008,14 +1016,14 @@ var PluginMenu = class _PluginMenu {
1008
1016
  addList(text, values, options) {
1009
1017
  const node = this.newNode(EntryTypes.List, text, options);
1010
1018
  node.options = Text.toRefs(values);
1011
- node.selectedIndex = options?.selectedIndex ?? 0;
1019
+ node.selectedIndex = int(options?.selectedIndex ?? 0);
1012
1020
  return this.attach(new PluginList(node, text), options);
1013
1021
  }
1014
1022
  /** Adds a list that asks for a second press before it does anything. */
1015
1023
  addConfirmList(text, values, options) {
1016
1024
  const node = this.newNode(EntryTypes.ConfirmList, text, options);
1017
1025
  node.options = Text.toRefs(values);
1018
- node.selectedIndex = options?.selectedIndex ?? 0;
1026
+ node.selectedIndex = int(options?.selectedIndex ?? 0);
1019
1027
  const item = new PluginConfirmList(node, text);
1020
1028
  if (options?.confirmationDescription !== void 0) {
1021
1029
  item.confirmationDescription = options.confirmationDescription;
@@ -1025,16 +1033,16 @@ var PluginMenu = class _PluginMenu {
1025
1033
  /** Adds a row with a bar the player drags between `min` and `max`. */
1026
1034
  addSlider(text, min, max, position, options) {
1027
1035
  const node = this.newNode(EntryTypes.Slider, text, options);
1028
- node.min = min;
1029
- node.max = max;
1030
- node.position = position;
1036
+ node.min = int(min);
1037
+ node.max = int(max);
1038
+ node.position = int(position);
1031
1039
  node.showDivider = options?.showDivider ?? false;
1032
1040
  return this.attach(new PluginSlider(node, text), options);
1033
1041
  }
1034
1042
  /** Adds a row whose value your code works out each time the player scrolls it, through `changeRequested`. */
1035
1043
  addDynamicList(text, initialValue, options) {
1036
1044
  const node = this.newNode(EntryTypes.DynamicList, text, options);
1037
- node.value = initialValue;
1045
+ node.value = String(initialValue);
1038
1046
  return this.attach(new PluginDynamicList(node, text), options);
1039
1047
  }
1040
1048
  /** Adds a row that only shows text, used to split a menu into sections. */
@@ -1071,7 +1079,7 @@ var PluginMenu = class _PluginMenu {
1071
1079
  description: Text.toRef(options?.description),
1072
1080
  defaultKey,
1073
1081
  defaultButton: options?.defaultButton,
1074
- shadowedControl: options?.shadowedControl
1082
+ shadowedControl: options?.shadowedControl === void 0 ? void 0 : int(options.shadowedControl)
1075
1083
  };
1076
1084
  (_a = this.node).keys ?? (_a.keys = []);
1077
1085
  this.node.keys.push(node);
@@ -1087,7 +1095,7 @@ var PluginMenu = class _PluginMenu {
1087
1095
  */
1088
1096
  insertAt(index, add) {
1089
1097
  const previous = this.insertIndex;
1090
- this.insertIndex = clamp(index, 0, this.itemList.length);
1098
+ this.insertIndex = clamp(int(index), 0, this.itemList.length);
1091
1099
  try {
1092
1100
  add();
1093
1101
  } finally {
@@ -1100,7 +1108,7 @@ var PluginMenu = class _PluginMenu {
1100
1108
  if (from < 0) {
1101
1109
  return;
1102
1110
  }
1103
- const to = clamp(index, 0, this.itemList.length - 1);
1111
+ const to = clamp(int(index), 0, this.itemList.length - 1);
1104
1112
  if (to === from) {
1105
1113
  return;
1106
1114
  }
@@ -1314,12 +1322,13 @@ var PluginPlayerList = class extends PluginItem {
1314
1322
  return this.node.selectedIndex ?? 0;
1315
1323
  }
1316
1324
  set selectedIndex(value) {
1317
- this.node.selectedIndex = value;
1318
- this.emit({ op: UpdateOps.SetSelectedIndex, itemId: this.id, index: value });
1325
+ this.node.selectedIndex = int(value);
1326
+ this.emit({ op: UpdateOps.SetSelectedIndex, itemId: this.id, index: this.node.selectedIndex });
1319
1327
  }
1320
1328
  /** Replaces the options, optionally moving the selection at the same time. */
1321
1329
  setOptions(options, selectedIndex) {
1322
1330
  this.node.options = Text.toRefs(options);
1331
+ selectedIndex = selectedIndex === void 0 ? void 0 : int(selectedIndex);
1323
1332
  if (selectedIndex !== void 0) {
1324
1333
  this.node.selectedIndex = selectedIndex;
1325
1334
  }
@@ -1369,7 +1378,7 @@ var PluginPlayerActions = class {
1369
1378
  addList(text, values, options) {
1370
1379
  const node = this.newNode(EntryTypes.List, text, options);
1371
1380
  node.options = Text.toRefs(values);
1372
- node.selectedIndex = options?.selectedIndex ?? 0;
1381
+ node.selectedIndex = int(options?.selectedIndex ?? 0);
1373
1382
  return this.attach(new PluginPlayerList(node, text), options);
1374
1383
  }
1375
1384
  /** Adds a row that only shows text. */
@@ -1448,9 +1457,9 @@ var PluginThemes = class {
1448
1457
  return;
1449
1458
  }
1450
1459
  const current = list.current?.toLowerCase();
1451
- this.availableValue = (list.themes ?? []).map((theme) => ({
1460
+ this.availableValue = (Array.isArray(list.themes) ? list.themes : []).filter((theme) => typeof theme?.id === "string").map((theme) => ({
1452
1461
  id: theme.id,
1453
- name: theme.name,
1462
+ name: typeof theme.name === "string" ? theme.name : theme.id,
1454
1463
  isCurrent: theme.id.toLowerCase() === current
1455
1464
  }));
1456
1465
  this.currentIdValue = list.current ?? void 0;
@@ -1489,6 +1498,12 @@ var PluginTranslations = class {
1489
1498
  };
1490
1499
 
1491
1500
  // src/client/plugin.ts
1501
+ var NotifyStyle = {
1502
+ Info: "info",
1503
+ Success: "success",
1504
+ Warning: "warning",
1505
+ Error: "error"
1506
+ };
1492
1507
  var CarriedItemOps = /* @__PURE__ */ new Set([
1493
1508
  UpdateOps.SetText,
1494
1509
  UpdateOps.SetDescription,
@@ -1581,7 +1596,7 @@ var VMenuPlugin = class _VMenuPlugin {
1581
1596
  if (!this.connected) {
1582
1597
  return;
1583
1598
  }
1584
- emitLocal(PluginEvents.Notify, JSON.stringify({ style, text: Text.toRef(text), durationMs }));
1599
+ emitLocal(PluginEvents.Notify, JSON.stringify({ style, text: Text.toRef(text), durationMs: durationMs === void 0 ? void 0 : int(durationMs) }));
1585
1600
  }
1586
1601
  /** Asks the player for text through vMenu's input box. Resolves to null if they cancelled or the box was unavailable. */
1587
1602
  async getText(title, options) {
@@ -1598,8 +1613,8 @@ var VMenuPlugin = class _VMenuPlugin {
1598
1613
  requestId,
1599
1614
  prompts: prompts.map((prompt) => ({
1600
1615
  title: Text.toRef(prompt.title),
1601
- maxLength: prompt.maxLength ?? 60,
1602
- initial: prompt.initialValue ?? "",
1616
+ maxLength: int(prompt.maxLength ?? 60),
1617
+ initial: String(prompt.initialValue ?? ""),
1603
1618
  suggestions: prompt.suggestions && prompt.suggestions.length > 0 ? prompt.suggestions.map((suggestion) => ({ value: suggestion.value, description: suggestion.description })) : void 0
1604
1619
  }))
1605
1620
  };
@@ -1877,6 +1892,7 @@ var VMenuPlugin = class _VMenuPlugin {
1877
1892
  };
1878
1893
  export {
1879
1894
  Gate,
1895
+ NotifyStyle,
1880
1896
  PluginBoolSetting,
1881
1897
  PluginButton,
1882
1898
  PluginCheckbox,
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var version = true ? "0.0.1" : "0.0.0-local";
2
+ var version = true ? "1.0.9" : "0.0.0-local";
3
3
 
4
4
  // src/format.ts
5
5
  function formatFloat(value) {
@@ -111,7 +111,7 @@ var natives = globalThis;
111
111
  var currentResource = () => natives.GetCurrentResourceName();
112
112
  var emitLocal = (eventName, ...args) => natives.emit(eventName, ...args);
113
113
  var onLocal = (eventName, handler) => natives.on(eventName, handler);
114
- var isAceAllowed = (playerSource, object) => natives.IsPlayerAceAllowed(playerSource, object) === true;
114
+ var isAceAllowed = (playerSource, object) => Boolean(natives.IsPlayerAceAllowed(playerSource, object));
115
115
  var log = {
116
116
  info: (resource, message) => console.log(`[${resource}] ${message}`),
117
117
  warn: (resource, message) => console.warn(`[${resource}] ${message}`),
@@ -1,4 +1,4 @@
1
- import type { GateNode, TextRef } from '../protocol';
1
+ import type { GateNode, TextRef } from '../protocol.js';
2
2
  export declare function sameText(left: TextRef | undefined, right: TextRef | undefined): boolean;
3
3
  export declare function sameTexts(left: TextRef[] | undefined, right: TextRef[] | undefined): boolean;
4
4
  export declare function sameGate(left: GateNode | undefined, right: GateNode | undefined): boolean;
@@ -1,4 +1,4 @@
1
- import type { PluginBoolSetting } from './settings';
1
+ import type { PluginBoolSetting } from './settings.js';
2
2
  /** A gate, or a permission's short name as a shortcut for `Gate.permission(name)`. */
3
3
  export type GateLike = Gate | string;
4
4
  /**
@@ -1,7 +1,7 @@
1
- import type { UpdateOp } from '../protocol';
2
- import type { PluginItem } from './item';
3
- import type { PluginKey } from './key';
4
- import type { PluginMenu } from './menu';
1
+ import type { UpdateOp } from '../protocol.js';
2
+ import type { PluginItem } from './item.js';
3
+ import type { PluginKey } from './key.js';
4
+ import type { PluginMenu } from './menu.js';
5
5
  export interface PluginHost {
6
6
  readonly resource: string;
7
7
  readonly isConnected: boolean;
@@ -1,14 +1,14 @@
1
- export { version } from '../version';
2
- export { Text, type TextLike } from '../text';
3
- export type { Unsubscribe } from '../signal';
4
- export type { RegisterResult } from '../protocol';
5
- export { Gate, type GateLike } from './gate';
6
- export { PluginSetting, PluginBoolSetting, PluginIntSetting, PluginFloatSetting, PluginStringSetting, PluginSettings, } from './settings';
7
- export { PluginItem, type ItemOptions } from './item';
8
- export { PluginButton, PluginConfirmButton, PluginCheckbox, PluginList, PluginConfirmList, PluginSlider, PluginDynamicList, PluginSeparator, PluginSubmenu, } from './items';
9
- export { PluginKey, type PluginKeyPress } from './key';
10
- export { PluginMenu, type CheckboxOptions, type ConfirmOptions, type KeyOptions, type ListOptions, type SliderOptions, type SubmenuOptions, } from './menu';
11
- export { PluginPlayerActions, PluginPlayerButton, PluginPlayerConfirmButton, PluginPlayerList, type PlayerTarget, } from './playerActions';
12
- export { PluginThemes, type PluginTheme } from './themes';
13
- export { PluginTranslations } from './translations';
14
- export { VMenuPlugin, type NotifyStyle, type PluginPrompt, type PromptSuggestion } from './plugin';
1
+ export { version } from '../version.js';
2
+ export { Text, type TextLike } from '../text.js';
3
+ export type { Unsubscribe } from '../signal.js';
4
+ export type { RegisterResult } from '../protocol.js';
5
+ export { Gate, type GateLike } from './gate.js';
6
+ export { PluginSetting, PluginBoolSetting, PluginIntSetting, PluginFloatSetting, PluginStringSetting, PluginSettings, } from './settings.js';
7
+ export { PluginItem, type ItemOptions } from './item.js';
8
+ export { PluginButton, PluginConfirmButton, PluginCheckbox, PluginList, PluginConfirmList, PluginSlider, PluginDynamicList, PluginSeparator, PluginSubmenu, } from './items.js';
9
+ export { PluginKey, type PluginKeyPress } from './key.js';
10
+ export { PluginMenu, type CheckboxOptions, type ConfirmOptions, type KeyOptions, type ListOptions, type SliderOptions, type SubmenuOptions, } from './menu.js';
11
+ export { PluginPlayerActions, PluginPlayerButton, PluginPlayerConfirmButton, PluginPlayerList, type PlayerTarget, } from './playerActions.js';
12
+ export { PluginThemes, type PluginTheme } from './themes.js';
13
+ export { PluginTranslations } from './translations.js';
14
+ export { VMenuPlugin, NotifyStyle, type PluginPrompt, type PromptSuggestion } from './plugin.js';
@@ -1,7 +1,7 @@
1
- import { type UpdateOp } from '../protocol';
2
- import { Signal, type Unsubscribe } from '../signal';
3
- import { type TextLike } from '../text';
4
- import { type GateLike } from './gate';
1
+ import { type UpdateOp } from '../protocol.js';
2
+ import { Signal, type Unsubscribe } from '../signal.js';
3
+ import { type TextLike } from '../text.js';
4
+ import { type GateLike } from './gate.js';
5
5
  /** Properties every row accepts when you add it. Setting them here sends them along with the row itself. */
6
6
  export interface ItemOptions {
7
7
  /** A stable id of your own. Generated when left out. Unique within your plugin. */
@@ -1,7 +1,7 @@
1
- import type { Unsubscribe } from '../signal';
2
- import { type TextLike } from '../text';
3
- import { PluginItem } from './item';
4
- import type { PluginMenu } from './menu';
1
+ import type { Unsubscribe } from '../signal.js';
2
+ import { type TextLike } from '../text.js';
3
+ import { PluginItem } from './item.js';
4
+ import type { PluginMenu } from './menu.js';
5
5
  /** A row the player presses. */
6
6
  export declare class PluginButton extends PluginItem {
7
7
  private readonly selected;
@@ -1,7 +1,7 @@
1
- import { type Unsubscribe } from '../signal';
2
- import { type TextLike } from '../text';
3
- import { type GateLike } from './gate';
4
- import type { PluginItem } from './item';
1
+ import { type Unsubscribe } from '../signal.js';
2
+ import { type TextLike } from '../text.js';
3
+ import { type GateLike } from './gate.js';
4
+ import type { PluginItem } from './item.js';
5
5
  /**
6
6
  * Which row the cursor was on when a key was pressed. At most one of the two is set, and neither
7
7
  * when the menu is empty or the row is not one of yours.
@@ -1,8 +1,8 @@
1
- import { type Unsubscribe } from '../signal';
2
- import { type TextLike } from '../text';
3
- import { PluginItem, type ItemOptions } from './item';
4
- import { PluginButton, PluginCheckbox, PluginConfirmButton, PluginConfirmList, PluginDynamicList, PluginList, PluginSeparator, PluginSlider, PluginSubmenu } from './items';
5
- import { PluginKey } from './key';
1
+ import { type Unsubscribe } from '../signal.js';
2
+ import { type TextLike } from '../text.js';
3
+ import { PluginItem, type ItemOptions } from './item.js';
4
+ import { PluginButton, PluginCheckbox, PluginConfirmButton, PluginConfirmList, PluginDynamicList, PluginList, PluginSeparator, PluginSlider, PluginSubmenu } from './items.js';
5
+ import { PluginKey } from './key.js';
6
6
  /** Extra properties for a confirm button or confirm list. */
7
7
  export interface ConfirmOptions extends ItemOptions {
8
8
  /** What the row asks before its second press. */
@@ -1,7 +1,7 @@
1
- import type { Unsubscribe } from '../signal';
2
- import { type TextLike } from '../text';
3
- import { PluginItem, type ItemOptions } from './item';
4
- import { PluginSeparator } from './items';
1
+ import type { Unsubscribe } from '../signal.js';
2
+ import { type TextLike } from '../text.js';
3
+ import { PluginItem, type ItemOptions } from './item.js';
4
+ import { PluginSeparator } from './items.js';
5
5
  /**
6
6
  * The player a player action was used on, read from vMenu's Online Players snapshot. The snapshot
7
7
  * can be stale and the player may have left, so your server side must check the target still exists
@@ -1,14 +1,20 @@
1
- import { type RegisterResult } from '../protocol';
2
- import { type Unsubscribe } from '../signal';
3
- import { type TextLike } from '../text';
4
- import type { PluginHost } from './host';
5
- import { PluginMenu } from './menu';
6
- import { PluginPlayerActions } from './playerActions';
7
- import { PluginSettings } from './settings';
8
- import { PluginThemes } from './themes';
9
- import { PluginTranslations } from './translations';
1
+ import { type RegisterResult } from '../protocol.js';
2
+ import { type Unsubscribe } from '../signal.js';
3
+ import { type TextLike } from '../text.js';
4
+ import { PluginMenu } from './menu.js';
5
+ import { PluginPlayerActions } from './playerActions.js';
6
+ import { PluginSettings } from './settings.js';
7
+ import { PluginThemes } from './themes.js';
8
+ import { PluginTranslations } from './translations.js';
9
+ /** How a notification is coloured in vMenu's notification area. The plain strings work too. */
10
+ export declare const NotifyStyle: {
11
+ readonly Info: 'info';
12
+ readonly Success: 'success';
13
+ readonly Warning: 'warning';
14
+ readonly Error: 'error';
15
+ };
10
16
  /** How a notification is coloured in vMenu's notification area. */
11
- export type NotifyStyle = 'info' | 'success' | 'warning' | 'error';
17
+ export type NotifyStyle = (typeof NotifyStyle)[keyof typeof NotifyStyle];
12
18
  /** One suggestion row the input box offers while the player types. */
13
19
  export interface PromptSuggestion {
14
20
  /** What lands in the box when the suggestion is picked. */
@@ -30,7 +36,7 @@ export interface PluginPrompt {
30
36
  * settings, then call `connect`. It registers again by itself whenever vMenu restarts, so everything
31
37
  * you declared and changed since is restored.
32
38
  */
33
- export declare class VMenuPlugin implements PluginHost {
39
+ export declare class VMenuPlugin {
34
40
  private readonly displayName;
35
41
  private readonly itemsById;
36
42
  private readonly menusById;
@@ -1,4 +1,4 @@
1
- import { type Unsubscribe } from '../signal';
1
+ import { type Unsubscribe } from '../signal.js';
2
2
  /** One of vMenu's looks. Hand the `id` to `themes.set` to put the menus in it, and show the `name` to the player. */
3
3
  export interface PluginTheme {
4
4
  readonly id: string;
@@ -1 +1,2 @@
1
1
  export declare function formatFloat(value: number): string;
2
+ export declare function int(value: unknown): number;
@@ -1,2 +1,2 @@
1
- export * from './client/index';
2
- export { ServerPluginDeclaration, VMenuServer } from './server/index';
1
+ export * from './client/index.js';
2
+ export { ServerPluginDeclaration, VMenuServer } from './server/index.js';
@@ -1,5 +1,5 @@
1
- export { version } from '../version';
2
- export type { Unsubscribe } from '../signal';
3
- export type { RegisterResult } from '../protocol';
4
- export { ServerPluginDeclaration } from './declaration';
5
- export { VMenuServer } from './server';
1
+ export { version } from '../version.js';
2
+ export type { Unsubscribe } from '../signal.js';
3
+ export type { RegisterResult } from '../protocol.js';
4
+ export { ServerPluginDeclaration } from './declaration.js';
5
+ export { VMenuServer } from './server.js';
@@ -1,6 +1,6 @@
1
- import { type RegisterResult } from '../protocol';
2
- import { type Unsubscribe } from '../signal';
3
- import type { ServerPluginDeclaration } from './declaration';
1
+ import { type RegisterResult } from '../protocol.js';
2
+ import { type Unsubscribe } from '../signal.js';
3
+ import type { ServerPluginDeclaration } from './declaration.js';
4
4
  /**
5
5
  * The server side entry point for a plugin. Call `register` once at startup; registering again
6
6
  * after a vMenu restart happens by itself. Then use `isPlayerAllowed` to check the permissions you declared.
package/dist/vmenu.js CHANGED
@@ -1,6 +1,5 @@
1
- // vMenu Enhanced plugin API for JavaScript, built for vMenu Enhanced v0.0.1.
1
+ // vMenu Enhanced plugin API for JavaScript, built for vMenu Enhanced v1.0.9.
2
2
  // Copy this file into your resource and load it before your own scripts, see https://docs.vespura.com/vmenu/enhanced/plugins/javascript/
3
- // Licensed GPL-3.0-or-later, https://github.com/TomGrobbe/vMenu/blob/enhanced/LICENSE.md
4
3
  "use strict";
5
4
  var vMenu = (() => {
6
5
  var __defProp = Object.defineProperty;
@@ -25,6 +24,7 @@ var vMenu = (() => {
25
24
  var iife_exports = {};
26
25
  __export(iife_exports, {
27
26
  Gate: () => Gate,
27
+ NotifyStyle: () => NotifyStyle,
28
28
  PluginBoolSetting: () => PluginBoolSetting,
29
29
  PluginButton: () => PluginButton,
30
30
  PluginCheckbox: () => PluginCheckbox,
@@ -57,7 +57,7 @@ var vMenu = (() => {
57
57
  });
58
58
 
59
59
  // src/version.ts
60
- var version = true ? "0.0.1" : "0.0.0-local";
60
+ var version = true ? "1.0.9" : "0.0.0-local";
61
61
 
62
62
  // src/text.ts
63
63
  var Text = class _Text {
@@ -82,8 +82,8 @@ var vMenu = (() => {
82
82
  if (text === null || text === void 0) {
83
83
  return void 0;
84
84
  }
85
- if (typeof text === "string") {
86
- return { text };
85
+ if (!(text instanceof _Text)) {
86
+ return { text: String(text) };
87
87
  }
88
88
  if (!text.isKey) {
89
89
  return { text: text.value };
@@ -161,7 +161,7 @@ var vMenu = (() => {
161
161
  var getConvar = (name, fallback) => natives.GetConvar(name, fallback) ?? fallback;
162
162
  var getKvp = (key) => natives.GetResourceKvpString(key) ?? void 0;
163
163
  var setKvp = (key, value) => natives.SetResourceKvp(key, value);
164
- var isAceAllowed = (playerSource, object) => natives.IsPlayerAceAllowed(playerSource, object) === true;
164
+ var isAceAllowed = (playerSource, object) => Boolean(natives.IsPlayerAceAllowed(playerSource, object));
165
165
  var log = {
166
166
  info: (resource, message) => console.log(`[${resource}] ${message}`),
167
167
  warn: (resource, message) => console.warn(`[${resource}] ${message}`),
@@ -186,6 +186,10 @@ var vMenu = (() => {
186
186
  const text = value.toFixed(4).replace(/0+$/, "");
187
187
  return text.endsWith(".") ? `${text}0` : text;
188
188
  }
189
+ function int(value) {
190
+ const number = Math.trunc(Number(value));
191
+ return Number.isFinite(number) ? number : 0;
192
+ }
189
193
 
190
194
  // src/protocol.ts
191
195
  var PROTOCOL_VERSION = 2;
@@ -734,6 +738,7 @@ var vMenu = (() => {
734
738
  return this.node.selectedIndex ?? 0;
735
739
  }
736
740
  set selectedIndex(value) {
741
+ value = int(value);
737
742
  if (this.selectedIndex === value) {
738
743
  return;
739
744
  }
@@ -743,6 +748,7 @@ var vMenu = (() => {
743
748
  /** Replaces the options, optionally moving the selection at the same time. */
744
749
  setOptions(options, selectedIndex) {
745
750
  const refs = Text.toRefs(options);
751
+ selectedIndex = selectedIndex === void 0 ? void 0 : int(selectedIndex);
746
752
  if (sameTexts(this.node.options, refs) && (selectedIndex === void 0 || selectedIndex === this.selectedIndex)) {
747
753
  return;
748
754
  }
@@ -825,6 +831,7 @@ var vMenu = (() => {
825
831
  return this.node.position ?? this.min;
826
832
  }
827
833
  set position(value) {
834
+ value = int(value);
828
835
  if (this.position === value) {
829
836
  return;
830
837
  }
@@ -867,6 +874,7 @@ var vMenu = (() => {
867
874
  return this.node.value ?? "";
868
875
  }
869
876
  set value(value) {
877
+ value = String(value);
870
878
  if (this.value === value) {
871
879
  return;
872
880
  }
@@ -884,7 +892,7 @@ var vMenu = (() => {
884
892
  case CallbackTypes.DynamicChanging:
885
893
  if (typeof callback.left === "boolean") {
886
894
  const next = this.changeRequested?.(callback.currentValue ?? "", callback.left);
887
- if (typeof next === "string") {
895
+ if (next !== null && next !== void 0) {
888
896
  this.value = next;
889
897
  }
890
898
  }
@@ -1069,14 +1077,14 @@ var vMenu = (() => {
1069
1077
  addList(text, values, options) {
1070
1078
  const node = this.newNode(EntryTypes.List, text, options);
1071
1079
  node.options = Text.toRefs(values);
1072
- node.selectedIndex = options?.selectedIndex ?? 0;
1080
+ node.selectedIndex = int(options?.selectedIndex ?? 0);
1073
1081
  return this.attach(new PluginList(node, text), options);
1074
1082
  }
1075
1083
  /** Adds a list that asks for a second press before it does anything. */
1076
1084
  addConfirmList(text, values, options) {
1077
1085
  const node = this.newNode(EntryTypes.ConfirmList, text, options);
1078
1086
  node.options = Text.toRefs(values);
1079
- node.selectedIndex = options?.selectedIndex ?? 0;
1087
+ node.selectedIndex = int(options?.selectedIndex ?? 0);
1080
1088
  const item = new PluginConfirmList(node, text);
1081
1089
  if (options?.confirmationDescription !== void 0) {
1082
1090
  item.confirmationDescription = options.confirmationDescription;
@@ -1086,16 +1094,16 @@ var vMenu = (() => {
1086
1094
  /** Adds a row with a bar the player drags between `min` and `max`. */
1087
1095
  addSlider(text, min, max, position, options) {
1088
1096
  const node = this.newNode(EntryTypes.Slider, text, options);
1089
- node.min = min;
1090
- node.max = max;
1091
- node.position = position;
1097
+ node.min = int(min);
1098
+ node.max = int(max);
1099
+ node.position = int(position);
1092
1100
  node.showDivider = options?.showDivider ?? false;
1093
1101
  return this.attach(new PluginSlider(node, text), options);
1094
1102
  }
1095
1103
  /** Adds a row whose value your code works out each time the player scrolls it, through `changeRequested`. */
1096
1104
  addDynamicList(text, initialValue, options) {
1097
1105
  const node = this.newNode(EntryTypes.DynamicList, text, options);
1098
- node.value = initialValue;
1106
+ node.value = String(initialValue);
1099
1107
  return this.attach(new PluginDynamicList(node, text), options);
1100
1108
  }
1101
1109
  /** Adds a row that only shows text, used to split a menu into sections. */
@@ -1132,7 +1140,7 @@ var vMenu = (() => {
1132
1140
  description: Text.toRef(options?.description),
1133
1141
  defaultKey,
1134
1142
  defaultButton: options?.defaultButton,
1135
- shadowedControl: options?.shadowedControl
1143
+ shadowedControl: options?.shadowedControl === void 0 ? void 0 : int(options.shadowedControl)
1136
1144
  };
1137
1145
  (_a = this.node).keys ?? (_a.keys = []);
1138
1146
  this.node.keys.push(node);
@@ -1148,7 +1156,7 @@ var vMenu = (() => {
1148
1156
  */
1149
1157
  insertAt(index, add) {
1150
1158
  const previous = this.insertIndex;
1151
- this.insertIndex = clamp(index, 0, this.itemList.length);
1159
+ this.insertIndex = clamp(int(index), 0, this.itemList.length);
1152
1160
  try {
1153
1161
  add();
1154
1162
  } finally {
@@ -1161,7 +1169,7 @@ var vMenu = (() => {
1161
1169
  if (from < 0) {
1162
1170
  return;
1163
1171
  }
1164
- const to = clamp(index, 0, this.itemList.length - 1);
1172
+ const to = clamp(int(index), 0, this.itemList.length - 1);
1165
1173
  if (to === from) {
1166
1174
  return;
1167
1175
  }
@@ -1375,12 +1383,13 @@ var vMenu = (() => {
1375
1383
  return this.node.selectedIndex ?? 0;
1376
1384
  }
1377
1385
  set selectedIndex(value) {
1378
- this.node.selectedIndex = value;
1379
- this.emit({ op: UpdateOps.SetSelectedIndex, itemId: this.id, index: value });
1386
+ this.node.selectedIndex = int(value);
1387
+ this.emit({ op: UpdateOps.SetSelectedIndex, itemId: this.id, index: this.node.selectedIndex });
1380
1388
  }
1381
1389
  /** Replaces the options, optionally moving the selection at the same time. */
1382
1390
  setOptions(options, selectedIndex) {
1383
1391
  this.node.options = Text.toRefs(options);
1392
+ selectedIndex = selectedIndex === void 0 ? void 0 : int(selectedIndex);
1384
1393
  if (selectedIndex !== void 0) {
1385
1394
  this.node.selectedIndex = selectedIndex;
1386
1395
  }
@@ -1430,7 +1439,7 @@ var vMenu = (() => {
1430
1439
  addList(text, values, options) {
1431
1440
  const node = this.newNode(EntryTypes.List, text, options);
1432
1441
  node.options = Text.toRefs(values);
1433
- node.selectedIndex = options?.selectedIndex ?? 0;
1442
+ node.selectedIndex = int(options?.selectedIndex ?? 0);
1434
1443
  return this.attach(new PluginPlayerList(node, text), options);
1435
1444
  }
1436
1445
  /** Adds a row that only shows text. */
@@ -1509,9 +1518,9 @@ var vMenu = (() => {
1509
1518
  return;
1510
1519
  }
1511
1520
  const current = list.current?.toLowerCase();
1512
- this.availableValue = (list.themes ?? []).map((theme) => ({
1521
+ this.availableValue = (Array.isArray(list.themes) ? list.themes : []).filter((theme) => typeof theme?.id === "string").map((theme) => ({
1513
1522
  id: theme.id,
1514
- name: theme.name,
1523
+ name: typeof theme.name === "string" ? theme.name : theme.id,
1515
1524
  isCurrent: theme.id.toLowerCase() === current
1516
1525
  }));
1517
1526
  this.currentIdValue = list.current ?? void 0;
@@ -1550,6 +1559,12 @@ var vMenu = (() => {
1550
1559
  };
1551
1560
 
1552
1561
  // src/client/plugin.ts
1562
+ var NotifyStyle = {
1563
+ Info: "info",
1564
+ Success: "success",
1565
+ Warning: "warning",
1566
+ Error: "error"
1567
+ };
1553
1568
  var CarriedItemOps = /* @__PURE__ */ new Set([
1554
1569
  UpdateOps.SetText,
1555
1570
  UpdateOps.SetDescription,
@@ -1642,7 +1657,7 @@ var vMenu = (() => {
1642
1657
  if (!this.connected) {
1643
1658
  return;
1644
1659
  }
1645
- emitLocal(PluginEvents.Notify, JSON.stringify({ style, text: Text.toRef(text), durationMs }));
1660
+ emitLocal(PluginEvents.Notify, JSON.stringify({ style, text: Text.toRef(text), durationMs: durationMs === void 0 ? void 0 : int(durationMs) }));
1646
1661
  }
1647
1662
  /** Asks the player for text through vMenu's input box. Resolves to null if they cancelled or the box was unavailable. */
1648
1663
  async getText(title, options) {
@@ -1659,8 +1674,8 @@ var vMenu = (() => {
1659
1674
  requestId,
1660
1675
  prompts: prompts.map((prompt) => ({
1661
1676
  title: Text.toRef(prompt.title),
1662
- maxLength: prompt.maxLength ?? 60,
1663
- initial: prompt.initialValue ?? "",
1677
+ maxLength: int(prompt.maxLength ?? 60),
1678
+ initial: String(prompt.initialValue ?? ""),
1664
1679
  suggestions: prompt.suggestions && prompt.suggestions.length > 0 ? prompt.suggestions.map((suggestion) => ({ value: suggestion.value, description: suggestion.description })) : void 0
1665
1680
  }))
1666
1681
  };
package/package.json CHANGED
@@ -1,44 +1,43 @@
1
- {
2
- "name": "@vespura/vmenu-plugin",
3
- "version": "0.0.1",
4
- "description": "Build vMenu Enhanced plugins in TypeScript or JavaScript.",
5
- "license": "GPL-3.0-or-later",
6
- "author": "Tom Grobbe",
7
- "homepage": "https://docs.vespura.com/vmenu/enhanced/plugins/",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/TomGrobbe/vMenu.git",
11
- "directory": "src/PluginApi/vMenu.Enhanced.JsAPI"
12
- },
13
- "keywords": [
14
- "fivem",
15
- "vmenu",
16
- "plugin"
17
- ],
18
- "type": "module",
19
- "sideEffects": false,
20
- "exports": {
21
- "./client": {
22
- "types": "./dist/types/client/index.d.ts",
23
- "import": "./dist/client.js"
24
- },
25
- "./server": {
26
- "types": "./dist/types/server/index.d.ts",
27
- "import": "./dist/server.js"
28
- }
29
- },
30
- "files": [
31
- "dist",
32
- "README.md",
33
- "LICENSE.md"
34
- ],
35
- "scripts": {
36
- "check": "tsc --noEmit && node scripts/check-contracts.mjs",
37
- "build": "node scripts/check-contracts.mjs && node scripts/build.mjs",
38
- "prepack": "node scripts/build.mjs"
39
- },
40
- "devDependencies": {
41
- "esbuild": "0.28.2",
42
- "typescript": "7.0.2"
43
- }
44
- }
1
+ {
2
+ "name": "@vespura/vmenu-plugin",
3
+ "version": "1.0.9",
4
+ "description": "Build vMenu Enhanced plugins in TypeScript or JavaScript.",
5
+ "license": "GPL-3.0-or-later",
6
+ "author": "Tom Grobbe",
7
+ "homepage": "https://docs.vespura.com/vmenu/enhanced/plugins/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/TomGrobbe/vMenu.git",
11
+ "directory": "src/PluginApi/vMenu.Enhanced.JsAPI"
12
+ },
13
+ "keywords": [
14
+ "fivem",
15
+ "vmenu",
16
+ "plugin"
17
+ ],
18
+ "type": "module",
19
+ "sideEffects": false,
20
+ "exports": {
21
+ "./client": {
22
+ "types": "./dist/types/client/index.d.ts",
23
+ "import": "./dist/client.js"
24
+ },
25
+ "./server": {
26
+ "types": "./dist/types/server/index.d.ts",
27
+ "import": "./dist/server.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE.md"
34
+ ],
35
+ "scripts": {
36
+ "check": "tsc --noEmit && node scripts/check-contracts.mjs",
37
+ "build": "node scripts/check-contracts.mjs && node scripts/build.mjs"
38
+ },
39
+ "devDependencies": {
40
+ "esbuild": "0.28.2",
41
+ "typescript": "7.0.2"
42
+ }
43
+ }