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

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",
@@ -725,14 +720,14 @@ var StateInspector = (0, import_react5.memo)(function StateInspector2({
725
720
  src: user.image,
726
721
  alt: user.name,
727
722
  style: {
728
- width: "12px",
729
- height: "12px",
723
+ width: "13px",
724
+ height: "13px",
730
725
  borderRadius: "50%",
731
726
  marginRight: "4px",
732
727
  display: "inline-block",
733
728
  position: "relative",
734
- top: "-1px",
735
- background: solidBorderColor
729
+ background: solidBorderColor,
730
+ verticalAlign: "middle"
736
731
  }
737
732
  }
738
733
  ),
@@ -909,7 +904,8 @@ var StateInspector = (0, import_react5.memo)(function StateInspector2({
909
904
  style: {
910
905
  fontSize: "11px",
911
906
  display: "flex",
912
- flexWrap: "wrap"
907
+ flexWrap: "wrap",
908
+ margin: 0
913
909
  }
914
910
  },
915
911
  entry.metadata.name
@@ -921,7 +917,8 @@ var StateInspector = (0, import_react5.memo)(function StateInspector2({
921
917
  style: {
922
918
  fontSize: "11px",
923
919
  display: "flex",
924
- flexWrap: "wrap"
920
+ flexWrap: "wrap",
921
+ margin: 0
925
922
  }
926
923
  },
927
924
  patch.op === "add" && /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)), " = ", /* @__PURE__ */ import_react5.default.createElement(import_react_inspector.ObjectInspector, { data: patch.value, theme })),
@@ -1134,8 +1131,7 @@ function pathToString(extendedPath) {
1134
1131
  ).join("");
1135
1132
  }
1136
1133
  function ellipsis(str, maxLength, position = "end") {
1137
- if (str.length <= maxLength)
1138
- return str;
1134
+ if (str.length <= maxLength) return str;
1139
1135
  switch (position) {
1140
1136
  case "start":
1141
1137
  return `\u2026${str.slice(str.length - maxLength)}`;
@@ -1208,8 +1204,7 @@ function useStateInspector({
1208
1204
  }) {
1209
1205
  const [root, setRoot] = import_react6.default.useState(null);
1210
1206
  (0, import_react6.useLayoutEffect)(() => {
1211
- if (disabled)
1212
- return;
1207
+ if (disabled) return;
1213
1208
  const { host, container } = createShadowRootElement();
1214
1209
  const root2 = (0, import_client.createRoot)(container);
1215
1210
  document.body.appendChild(host);
@@ -1219,8 +1214,7 @@ function useStateInspector({
1219
1214
  };
1220
1215
  }, [disabled]);
1221
1216
  (0, import_react6.useLayoutEffect)(() => {
1222
- if (!root)
1223
- return;
1217
+ if (!root) return;
1224
1218
  root.render(
1225
1219
  /* @__PURE__ */ import_react6.default.createElement(
1226
1220
  StateInspector,
@@ -1327,8 +1321,7 @@ function throttle(fn, ms) {
1327
1321
  return (...args) => {
1328
1322
  const now = Date.now();
1329
1323
  if (now - lastCall < ms) {
1330
- if (timeoutId)
1331
- clearTimeout(timeoutId);
1324
+ if (timeoutId) clearTimeout(timeoutId);
1332
1325
  timeoutId = setTimeout(
1333
1326
  () => {
1334
1327
  lastCall = now;
@@ -1467,8 +1460,7 @@ function useMultiplayerState(initialState, options) {
1467
1460
  });
1468
1461
  }, [multiplayerStateManager]);
1469
1462
  (0, import_react7.useEffect)(() => {
1470
- if (!unrecoverableError)
1471
- return;
1463
+ if (!unrecoverableError) return;
1472
1464
  const el = document.createElement("div");
1473
1465
  el.id = "noya-multiplayer-react-error-overlay";
1474
1466
  document.body.appendChild(el);
@@ -1516,13 +1508,11 @@ function enforceSchema(initialState, schema) {
1516
1508
  function parseAppDataParameter(options) {
1517
1509
  const window2 = options?.window ?? globalThis;
1518
1510
  const location = window2.location;
1519
- if (!location)
1520
- return null;
1511
+ if (!location) return null;
1521
1512
  const urlHash = (location.hash || "").replace(/^#/, "");
1522
1513
  const params = new URLSearchParams(urlHash);
1523
1514
  const data = params.get("data");
1524
- if (!data)
1525
- return null;
1515
+ if (!data) return null;
1526
1516
  try {
1527
1517
  return JSON.parse(data);
1528
1518
  } catch (e) {
@@ -1540,8 +1530,7 @@ function getAppData(initialState, schema, options) {
1540
1530
  }
1541
1531
  function useBroadcastConnectedUsers(connectedUsers) {
1542
1532
  (0, import_react8.useEffect)(() => {
1543
- if (!isEmbeddedApp())
1544
- return;
1533
+ if (!isEmbeddedApp()) return;
1545
1534
  const message = {
1546
1535
  type: "multiplayer.setUsers",
1547
1536
  payload: { users: connectedUsers }
@@ -1551,8 +1540,7 @@ function useBroadcastConnectedUsers(connectedUsers) {
1551
1540
  }
1552
1541
  function useBroadcastMenuItems(menuItems, handleSelectMenuItem) {
1553
1542
  (0, import_react8.useEffect)(() => {
1554
- if (!isEmbeddedApp())
1555
- return;
1543
+ if (!isEmbeddedApp()) return;
1556
1544
  const message = {
1557
1545
  type: "application.setMenuItems",
1558
1546
  payload: { menuItems }