@noya-app/noya-multiplayer-react 0.1.40 → 0.1.41

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.
@@ -0,0 +1,206 @@
1
+ import * as _noya_app_state_manager from '@noya-app/state-manager';
2
+ import { MultiplayerUser, EphemeralUserData, ConnectionEvent, Task, MultiplayerStateManager, AssetManager, StateManager, StateManagerOptions, MultiplayerStateManagerOptions, SyncAdapterOptions, TSchema, Static } from '@noya-app/state-manager';
3
+ export * from '@noya-app/state-manager';
4
+ import React, { ComponentPropsWithoutRef, SetStateAction } from 'react';
5
+ import { GetAtPath, ObservableOptions, PathKey, Observable } from '@noya-app/observable';
6
+
7
+ declare function getAvatarInitials(name: string): string;
8
+ /**
9
+ * Create a background color and initials from a name
10
+ *
11
+ * Adapted from https://mui.com/material-ui/react-avatar/
12
+ */
13
+ declare function getAvatarStyle(name: string): {
14
+ color: string;
15
+ backgroundColor: string;
16
+ };
17
+
18
+ type Point = {
19
+ x: number;
20
+ y: number;
21
+ };
22
+ type UserPointerData = {
23
+ pointer?: Point;
24
+ };
25
+ type UserPointerProps<E extends UserPointerData> = {
26
+ user: MultiplayerUser;
27
+ ephemeralUserData: EphemeralUserData<E>;
28
+ hideAfter?: number;
29
+ };
30
+ declare const UserPointer_: <E extends UserPointerData>({ user, ephemeralUserData, hideAfter, }: UserPointerProps<E>) => React.JSX.Element | null;
31
+ declare const UserPointer: typeof UserPointer_;
32
+ declare const UserPointerContainer: React.NamedExoticComponent<{
33
+ point: Point;
34
+ children: React.ReactNode;
35
+ show?: boolean;
36
+ }>;
37
+ declare const UserNameTag: React.NamedExoticComponent<{
38
+ background: string;
39
+ children: React.ReactNode;
40
+ }>;
41
+ type UserPointersOverlayProps<E extends UserPointerData> = {
42
+ connectedUsers: MultiplayerUser[];
43
+ ephemeralUserData: EphemeralUserData<E>;
44
+ };
45
+ declare const UserPointersOverlay: React.MemoExoticComponent<(<E extends UserPointerData>({ connectedUsers, ephemeralUserData }: UserPointersOverlayProps<E>) => React.JSX.Element)>;
46
+ declare const UserPointerIcon: React.NamedExoticComponent<{
47
+ color?: string;
48
+ }>;
49
+
50
+ type StateInspectorAnchor = "left" | "right" | "bottom left" | "bottom right";
51
+ type StateInspectorOptions = {
52
+ colorScheme?: "light" | "dark";
53
+ anchor?: StateInspectorAnchor;
54
+ };
55
+ declare const StateInspector: React.MemoExoticComponent<(<S, M, E>({ state, connectionEvents, connectedUsers, tasks, userId, unstyled, colorScheme, multiplayerStateManager, assetManager, ephemeralUserData, anchor, forceInit, ...props }: {
56
+ state: S;
57
+ connectionEvents?: ConnectionEvent<S>[];
58
+ connectedUsers?: MultiplayerUser[];
59
+ tasks?: Task[];
60
+ userId?: string;
61
+ unstyled?: boolean;
62
+ multiplayerStateManager: MultiplayerStateManager<S, M>;
63
+ assetManager: AssetManager;
64
+ ephemeralUserData: EphemeralUserData<E>;
65
+ forceInit: () => void;
66
+ } & StateInspectorOptions & ComponentPropsWithoutRef<"div">) => React.JSX.Element | null)>;
67
+
68
+ declare function useSyncStateManager<S, M>(stateManager: StateManager<S, M>): S;
69
+ declare function useSyncStateManager<S, M, P extends string>(stateManager: StateManager<S, M>, path: P): GetAtPath<S, P>;
70
+ declare function useManagedState<S, M = void>(createInitialState: () => S, options?: StateManagerOptions<S, M>): readonly [S, (...args: M extends {} ? [metadata: M, action: SetStateAction<S>] : [action: SetStateAction<S>]) => void, StateManager<S, M>];
71
+ declare function useManagedHistory<S, M = void>(stateManager: StateManager<S, M>): _noya_app_state_manager.HistorySnapshot<S, M>;
72
+ declare function useSyncMultiplayerStateManager<S, M = void>(multiplayerStateManager: MultiplayerStateManager<S, M>): S;
73
+ declare function useEphemeralUserData<E>(ephemeralUserData: EphemeralUserData<E>, options?: {
74
+ throttle?: number;
75
+ }): {
76
+ data: Record<string, E | undefined>;
77
+ metadata: Record<string, _noya_app_state_manager.EphemeralUserMetadata | undefined>;
78
+ currentUserId: string | undefined;
79
+ };
80
+ type UseMultiplayerStateOptions<S, M = void, E = void> = MultiplayerStateManagerOptions<S, M> & {
81
+ sync?: (options: Pick<SyncAdapterOptions<S, M, E>, "ms" | "ephemeralUserData" | "assetManager">) => void;
82
+ trackConnectionEvents?: boolean;
83
+ trackTasks?: boolean;
84
+ inspector?: boolean | StateInspectorOptions;
85
+ };
86
+ declare function useMultiplayerState<S, M = void, E = void>(initialState: S | (() => S), options?: UseMultiplayerStateOptions<S, M, E>): readonly [S, (...args: M extends {} ? [metadata: M, action: S | ((prevState: S) => S)] : [action: S | ((prevState: S) => S)]) => void, {
87
+ tasks: Task[];
88
+ userId: string | undefined;
89
+ connectionEvents: ConnectionEvent<S>[] | undefined;
90
+ connectedUsers: MultiplayerUser[];
91
+ ephemeralUserData: EphemeralUserData<E>;
92
+ multiplayerStateManager: MultiplayerStateManager<S, M>;
93
+ assets: _noya_app_state_manager.Asset[];
94
+ assetManager: AssetManager;
95
+ createAsset: (options: _noya_app_state_manager.CreateAssetOptions | Uint8Array | File | Blob) => Promise<_noya_app_state_manager.Asset>;
96
+ deleteAsset: (id: string) => Promise<void>;
97
+ }];
98
+
99
+ type AppViewType = "editable" | "readOnly" | "preview";
100
+ type AppTheme = "light" | "dark";
101
+ type AppData<State> = {
102
+ multiplayerUrl?: string;
103
+ theme: AppTheme;
104
+ viewType: AppViewType;
105
+ initialState: State;
106
+ inspector: boolean | StateInspectorOptions;
107
+ };
108
+ declare function createDefaultAppData<State>(initialState: State): AppData<State>;
109
+ type GetAppDataOptions = {
110
+ window?: {
111
+ location: {
112
+ hash: string;
113
+ };
114
+ };
115
+ overrideExistingState?: boolean;
116
+ };
117
+ declare function enforceSchema<State>(initialState: State, schema?: TSchema): State;
118
+ declare function parseAppDataParameter(options?: GetAppDataOptions): AppData<unknown> | null;
119
+ declare function getAppData<State>(initialState: State | (() => State), schema?: TSchema, options?: GetAppDataOptions): AppData<State>;
120
+ type IframeToHostMessage = {
121
+ type: "application.setMenuItems";
122
+ payload: {
123
+ menuItems: any[];
124
+ };
125
+ } | {
126
+ type: "multiplayer.setUsers";
127
+ payload: {
128
+ users: any[];
129
+ };
130
+ };
131
+ type HostToIframeMessage = {
132
+ type: "application.selectMenuItem";
133
+ payload: {
134
+ value: string;
135
+ };
136
+ };
137
+ declare function useBroadcastConnectedUsers(connectedUsers: MultiplayerUser[]): void;
138
+ declare function useBroadcastMenuItems<T>(menuItems: any[], handleSelectMenuItem: (value: T) => void): void;
139
+ declare function isEmbeddedApp(): boolean;
140
+ type UseNoyaStateOptions<S, M = void, E = void> = UseMultiplayerStateOptions<S, M, E> & {
141
+ offlineStorageKey?: string;
142
+ };
143
+ type UseNoyaStateResult<S, M = void, E = void> = [
144
+ ReturnType<typeof useMultiplayerState<S, M, E>>[0],
145
+ ReturnType<typeof useMultiplayerState<S, M, E>>[1],
146
+ ReturnType<typeof useMultiplayerState<S, M, E>>[2] & {
147
+ theme: AppTheme;
148
+ viewType: AppViewType;
149
+ }
150
+ ];
151
+ /**
152
+ * Sync data locally or to a multiplayer server.
153
+ *
154
+ * There are 3 ways to use this hook:
155
+ *
156
+ * 1. Pass an initial state: `useNoyaState(initialState)`
157
+ * 2. Pass an initial state and a schema: `useNoyaState(initialState, { schema })`
158
+ * 3. Pass only a schema, and a conforming initial state is created automatically: `useNoyaState({ schema })`
159
+ */
160
+ declare function useNoyaState<S, M = void, E = void, O extends UseNoyaStateOptions<S, M, E> = UseNoyaStateOptions<S, M, E>>(...args: [
161
+ initialState: O["schema"] extends TSchema ? Static<O["schema"]> | (() => Static<O["schema"]>) : S | (() => S),
162
+ options?: O
163
+ ] | [options: O & {
164
+ schema: TSchema;
165
+ }]): UseNoyaStateResult<O["schema"] extends TSchema ? Static<O["schema"]> : S, M, E>;
166
+
167
+ interface NoyaStateProviderProps<S, M, E> extends Omit<UseNoyaStateOptions<S, M, E>, "schema" | "mergeHistoryEntries"> {
168
+ children: React.ReactNode;
169
+ initialState?: S;
170
+ }
171
+ declare function useAssets(): _noya_app_state_manager.Asset[];
172
+ declare function useAsset(id: string | undefined): _noya_app_state_manager.Asset | undefined;
173
+ declare function useAssetManager(): {
174
+ create: (options: _noya_app_state_manager.CreateAssetOptions | Uint8Array | File | Blob) => Promise<_noya_app_state_manager.Asset>;
175
+ delete: (id: string) => Promise<void>;
176
+ };
177
+ declare function createNoyaContext<Schema extends TSchema, M = void, E = void>({ schema, mergeHistoryEntries, }: {
178
+ schema: Schema;
179
+ mergeHistoryEntries?: StateManagerOptions<Static<Schema>, M>["mergeHistoryEntries"];
180
+ }): {
181
+ Context: React.Context<{
182
+ ms: MultiplayerStateManager<Static<Schema>, M>;
183
+ assetManager: AssetManager;
184
+ } | undefined>;
185
+ Provider: ({ children, initialState, ...options }: NoyaStateProviderProps<Static<Schema>, M, E>) => React.JSX.Element;
186
+ useValue: {
187
+ (): Static<Schema>;
188
+ <A>(selector: (value: Static<Schema>) => A, options?: Pick<ObservableOptions, "isEqual">): A;
189
+ <P extends PathKey[] | string>(path: P): GetAtPath<Static<Schema>, P>;
190
+ };
191
+ useSetValue: {
192
+ (): M extends {} ? (metadata: M, value: SetStateAction<Static<Schema>>) => void : (value: SetStateAction<Static<Schema>>) => void;
193
+ <P extends PathKey[] | string>(path: P): M extends {} ? (metadata: M, value: SetStateAction<GetAtPath<Static<Schema>, P>>) => void : (value: SetStateAction<GetAtPath<Static<Schema>, P>>) => void;
194
+ };
195
+ useValueState: {
196
+ (): [Static<Schema>, M extends {} ? (metadata: M, value: SetStateAction<Static<Schema>>) => void : (value: SetStateAction<Static<Schema>>) => void];
197
+ <A>(selector: (value: Static<Schema>) => A, options?: ObservableOptions): [A, M extends {} ? (metadata: M, value: SetStateAction<Static<Schema>>) => void : (value: SetStateAction<Static<Schema>>) => void];
198
+ <P extends PathKey[] | string>(path?: P): [GetAtPath<Static<Schema>, P>, M extends {} ? (metadata: M, value: SetStateAction<GetAtPath<Static<Schema>, P>>) => void : (value: SetStateAction<GetAtPath<Static<Schema>, P>>) => void];
199
+ };
200
+ useStateManager: () => MultiplayerStateManager<Static<Schema>, M>;
201
+ };
202
+
203
+ declare function useObservable<T>(observable: Observable<T>): T;
204
+ declare function useObservable<T, P extends PathKey[] | string>(observable: Observable<T>, path: P): GetAtPath<T, P>;
205
+
206
+ export { type AppData, type AppTheme, type AppViewType, type GetAppDataOptions, type HostToIframeMessage, type IframeToHostMessage, StateInspector, type StateInspectorAnchor, type StateInspectorOptions, type UseMultiplayerStateOptions, type UseNoyaStateOptions, type UseNoyaStateResult, UserNameTag, UserPointer, UserPointerContainer, type UserPointerData, UserPointerIcon, type UserPointerProps, UserPointersOverlay, type UserPointersOverlayProps, createDefaultAppData, createNoyaContext, enforceSchema, getAppData, getAvatarInitials, getAvatarStyle, isEmbeddedApp, parseAppDataParameter, useAsset, useAssetManager, useAssets, useBroadcastConnectedUsers, useBroadcastMenuItems, useEphemeralUserData, useManagedHistory, useManagedState, useMultiplayerState, useNoyaState, useObservable, useSyncMultiplayerStateManager, useSyncStateManager };
package/dist/index.d.ts CHANGED
@@ -203,4 +203,4 @@ declare function createNoyaContext<Schema extends TSchema, M = void, E = void>({
203
203
  declare function useObservable<T>(observable: Observable<T>): T;
204
204
  declare function useObservable<T, P extends PathKey[] | string>(observable: Observable<T>, path: P): GetAtPath<T, P>;
205
205
 
206
- export { AppData, AppTheme, AppViewType, GetAppDataOptions, HostToIframeMessage, IframeToHostMessage, StateInspector, StateInspectorAnchor, StateInspectorOptions, UseMultiplayerStateOptions, UseNoyaStateOptions, UseNoyaStateResult, UserNameTag, UserPointer, UserPointerContainer, UserPointerData, UserPointerIcon, UserPointerProps, UserPointersOverlay, UserPointersOverlayProps, createDefaultAppData, createNoyaContext, enforceSchema, getAppData, getAvatarInitials, getAvatarStyle, isEmbeddedApp, parseAppDataParameter, useAsset, useAssetManager, useAssets, useBroadcastConnectedUsers, useBroadcastMenuItems, useEphemeralUserData, useManagedHistory, useManagedState, useMultiplayerState, useNoyaState, useObservable, useSyncMultiplayerStateManager, useSyncStateManager };
206
+ export { type AppData, type AppTheme, type AppViewType, type GetAppDataOptions, type HostToIframeMessage, type IframeToHostMessage, StateInspector, type StateInspectorAnchor, type StateInspectorOptions, type UseMultiplayerStateOptions, type UseNoyaStateOptions, type UseNoyaStateResult, UserNameTag, UserPointer, UserPointerContainer, type UserPointerData, UserPointerIcon, type UserPointerProps, UserPointersOverlay, type UserPointersOverlayProps, createDefaultAppData, createNoyaContext, enforceSchema, getAppData, getAvatarInitials, getAvatarStyle, isEmbeddedApp, parseAppDataParameter, useAsset, useAssetManager, useAssets, useBroadcastConnectedUsers, useBroadcastMenuItems, useEphemeralUserData, useManagedHistory, useManagedState, useMultiplayerState, useNoyaState, useObservable, useSyncMultiplayerStateManager, useSyncStateManager };
package/dist/index.js CHANGED
@@ -139,16 +139,14 @@ var UserPointer_ = function UserPointer({
139
139
  const [, setForceUpdate] = (0, import_react2.useState)(0);
140
140
  const updatedAt = metadata?.updatedAt ?? 0;
141
141
  (0, import_react2.useEffect)(() => {
142
- if (!shouldShow(hideAfter, updatedAt))
143
- return;
142
+ if (!shouldShow(hideAfter, updatedAt)) return;
144
143
  const timeoutId = setTimeout(() => {
145
144
  setForceUpdate((prev) => prev + 1);
146
145
  }, hideAfter);
147
146
  return () => clearTimeout(timeoutId);
148
147
  }, [hideAfter, updatedAt]);
149
148
  const show = shouldShow(hideAfter, updatedAt);
150
- if (!data || !data.pointer)
151
- return null;
149
+ if (!data || !data.pointer) return null;
152
150
  return /* @__PURE__ */ import_react2.default.createElement(UserPointerContainer, { key: user.id, point: data.pointer, show }, /* @__PURE__ */ import_react2.default.createElement(UserPointerIcon, { color: avatarStyle.backgroundColor }), /* @__PURE__ */ import_react2.default.createElement(UserNameTag, { background: avatarStyle.backgroundColor }, user.name));
153
151
  };
154
152
  var UserPointer2 = (0, import_react2.memo)(UserPointer_);
@@ -204,8 +202,7 @@ var UserNameTag = (0, import_react2.memo)(function UserNameTag2({
204
202
  var UserPointersOverlay = (0, import_react2.memo)(function UserPointers({ connectedUsers, ephemeralUserData }) {
205
203
  const currentUserId = useObservable(ephemeralUserData.currentUserId$);
206
204
  return /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, connectedUsers.map((user) => {
207
- if (user.id === currentUserId)
208
- return null;
205
+ if (user.id === currentUserId) return null;
209
206
  return /* @__PURE__ */ import_react2.default.createElement(
210
207
  UserPointer2,
211
208
  {
@@ -622,8 +619,7 @@ var StateInspector = (0, import_react5.memo)(function StateInspector2({
622
619
  }
623
620
  }, [historySnapshot]);
624
621
  (0, import_react5.useEffect)(() => {
625
- if (!historyContainerRef.current)
626
- return;
622
+ if (!historyContainerRef.current) return;
627
623
  const historyEntry = historyContainerRef.current.querySelector(
628
624
  `#${HISTORY_ELEMENT_PREFIX}${historySnapshot.historyIndex}`
629
625
  );
@@ -655,8 +651,7 @@ var StateInspector = (0, import_react5.memo)(function StateInspector2({
655
651
  zIndex: 9999,
656
652
  lineHeight: "13px"
657
653
  };
658
- if (!didMount)
659
- return null;
654
+ if (!didMount) return null;
660
655
  if (!showInspector) {
661
656
  return /* @__PURE__ */ import_react5.default.createElement(
662
657
  "div",
@@ -1134,8 +1129,7 @@ function pathToString(extendedPath) {
1134
1129
  ).join("");
1135
1130
  }
1136
1131
  function ellipsis(str, maxLength, position = "end") {
1137
- if (str.length <= maxLength)
1138
- return str;
1132
+ if (str.length <= maxLength) return str;
1139
1133
  switch (position) {
1140
1134
  case "start":
1141
1135
  return `\u2026${str.slice(str.length - maxLength)}`;
@@ -1208,8 +1202,7 @@ function useStateInspector({
1208
1202
  }) {
1209
1203
  const [root, setRoot] = import_react6.default.useState(null);
1210
1204
  (0, import_react6.useLayoutEffect)(() => {
1211
- if (disabled)
1212
- return;
1205
+ if (disabled) return;
1213
1206
  const { host, container } = createShadowRootElement();
1214
1207
  const root2 = (0, import_client.createRoot)(container);
1215
1208
  document.body.appendChild(host);
@@ -1219,8 +1212,7 @@ function useStateInspector({
1219
1212
  };
1220
1213
  }, [disabled]);
1221
1214
  (0, import_react6.useLayoutEffect)(() => {
1222
- if (!root)
1223
- return;
1215
+ if (!root) return;
1224
1216
  root.render(
1225
1217
  /* @__PURE__ */ import_react6.default.createElement(
1226
1218
  StateInspector,
@@ -1327,8 +1319,7 @@ function throttle(fn, ms) {
1327
1319
  return (...args) => {
1328
1320
  const now = Date.now();
1329
1321
  if (now - lastCall < ms) {
1330
- if (timeoutId)
1331
- clearTimeout(timeoutId);
1322
+ if (timeoutId) clearTimeout(timeoutId);
1332
1323
  timeoutId = setTimeout(
1333
1324
  () => {
1334
1325
  lastCall = now;
@@ -1467,8 +1458,7 @@ function useMultiplayerState(initialState, options) {
1467
1458
  });
1468
1459
  }, [multiplayerStateManager]);
1469
1460
  (0, import_react7.useEffect)(() => {
1470
- if (!unrecoverableError)
1471
- return;
1461
+ if (!unrecoverableError) return;
1472
1462
  const el = document.createElement("div");
1473
1463
  el.id = "noya-multiplayer-react-error-overlay";
1474
1464
  document.body.appendChild(el);
@@ -1516,13 +1506,11 @@ function enforceSchema(initialState, schema) {
1516
1506
  function parseAppDataParameter(options) {
1517
1507
  const window2 = options?.window ?? globalThis;
1518
1508
  const location = window2.location;
1519
- if (!location)
1520
- return null;
1509
+ if (!location) return null;
1521
1510
  const urlHash = (location.hash || "").replace(/^#/, "");
1522
1511
  const params = new URLSearchParams(urlHash);
1523
1512
  const data = params.get("data");
1524
- if (!data)
1525
- return null;
1513
+ if (!data) return null;
1526
1514
  try {
1527
1515
  return JSON.parse(data);
1528
1516
  } catch (e) {
@@ -1540,8 +1528,7 @@ function getAppData(initialState, schema, options) {
1540
1528
  }
1541
1529
  function useBroadcastConnectedUsers(connectedUsers) {
1542
1530
  (0, import_react8.useEffect)(() => {
1543
- if (!isEmbeddedApp())
1544
- return;
1531
+ if (!isEmbeddedApp()) return;
1545
1532
  const message = {
1546
1533
  type: "multiplayer.setUsers",
1547
1534
  payload: { users: connectedUsers }
@@ -1551,8 +1538,7 @@ function useBroadcastConnectedUsers(connectedUsers) {
1551
1538
  }
1552
1539
  function useBroadcastMenuItems(menuItems, handleSelectMenuItem) {
1553
1540
  (0, import_react8.useEffect)(() => {
1554
- if (!isEmbeddedApp())
1555
- return;
1541
+ if (!isEmbeddedApp()) return;
1556
1542
  const message = {
1557
1543
  type: "application.setMenuItems",
1558
1544
  payload: { menuItems }