@xipkg/calls-store 0.0.12 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,34 @@
1
1
  import * as zustand_middleware from 'zustand/middleware';
2
2
  import * as zustand from 'zustand';
3
3
  import { LiveKitRoomProps } from '@livekit/components-react';
4
- import { LocalUserChoices as LocalUserChoices$1 } from '@livekit/components-core';
5
- import { VideoQuality } from 'livekit-client';
4
+ import { Track, VideoQuality } from 'livekit-client';
5
+ import { TrackReferenceOrPlaceholder, LocalUserChoices as LocalUserChoices$1 } from '@livekit/components-core';
6
6
  import { NoiseCancellationMode } from '@xipkg/calls-types';
7
7
 
8
+ /** Стабильный идентификатор участника для закрепления в рамках кабинета */
9
+ type PinnedParticipantT = {
10
+ userId: string;
11
+ source: Track.Source;
12
+ };
13
+ /** @deprecated Используйте PinnedParticipantT */
14
+ type PinnedTrackT = PinnedParticipantT;
15
+ declare function getParticipantUserId(participant: {
16
+ identity: string;
17
+ metadata?: string;
18
+ }): string;
19
+ declare function toPinnedParticipant(track: TrackReferenceOrPlaceholder): PinnedParticipantT;
20
+ /** @deprecated Используйте toPinnedParticipant */
21
+ declare const toPinnedTrack: typeof toPinnedParticipant;
22
+ declare function matchesPinnedParticipant(track: TrackReferenceOrPlaceholder, pinned: PinnedParticipantT): boolean;
23
+ /** @deprecated Используйте matchesPinnedParticipant */
24
+ declare const matchesPinnedTrack: typeof matchesPinnedParticipant;
25
+ declare function findPinnedTrackRef(tracks: TrackReferenceOrPlaceholder[], pinned: PinnedParticipantT): TrackReferenceOrPlaceholder | undefined;
26
+ /** Перемещает закреплённых участников в начало списка (порядок pin сохраняется) */
27
+ declare function applyPinsFirst(tracks: TrackReferenceOrPlaceholder[], pins: PinnedParticipantT[]): TrackReferenceOrPlaceholder[];
28
+ declare function applyPinFirst(tracks: TrackReferenceOrPlaceholder[], pinned: PinnedParticipantT | null): TrackReferenceOrPlaceholder[];
29
+ /** Участник для главной плитки focus-сетки (без учёта локального pin) */
30
+ declare function pickDefaultFocusTrack(tracks: TrackReferenceOrPlaceholder[]): TrackReferenceOrPlaceholder | undefined;
31
+
8
32
  type RaisedHandT = {
9
33
  participantId: string;
10
34
  participantName: string;
@@ -36,7 +60,13 @@ type useCallStoreT = {
36
60
  token: string | undefined;
37
61
  raisedHands: RaisedHandT[];
38
62
  isHandRaised: boolean;
63
+ /** Закреплённые участники по кабинетам (userId + source), порядок = приоритет */
64
+ pinnedByClassroom: Record<string, PinnedParticipantT[]>;
39
65
  updateStore: (type: keyof useCallStoreT, value: any) => void;
66
+ togglePinnedParticipant: (pin: PinnedParticipantT, classroomId: string) => void;
67
+ removePinnedParticipant: (pin: PinnedParticipantT, classroomId: string) => void;
68
+ getPinsForClassroom: (classroomId: string) => PinnedParticipantT[];
69
+ isParticipantPinned: (pin: PinnedParticipantT, classroomId: string) => boolean;
40
70
  addRaisedHand: (hand: RaisedHandT) => void;
41
71
  removeRaisedHand: (participantId: string) => void;
42
72
  toggleHandRaised: () => void;
@@ -56,6 +86,7 @@ declare const useCallStore: zustand.UseBoundStore<Omit<zustand.StoreApi<useCallS
56
86
  preferredFocusLayout: "horizontal" | "vertical";
57
87
  activeCorner: CornerT;
58
88
  compactViewMode: CompactViewModeT;
89
+ pinnedByClassroom: Record<string, PinnedParticipantT[]>;
59
90
  }>>) => void;
60
91
  clearStorage: () => void;
61
92
  rehydrate: () => Promise<void> | void;
@@ -73,6 +104,7 @@ declare const useCallStore: zustand.UseBoundStore<Omit<zustand.StoreApi<useCallS
73
104
  preferredFocusLayout: "horizontal" | "vertical";
74
105
  activeCorner: CornerT;
75
106
  compactViewMode: CompactViewModeT;
107
+ pinnedByClassroom: Record<string, PinnedParticipantT[]>;
76
108
  }>>;
77
109
  };
78
110
  }>;
@@ -220,4 +252,4 @@ interface FocusModeState {
220
252
  }
221
253
  declare const useFocusModeStore: zustand.UseBoundStore<zustand.StoreApi<FocusModeState>>;
222
254
 
223
- export { type CompactViewModeT, type CornerT, type VideoResolution, closePermissionsDialog, openPermissionsDialog, useCallStore, useFeaturesStore, useFocusModeStore, usePermissionsStore, useSoundEffectsStore, useUserChoicesStore };
255
+ export { type CompactViewModeT, type CornerT, type PinnedParticipantT, type PinnedTrackT, type VideoResolution, applyPinFirst, applyPinsFirst, closePermissionsDialog, findPinnedTrackRef, getParticipantUserId, matchesPinnedParticipant, matchesPinnedTrack, openPermissionsDialog, pickDefaultFocusTrack, toPinnedParticipant, toPinnedTrack, useCallStore, useFeaturesStore, useFocusModeStore, usePermissionsStore, useSoundEffectsStore, useUserChoicesStore };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { create } from 'zustand';
2
2
  import { persist } from 'zustand/middleware';
3
3
  import { saveUserChoices, loadUserChoices } from '@livekit/components-core';
4
- import { VideoQuality } from 'livekit-client';
4
+ import { VideoQuality, Track } from 'livekit-client';
5
5
 
6
6
  // src/store/callStore.ts
7
7
  var useCallStore = create()(
@@ -31,7 +31,45 @@ var useCallStore = create()(
31
31
  // Поднятые руки
32
32
  raisedHands: [],
33
33
  isHandRaised: false,
34
+ pinnedByClassroom: {},
34
35
  updateStore: (type, value) => set({ [type]: value }),
36
+ togglePinnedParticipant: (pin, classroomId) => set((state) => {
37
+ const current = state.pinnedByClassroom[classroomId] ?? [];
38
+ const existingIndex = current.findIndex(
39
+ (item) => item.userId === pin.userId && item.source === pin.source
40
+ );
41
+ const next = { ...state.pinnedByClassroom };
42
+ if (existingIndex >= 0) {
43
+ const updated = current.filter((_, index) => index !== existingIndex);
44
+ if (updated.length) {
45
+ next[classroomId] = updated;
46
+ } else {
47
+ delete next[classroomId];
48
+ }
49
+ } else {
50
+ next[classroomId] = [...current, pin];
51
+ }
52
+ return { pinnedByClassroom: next };
53
+ }),
54
+ removePinnedParticipant: (pin, classroomId) => set((state) => {
55
+ const current = state.pinnedByClassroom[classroomId];
56
+ if (!current?.length) return state;
57
+ const updated = current.filter(
58
+ (item) => !(item.userId === pin.userId && item.source === pin.source)
59
+ );
60
+ const next = { ...state.pinnedByClassroom };
61
+ if (updated.length) {
62
+ next[classroomId] = updated;
63
+ } else {
64
+ delete next[classroomId];
65
+ }
66
+ return { pinnedByClassroom: next };
67
+ }),
68
+ getPinsForClassroom: (classroomId) => get().pinnedByClassroom[classroomId] ?? [],
69
+ isParticipantPinned: (pin, classroomId) => {
70
+ const pins = get().pinnedByClassroom[classroomId] ?? [];
71
+ return pins.some((item) => item.userId === pin.userId && item.source === pin.source);
72
+ },
35
73
  // Поднятые руки
36
74
  addRaisedHand: (hand) => set((state) => {
37
75
  const existingHand = state.raisedHands.find(
@@ -58,7 +96,7 @@ var useCallStore = create()(
58
96
  }),
59
97
  {
60
98
  name: "call-store",
61
- version: 4,
99
+ version: 5,
62
100
  migrate: (persisted, version) => {
63
101
  const state = persisted;
64
102
  if (version < 2) {
@@ -75,6 +113,10 @@ var useCallStore = create()(
75
113
  const mode = state.compactViewMode;
76
114
  state.compactViewMode = mode === "basic" || mode === "expanded" || mode === "audio" ? mode : "basic";
77
115
  }
116
+ if (version < 5) {
117
+ state.pinnedByClassroom = typeof state.pinnedByClassroom === "object" && state.pinnedByClassroom !== null ? state.pinnedByClassroom : {};
118
+ delete state.pinnedTrack;
119
+ }
78
120
  return state;
79
121
  },
80
122
  partialize: (state) => ({
@@ -87,7 +129,8 @@ var useCallStore = create()(
87
129
  carouselType: state.carouselType,
88
130
  preferredFocusLayout: state.preferredFocusLayout,
89
131
  activeCorner: state.activeCorner,
90
- compactViewMode: state.compactViewMode
132
+ compactViewMode: state.compactViewMode,
133
+ pinnedByClassroom: state.pinnedByClassroom
91
134
  })
92
135
  }
93
136
  )
@@ -160,6 +203,55 @@ var useUserChoicesStore = create()(
160
203
  }
161
204
  )
162
205
  );
206
+ function getParticipantUserId(participant) {
207
+ if (participant.metadata) {
208
+ try {
209
+ const meta = JSON.parse(participant.metadata);
210
+ if (meta?.user_id != null) return String(meta.user_id);
211
+ if (meta?.id != null) return String(meta.id);
212
+ } catch {
213
+ }
214
+ }
215
+ return participant.identity;
216
+ }
217
+ function toPinnedParticipant(track) {
218
+ return {
219
+ userId: getParticipantUserId(track.participant),
220
+ source: track.source ?? Track.Source.Camera
221
+ };
222
+ }
223
+ var toPinnedTrack = toPinnedParticipant;
224
+ function matchesPinnedParticipant(track, pinned) {
225
+ return getParticipantUserId(track.participant) === pinned.userId && track.source === pinned.source;
226
+ }
227
+ var matchesPinnedTrack = matchesPinnedParticipant;
228
+ function findPinnedTrackRef(tracks, pinned) {
229
+ return tracks.find((track) => matchesPinnedParticipant(track, pinned));
230
+ }
231
+ function applyPinsFirst(tracks, pins) {
232
+ if (!pins.length) return tracks;
233
+ const pinnedRefs = [];
234
+ const seen = /* @__PURE__ */ new Set();
235
+ for (const pin of pins) {
236
+ const ref = findPinnedTrackRef(tracks, pin);
237
+ if (ref && !seen.has(ref)) {
238
+ pinnedRefs.push(ref);
239
+ seen.add(ref);
240
+ }
241
+ }
242
+ if (!pinnedRefs.length) return tracks;
243
+ return [...pinnedRefs, ...tracks.filter((track) => !seen.has(track))];
244
+ }
245
+ function applyPinFirst(tracks, pinned) {
246
+ return applyPinsFirst(tracks, pinned ? [pinned] : []);
247
+ }
248
+ function pickDefaultFocusTrack(tracks) {
249
+ const screenShare = tracks.find(
250
+ (track) => track.source === Track.Source.ScreenShare && track.publication?.isSubscribed
251
+ );
252
+ if (screenShare) return screenShare;
253
+ return tracks.find((track) => track.source === Track.Source.Camera);
254
+ }
163
255
  var DEFAULTS = {
164
256
  chatMessage: 0.25,
165
257
  handRaise: 0.25,
@@ -231,6 +323,6 @@ var useFocusModeStore = create((set) => ({
231
323
  toggleFocusMode: () => set((state) => ({ focusMode: !state.focusMode }))
232
324
  }));
233
325
 
234
- export { closePermissionsDialog, openPermissionsDialog, useCallStore, useFeaturesStore, useFocusModeStore, usePermissionsStore, useSoundEffectsStore, useUserChoicesStore };
326
+ export { applyPinFirst, applyPinsFirst, closePermissionsDialog, findPinnedTrackRef, getParticipantUserId, matchesPinnedParticipant, matchesPinnedTrack, openPermissionsDialog, pickDefaultFocusTrack, toPinnedParticipant, toPinnedTrack, useCallStore, useFeaturesStore, useFocusModeStore, usePermissionsStore, useSoundEffectsStore, useUserChoicesStore };
235
327
  //# sourceMappingURL=index.mjs.map
236
328
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/store/callStore.ts","../src/store/permissions.ts","../src/store/userChoices.ts","../src/store/useSoundEffectsStore.ts","../src/store/featuresStore.ts","../src/store/useFocusModeStore.ts"],"names":["create","persist"],"mappings":"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","file":"index.mjs","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { LiveKitRoomProps } from '@livekit/components-react';\nimport { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\n\ntype RaisedHandT = {\n participantId: string;\n participantName: string;\n timestamp: number;\n};\n\nexport type CornerT = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\nexport type CompactViewModeT = 'basic' | 'expanded' | 'audio';\n\ntype useCallStoreT = {\n // разрешение от браузера на использование камеры\n isCameraPermission: boolean | null;\n isMicroPermission: boolean | null;\n // включён ли у пользователя микро\n audioEnabled: boolean;\n videoEnabled: boolean;\n // id-выбранного устройства\n audioDeviceId: ConstrainDOMString | undefined;\n audioOutputDeviceId: ConstrainDOMString | undefined;\n videoDeviceId: ConstrainDOMString | undefined;\n // подключена ли конференция\n connect: LiveKitRoomProps['connect'];\n // началась ли ВКС для пользователя\n isStarted: boolean | undefined;\n // состояние подключения\n isConnecting: boolean;\n\n mode: 'compact' | 'full';\n carouselType: 'grid' | 'horizontal' | 'vertical';\n preferredFocusLayout: 'horizontal' | 'vertical';\n activeCorner: CornerT;\n\n /** Режим вида компакт-ВКС: одна плитка, несколько плиток или только аудио */\n compactViewMode: CompactViewModeT;\n\n // Текущая активная доска (для синхронизации с новыми участниками)\n activeBoardId: string | undefined;\n activeClassroom: string | undefined;\n\n /** Локальный переключатель: пользователь сам ушёл в полную ВКС («Только меня»), комната на сервере остаётся на доске */\n localFullView: boolean;\n\n // токен для конференции\n token: string | undefined;\n\n // Поднятые руки\n raisedHands: RaisedHandT[];\n isHandRaised: boolean;\n\n updateStore: (type: keyof useCallStoreT, value: any) => void;\n addRaisedHand: (hand: RaisedHandT) => void;\n removeRaisedHand: (participantId: string) => void;\n toggleHandRaised: () => void;\n clearAllRaisedHands: () => void;\n isHandRaisedByParticipant: (participantId: string) => boolean;\n};\n\nexport const useCallStore = create<useCallStoreT>()(\n persist(\n (set, get) => ({\n isCameraPermission: null,\n isMicroPermission: null,\n audioEnabled: false,\n videoEnabled: false,\n audioDeviceId: undefined,\n audioOutputDeviceId: undefined,\n videoDeviceId: undefined,\n connect: undefined,\n isStarted: undefined,\n isConnecting: false,\n mode: 'full',\n carouselType: 'grid',\n preferredFocusLayout: 'horizontal',\n activeCorner: 'top-left',\n compactViewMode: 'basic',\n\n // Текущая активная доска\n activeBoardId: undefined,\n activeClassroom: undefined,\n\n localFullView: false,\n\n // токен для конференции\n token: undefined,\n\n // Поднятые руки\n raisedHands: [],\n isHandRaised: false,\n\n updateStore: (type: keyof useCallStoreT, value: any) => set({ [type]: value }),\n\n // Поднятые руки\n addRaisedHand: (hand: RaisedHandT) =>\n set((state) => {\n // Проверяем, есть ли уже рука от этого участника\n const existingHand = state.raisedHands.find(\n (h) => h.participantId === hand.participantId,\n );\n if (existingHand) {\n // Обновляем существующую руку\n return {\n raisedHands: state.raisedHands.map((h) =>\n h.participantId === hand.participantId ? hand : h,\n ),\n };\n }\n // Добавляем новую руку\n return { raisedHands: [...state.raisedHands, hand] };\n }),\n removeRaisedHand: (participantId: string) =>\n set((state) => ({\n raisedHands: state.raisedHands.filter((hand) => hand.participantId !== participantId),\n })),\n toggleHandRaised: () => set((state) => ({ isHandRaised: !state.isHandRaised })),\n clearAllRaisedHands: () => set({ raisedHands: [], isHandRaised: false }),\n isHandRaisedByParticipant: (participantId: string) => {\n const state = get();\n return state.raisedHands.some((hand) => hand.participantId === participantId);\n },\n }),\n {\n name: 'call-store',\n version: 4,\n migrate: (persisted, version) => {\n const state = persisted as Record<string, unknown>;\n if (version < 2) {\n const ct = state.carouselType;\n if (ct === 'focus') {\n state.carouselType = 'horizontal';\n }\n }\n if (version < 3) {\n const ct = state.carouselType;\n state.preferredFocusLayout = ct === 'horizontal' || ct === 'vertical' ? ct : 'horizontal';\n }\n if (version < 4) {\n const mode = state.compactViewMode;\n state.compactViewMode =\n mode === 'basic' || mode === 'expanded' || mode === 'audio' ? mode : 'basic';\n }\n return state as useCallStoreT;\n },\n partialize: (state) => ({\n isCameraPermission: state.isCameraPermission,\n isMicroPermission: state.isMicroPermission,\n audioEnabled: state.audioEnabled,\n videoEnabled: state.videoEnabled,\n audioDeviceId: state.audioDeviceId,\n videoDeviceId: state.videoDeviceId,\n carouselType: state.carouselType,\n preferredFocusLayout: state.preferredFocusLayout,\n activeCorner: state.activeCorner,\n compactViewMode: state.compactViewMode,\n }),\n },\n ),\n);\n","import { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\n\ntype PermissionStateT = undefined | 'granted' | 'prompt' | 'denied' | 'unavailable';\n\ntype BaseStateT = {\n cameraPermission: PermissionStateT;\n microphonePermission: PermissionStateT;\n isLoading: boolean;\n isPermissionDialogOpen: boolean;\n};\n\ntype DerivedStateT = {\n isCameraGranted: boolean;\n isMicrophoneGranted: boolean;\n isCameraDenied: boolean;\n isMicrophoneDenied: boolean;\n isCameraPrompted: boolean;\n isMicrophonePrompted: boolean;\n};\n\ntype State = BaseStateT & DerivedStateT;\n\nexport const usePermissionsStore = create<State>()(\n persist(\n (_, get) => ({\n cameraPermission: undefined,\n microphonePermission: undefined,\n isLoading: true,\n isPermissionDialogOpen: false,\n\n get isCameraGranted() {\n return get().cameraPermission === 'granted';\n },\n get isMicrophoneGranted() {\n return get().microphonePermission === 'granted';\n },\n get isCameraDenied() {\n return get().cameraPermission === 'denied';\n },\n get isMicrophoneDenied() {\n return get().microphonePermission === 'denied';\n },\n get isCameraPrompted() {\n return get().cameraPermission === 'prompt';\n },\n get isMicrophonePrompted() {\n return get().microphonePermission === 'prompt';\n },\n }),\n {\n name: 'permissions-storage',\n partialize: (state) => ({\n cameraPermission: state.cameraPermission,\n microphonePermission: state.microphonePermission,\n }),\n },\n ),\n);\n\nexport const openPermissionsDialog = () => {\n usePermissionsStore.setState({ isPermissionDialogOpen: true });\n};\n\nexport const closePermissionsDialog = () => {\n usePermissionsStore.setState({ isPermissionDialogOpen: false });\n};\n","import { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\nimport {\n loadUserChoices,\n saveUserChoices,\n LocalUserChoices as LocalUserChoicesLK,\n} from '@livekit/components-core';\nimport { VideoQuality } from 'livekit-client';\nimport type { NoiseCancellationMode } from '@xipkg/calls-types';\n\nexport type VideoResolution = 'h720' | 'h360' | 'h180';\n\nexport type LocalUserChoices = LocalUserChoicesLK & {\n noiseReductionEnabled?: boolean;\n blurEnabled?: boolean;\n audioOutputDeviceId?: string;\n videoPublishResolution?: VideoResolution;\n videoSubscribeQuality?: VideoQuality;\n /** Включено ли шумоподавление (любой режим кроме off). */\n noiseCancellationEnabled?: boolean;\n /** Режим шумоподавления: off | webrtc | krisp. */\n noiseCancellationMode?: NoiseCancellationMode;\n};\n\nfunction getUserChoicesState(): LocalUserChoices {\n return {\n noiseReductionEnabled: false,\n blurEnabled: false,\n audioOutputDeviceId: 'default',\n videoPublishResolution: 'h720',\n videoSubscribeQuality: VideoQuality.HIGH,\n noiseCancellationEnabled: false,\n noiseCancellationMode: 'webrtc',\n ...loadUserChoices(),\n };\n}\n\nexport const useUserChoicesStore = create<LocalUserChoices>()(\n persist(\n () => ({\n ...getUserChoicesState(),\n }),\n {\n name: 'user-choices-storage',\n onRehydrateStorage: () => (state) => {\n if (state) {\n saveUserChoices(state, false);\n }\n },\n },\n ),\n);\n","import { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\n\ntype SoundKey =\n | 'chatMessage'\n | 'handRaise'\n | 'userJoin'\n | 'userLeft'\n | 'boardTimerEnd'\n | 'boardTimerWarn';\n\ninterface SoundEffectsState {\n chatMessageVolume: number;\n handRaiseVolume: number;\n userJoinVolume: number;\n userLeftVolume: number;\n boardTimerEndVolume: number;\n boardTimerWarnVolume: number;\n\n setSoundVolume: (key: SoundKey, volume: number) => void;\n getEffectiveVolume: (key: SoundKey) => number;\n}\n\nconst DEFAULTS: Record<SoundKey, number> = {\n chatMessage: 0.25,\n handRaise: 0.25,\n userJoin: 0.25,\n userLeft: 0.25,\n boardTimerEnd: 1,\n boardTimerWarn: 1,\n};\n\nconst VOLUME_KEY_MAP: Record<SoundKey, keyof SoundEffectsState> = {\n chatMessage: 'chatMessageVolume',\n handRaise: 'handRaiseVolume',\n userJoin: 'userJoinVolume',\n userLeft: 'userLeftVolume',\n boardTimerEnd: 'boardTimerEndVolume',\n boardTimerWarn: 'boardTimerWarnVolume',\n};\n\nconst clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value));\n\nexport const useSoundEffectsStore = create<SoundEffectsState>()(\n persist(\n (set, get) => ({\n chatMessageVolume: DEFAULTS.chatMessage,\n handRaiseVolume: DEFAULTS.handRaise,\n userJoinVolume: DEFAULTS.userJoin,\n userLeftVolume: DEFAULTS.userLeft,\n boardTimerEndVolume: DEFAULTS.boardTimerEnd,\n boardTimerWarnVolume: DEFAULTS.boardTimerWarn,\n\n setSoundVolume: (key, volume) => {\n const min = key === 'boardTimerEnd' ? 0.2 : 0;\n const clamped = clamp(volume, min, 1);\n set({ [VOLUME_KEY_MAP[key]]: clamped });\n },\n\n getEffectiveVolume: (key) => {\n const storeKey = VOLUME_KEY_MAP[key];\n return get()[storeKey] as number;\n },\n }),\n {\n name: 'sound-effects-store',\n version: 1,\n partialize: (state) => ({\n chatMessageVolume: state.chatMessageVolume,\n handRaiseVolume: state.handRaiseVolume,\n userJoinVolume: state.userJoinVolume,\n userLeftVolume: state.userLeftVolume,\n boardTimerEndVolume: state.boardTimerEndVolume,\n boardTimerWarnVolume: state.boardTimerWarnVolume,\n }),\n },\n ),\n);\n\nexport { DEFAULTS as SOUND_DEFAULTS };\nexport type { SoundKey };\n","import { create } from 'zustand';\n\nexport type FeatureKey = 'compactView' | 'whiteboard' | 'raiseHand' | 'chat';\n\ntype FeatureMapT = Record<FeatureKey, boolean>;\n\ntype FeaturesState = {\n features: FeatureMapT;\n\n setFeature: (key: FeatureKey, value: boolean) => void;\n setFeatures: (features: Partial<FeatureMapT>) => void;\n};\n\nconst defaultFeatures: FeatureMapT = {\n compactView: true,\n whiteboard: true,\n raiseHand: true,\n chat: true,\n};\n\nexport const useFeaturesStore = create<FeaturesState>((set) => ({\n features: defaultFeatures,\n\n setFeature: (key, value) =>\n set((state) => ({\n features: { ...state.features, [key]: value },\n })),\n\n setFeatures: (features) =>\n set((state) => ({\n features: { ...state.features, ...features },\n })),\n}));\n","import { create } from 'zustand';\n\ninterface FocusModeState {\n focusMode: boolean;\n setFocusMode: (value: boolean) => void;\n toggleFocusMode: () => void;\n}\n\nexport const useFocusModeStore = create<FocusModeState>((set) => ({\n focusMode: false,\n setFocusMode: (value) => set({ focusMode: value }),\n toggleFocusMode: () => set((state) => ({ focusMode: !state.focusMode })),\n}));\n"]}
1
+ {"version":3,"sources":["../src/store/callStore.ts","../src/store/permissions.ts","../src/store/userChoices.ts","../src/store/pinnedTrack.ts","../src/store/useSoundEffectsStore.ts","../src/store/featuresStore.ts","../src/store/useFocusModeStore.ts"],"names":["create","persist"],"mappings":"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","file":"index.mjs","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { LiveKitRoomProps } from '@livekit/components-react';\nimport { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\nimport type { PinnedParticipantT } from './pinnedTrack';\n\ntype RaisedHandT = {\n participantId: string;\n participantName: string;\n timestamp: number;\n};\n\nexport type CornerT = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\nexport type CompactViewModeT = 'basic' | 'expanded' | 'audio';\n\ntype useCallStoreT = {\n // разрешение от браузера на использование камеры\n isCameraPermission: boolean | null;\n isMicroPermission: boolean | null;\n // включён ли у пользователя микро\n audioEnabled: boolean;\n videoEnabled: boolean;\n // id-выбранного устройства\n audioDeviceId: ConstrainDOMString | undefined;\n audioOutputDeviceId: ConstrainDOMString | undefined;\n videoDeviceId: ConstrainDOMString | undefined;\n // подключена ли конференция\n connect: LiveKitRoomProps['connect'];\n // началась ли ВКС для пользователя\n isStarted: boolean | undefined;\n // состояние подключения\n isConnecting: boolean;\n\n mode: 'compact' | 'full';\n carouselType: 'grid' | 'horizontal' | 'vertical';\n preferredFocusLayout: 'horizontal' | 'vertical';\n activeCorner: CornerT;\n\n /** Режим вида компакт-ВКС: одна плитка, несколько плиток или только аудио */\n compactViewMode: CompactViewModeT;\n\n // Текущая активная доска (для синхронизации с новыми участниками)\n activeBoardId: string | undefined;\n activeClassroom: string | undefined;\n\n /** Локальный переключатель: пользователь сам ушёл в полную ВКС («Только меня»), комната на сервере остаётся на доске */\n localFullView: boolean;\n\n // токен для конференции\n token: string | undefined;\n\n // Поднятые руки\n raisedHands: RaisedHandT[];\n isHandRaised: boolean;\n\n /** Закреплённые участники по кабинетам (userId + source), порядок = приоритет */\n pinnedByClassroom: Record<string, PinnedParticipantT[]>;\n\n updateStore: (type: keyof useCallStoreT, value: any) => void;\n togglePinnedParticipant: (pin: PinnedParticipantT, classroomId: string) => void;\n removePinnedParticipant: (pin: PinnedParticipantT, classroomId: string) => void;\n getPinsForClassroom: (classroomId: string) => PinnedParticipantT[];\n isParticipantPinned: (pin: PinnedParticipantT, classroomId: string) => boolean;\n addRaisedHand: (hand: RaisedHandT) => void;\n removeRaisedHand: (participantId: string) => void;\n toggleHandRaised: () => void;\n clearAllRaisedHands: () => void;\n isHandRaisedByParticipant: (participantId: string) => boolean;\n};\n\nexport const useCallStore = create<useCallStoreT>()(\n persist(\n (set, get) => ({\n isCameraPermission: null,\n isMicroPermission: null,\n audioEnabled: false,\n videoEnabled: false,\n audioDeviceId: undefined,\n audioOutputDeviceId: undefined,\n videoDeviceId: undefined,\n connect: undefined,\n isStarted: undefined,\n isConnecting: false,\n mode: 'full',\n carouselType: 'grid',\n preferredFocusLayout: 'horizontal',\n activeCorner: 'top-left',\n compactViewMode: 'basic',\n\n // Текущая активная доска\n activeBoardId: undefined,\n activeClassroom: undefined,\n\n localFullView: false,\n\n // токен для конференции\n token: undefined,\n\n // Поднятые руки\n raisedHands: [],\n isHandRaised: false,\n\n pinnedByClassroom: {},\n\n updateStore: (type: keyof useCallStoreT, value: any) => set({ [type]: value }),\n\n togglePinnedParticipant: (pin: PinnedParticipantT, classroomId: string) =>\n set((state) => {\n const current = state.pinnedByClassroom[classroomId] ?? [];\n const existingIndex = current.findIndex(\n (item) => item.userId === pin.userId && item.source === pin.source,\n );\n const next = { ...state.pinnedByClassroom };\n\n if (existingIndex >= 0) {\n const updated = current.filter((_, index) => index !== existingIndex);\n if (updated.length) {\n next[classroomId] = updated;\n } else {\n delete next[classroomId];\n }\n } else {\n next[classroomId] = [...current, pin];\n }\n\n return { pinnedByClassroom: next };\n }),\n removePinnedParticipant: (pin: PinnedParticipantT, classroomId: string) =>\n set((state) => {\n const current = state.pinnedByClassroom[classroomId];\n if (!current?.length) return state;\n\n const updated = current.filter(\n (item) => !(item.userId === pin.userId && item.source === pin.source),\n );\n const next = { ...state.pinnedByClassroom };\n if (updated.length) {\n next[classroomId] = updated;\n } else {\n delete next[classroomId];\n }\n return { pinnedByClassroom: next };\n }),\n getPinsForClassroom: (classroomId: string) => get().pinnedByClassroom[classroomId] ?? [],\n isParticipantPinned: (pin: PinnedParticipantT, classroomId: string) => {\n const pins = get().pinnedByClassroom[classroomId] ?? [];\n return pins.some((item) => item.userId === pin.userId && item.source === pin.source);\n },\n\n // Поднятые руки\n addRaisedHand: (hand: RaisedHandT) =>\n set((state) => {\n // Проверяем, есть ли уже рука от этого участника\n const existingHand = state.raisedHands.find(\n (h) => h.participantId === hand.participantId,\n );\n if (existingHand) {\n // Обновляем существующую руку\n return {\n raisedHands: state.raisedHands.map((h) =>\n h.participantId === hand.participantId ? hand : h,\n ),\n };\n }\n // Добавляем новую руку\n return { raisedHands: [...state.raisedHands, hand] };\n }),\n removeRaisedHand: (participantId: string) =>\n set((state) => ({\n raisedHands: state.raisedHands.filter((hand) => hand.participantId !== participantId),\n })),\n toggleHandRaised: () => set((state) => ({ isHandRaised: !state.isHandRaised })),\n clearAllRaisedHands: () => set({ raisedHands: [], isHandRaised: false }),\n isHandRaisedByParticipant: (participantId: string) => {\n const state = get();\n return state.raisedHands.some((hand) => hand.participantId === participantId);\n },\n }),\n {\n name: 'call-store',\n version: 5,\n migrate: (persisted, version) => {\n const state = persisted as Record<string, unknown>;\n if (version < 2) {\n const ct = state.carouselType;\n if (ct === 'focus') {\n state.carouselType = 'horizontal';\n }\n }\n if (version < 3) {\n const ct = state.carouselType;\n state.preferredFocusLayout = ct === 'horizontal' || ct === 'vertical' ? ct : 'horizontal';\n }\n if (version < 4) {\n const mode = state.compactViewMode;\n state.compactViewMode =\n mode === 'basic' || mode === 'expanded' || mode === 'audio' ? mode : 'basic';\n }\n if (version < 5) {\n state.pinnedByClassroom =\n typeof state.pinnedByClassroom === 'object' && state.pinnedByClassroom !== null\n ? state.pinnedByClassroom\n : {};\n delete state.pinnedTrack;\n }\n return state as useCallStoreT;\n },\n partialize: (state) => ({\n isCameraPermission: state.isCameraPermission,\n isMicroPermission: state.isMicroPermission,\n audioEnabled: state.audioEnabled,\n videoEnabled: state.videoEnabled,\n audioDeviceId: state.audioDeviceId,\n videoDeviceId: state.videoDeviceId,\n carouselType: state.carouselType,\n preferredFocusLayout: state.preferredFocusLayout,\n activeCorner: state.activeCorner,\n compactViewMode: state.compactViewMode,\n pinnedByClassroom: state.pinnedByClassroom,\n }),\n },\n ),\n);\n","import { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\n\ntype PermissionStateT = undefined | 'granted' | 'prompt' | 'denied' | 'unavailable';\n\ntype BaseStateT = {\n cameraPermission: PermissionStateT;\n microphonePermission: PermissionStateT;\n isLoading: boolean;\n isPermissionDialogOpen: boolean;\n};\n\ntype DerivedStateT = {\n isCameraGranted: boolean;\n isMicrophoneGranted: boolean;\n isCameraDenied: boolean;\n isMicrophoneDenied: boolean;\n isCameraPrompted: boolean;\n isMicrophonePrompted: boolean;\n};\n\ntype State = BaseStateT & DerivedStateT;\n\nexport const usePermissionsStore = create<State>()(\n persist(\n (_, get) => ({\n cameraPermission: undefined,\n microphonePermission: undefined,\n isLoading: true,\n isPermissionDialogOpen: false,\n\n get isCameraGranted() {\n return get().cameraPermission === 'granted';\n },\n get isMicrophoneGranted() {\n return get().microphonePermission === 'granted';\n },\n get isCameraDenied() {\n return get().cameraPermission === 'denied';\n },\n get isMicrophoneDenied() {\n return get().microphonePermission === 'denied';\n },\n get isCameraPrompted() {\n return get().cameraPermission === 'prompt';\n },\n get isMicrophonePrompted() {\n return get().microphonePermission === 'prompt';\n },\n }),\n {\n name: 'permissions-storage',\n partialize: (state) => ({\n cameraPermission: state.cameraPermission,\n microphonePermission: state.microphonePermission,\n }),\n },\n ),\n);\n\nexport const openPermissionsDialog = () => {\n usePermissionsStore.setState({ isPermissionDialogOpen: true });\n};\n\nexport const closePermissionsDialog = () => {\n usePermissionsStore.setState({ isPermissionDialogOpen: false });\n};\n","import { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\nimport {\n loadUserChoices,\n saveUserChoices,\n LocalUserChoices as LocalUserChoicesLK,\n} from '@livekit/components-core';\nimport { VideoQuality } from 'livekit-client';\nimport type { NoiseCancellationMode } from '@xipkg/calls-types';\n\nexport type VideoResolution = 'h720' | 'h360' | 'h180';\n\nexport type LocalUserChoices = LocalUserChoicesLK & {\n noiseReductionEnabled?: boolean;\n blurEnabled?: boolean;\n audioOutputDeviceId?: string;\n videoPublishResolution?: VideoResolution;\n videoSubscribeQuality?: VideoQuality;\n /** Включено ли шумоподавление (любой режим кроме off). */\n noiseCancellationEnabled?: boolean;\n /** Режим шумоподавления: off | webrtc | krisp. */\n noiseCancellationMode?: NoiseCancellationMode;\n};\n\nfunction getUserChoicesState(): LocalUserChoices {\n return {\n noiseReductionEnabled: false,\n blurEnabled: false,\n audioOutputDeviceId: 'default',\n videoPublishResolution: 'h720',\n videoSubscribeQuality: VideoQuality.HIGH,\n noiseCancellationEnabled: false,\n noiseCancellationMode: 'webrtc',\n ...loadUserChoices(),\n };\n}\n\nexport const useUserChoicesStore = create<LocalUserChoices>()(\n persist(\n () => ({\n ...getUserChoicesState(),\n }),\n {\n name: 'user-choices-storage',\n onRehydrateStorage: () => (state) => {\n if (state) {\n saveUserChoices(state, false);\n }\n },\n },\n ),\n);\n","import { Track } from 'livekit-client';\nimport type { TrackReferenceOrPlaceholder } from '@livekit/components-core';\n\n/** Стабильный идентификатор участника для закрепления в рамках кабинета */\nexport type PinnedParticipantT = {\n userId: string;\n source: Track.Source;\n};\n\n/** @deprecated Используйте PinnedParticipantT */\nexport type PinnedTrackT = PinnedParticipantT;\n\nexport function getParticipantUserId(participant: { identity: string; metadata?: string }): string {\n if (participant.metadata) {\n try {\n const meta = JSON.parse(participant.metadata) as {\n user_id?: string | number;\n id?: string | number;\n };\n if (meta?.user_id != null) return String(meta.user_id);\n if (meta?.id != null) return String(meta.id);\n } catch {\n /* metadata is not JSON */\n }\n }\n return participant.identity;\n}\n\nexport function toPinnedParticipant(track: TrackReferenceOrPlaceholder): PinnedParticipantT {\n return {\n userId: getParticipantUserId(track.participant),\n source: track.source ?? Track.Source.Camera,\n };\n}\n\n/** @deprecated Используйте toPinnedParticipant */\nexport const toPinnedTrack = toPinnedParticipant;\n\nexport function matchesPinnedParticipant(\n track: TrackReferenceOrPlaceholder,\n pinned: PinnedParticipantT,\n): boolean {\n return (\n getParticipantUserId(track.participant) === pinned.userId && track.source === pinned.source\n );\n}\n\n/** @deprecated Используйте matchesPinnedParticipant */\nexport const matchesPinnedTrack = matchesPinnedParticipant;\n\nexport function findPinnedTrackRef(\n tracks: TrackReferenceOrPlaceholder[],\n pinned: PinnedParticipantT,\n): TrackReferenceOrPlaceholder | undefined {\n return tracks.find((track) => matchesPinnedParticipant(track, pinned));\n}\n\n/** Перемещает закреплённых участников в начало списка (порядок pin сохраняется) */\nexport function applyPinsFirst(\n tracks: TrackReferenceOrPlaceholder[],\n pins: PinnedParticipantT[],\n): TrackReferenceOrPlaceholder[] {\n if (!pins.length) return tracks;\n\n const pinnedRefs: TrackReferenceOrPlaceholder[] = [];\n const seen = new Set<TrackReferenceOrPlaceholder>();\n\n for (const pin of pins) {\n const ref = findPinnedTrackRef(tracks, pin);\n if (ref && !seen.has(ref)) {\n pinnedRefs.push(ref);\n seen.add(ref);\n }\n }\n\n if (!pinnedRefs.length) return tracks;\n return [...pinnedRefs, ...tracks.filter((track) => !seen.has(track))];\n}\n\nexport function applyPinFirst(\n tracks: TrackReferenceOrPlaceholder[],\n pinned: PinnedParticipantT | null,\n): TrackReferenceOrPlaceholder[] {\n return applyPinsFirst(tracks, pinned ? [pinned] : []);\n}\n\n/** Участник для главной плитки focus-сетки (без учёта локального pin) */\nexport function pickDefaultFocusTrack(\n tracks: TrackReferenceOrPlaceholder[],\n): TrackReferenceOrPlaceholder | undefined {\n const screenShare = tracks.find(\n (track) => track.source === Track.Source.ScreenShare && track.publication?.isSubscribed,\n );\n if (screenShare) return screenShare;\n return tracks.find((track) => track.source === Track.Source.Camera);\n}\n","import { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\n\ntype SoundKey =\n | 'chatMessage'\n | 'handRaise'\n | 'userJoin'\n | 'userLeft'\n | 'boardTimerEnd'\n | 'boardTimerWarn';\n\ninterface SoundEffectsState {\n chatMessageVolume: number;\n handRaiseVolume: number;\n userJoinVolume: number;\n userLeftVolume: number;\n boardTimerEndVolume: number;\n boardTimerWarnVolume: number;\n\n setSoundVolume: (key: SoundKey, volume: number) => void;\n getEffectiveVolume: (key: SoundKey) => number;\n}\n\nconst DEFAULTS: Record<SoundKey, number> = {\n chatMessage: 0.25,\n handRaise: 0.25,\n userJoin: 0.25,\n userLeft: 0.25,\n boardTimerEnd: 1,\n boardTimerWarn: 1,\n};\n\nconst VOLUME_KEY_MAP: Record<SoundKey, keyof SoundEffectsState> = {\n chatMessage: 'chatMessageVolume',\n handRaise: 'handRaiseVolume',\n userJoin: 'userJoinVolume',\n userLeft: 'userLeftVolume',\n boardTimerEnd: 'boardTimerEndVolume',\n boardTimerWarn: 'boardTimerWarnVolume',\n};\n\nconst clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value));\n\nexport const useSoundEffectsStore = create<SoundEffectsState>()(\n persist(\n (set, get) => ({\n chatMessageVolume: DEFAULTS.chatMessage,\n handRaiseVolume: DEFAULTS.handRaise,\n userJoinVolume: DEFAULTS.userJoin,\n userLeftVolume: DEFAULTS.userLeft,\n boardTimerEndVolume: DEFAULTS.boardTimerEnd,\n boardTimerWarnVolume: DEFAULTS.boardTimerWarn,\n\n setSoundVolume: (key, volume) => {\n const min = key === 'boardTimerEnd' ? 0.2 : 0;\n const clamped = clamp(volume, min, 1);\n set({ [VOLUME_KEY_MAP[key]]: clamped });\n },\n\n getEffectiveVolume: (key) => {\n const storeKey = VOLUME_KEY_MAP[key];\n return get()[storeKey] as number;\n },\n }),\n {\n name: 'sound-effects-store',\n version: 1,\n partialize: (state) => ({\n chatMessageVolume: state.chatMessageVolume,\n handRaiseVolume: state.handRaiseVolume,\n userJoinVolume: state.userJoinVolume,\n userLeftVolume: state.userLeftVolume,\n boardTimerEndVolume: state.boardTimerEndVolume,\n boardTimerWarnVolume: state.boardTimerWarnVolume,\n }),\n },\n ),\n);\n\nexport { DEFAULTS as SOUND_DEFAULTS };\nexport type { SoundKey };\n","import { create } from 'zustand';\n\nexport type FeatureKey = 'compactView' | 'whiteboard' | 'raiseHand' | 'chat';\n\ntype FeatureMapT = Record<FeatureKey, boolean>;\n\ntype FeaturesState = {\n features: FeatureMapT;\n\n setFeature: (key: FeatureKey, value: boolean) => void;\n setFeatures: (features: Partial<FeatureMapT>) => void;\n};\n\nconst defaultFeatures: FeatureMapT = {\n compactView: true,\n whiteboard: true,\n raiseHand: true,\n chat: true,\n};\n\nexport const useFeaturesStore = create<FeaturesState>((set) => ({\n features: defaultFeatures,\n\n setFeature: (key, value) =>\n set((state) => ({\n features: { ...state.features, [key]: value },\n })),\n\n setFeatures: (features) =>\n set((state) => ({\n features: { ...state.features, ...features },\n })),\n}));\n","import { create } from 'zustand';\n\ninterface FocusModeState {\n focusMode: boolean;\n setFocusMode: (value: boolean) => void;\n toggleFocusMode: () => void;\n}\n\nexport const useFocusModeStore = create<FocusModeState>((set) => ({\n focusMode: false,\n setFocusMode: (value) => set({ focusMode: value }),\n toggleFocusMode: () => set((state) => ({ focusMode: !state.focusMode })),\n}));\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xipkg/calls-store",
3
- "version": "0.0.12",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -13,7 +13,7 @@
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
15
  "zustand": "5.0.3",
16
- "@xipkg/calls-types": "^0.0.11"
16
+ "@xipkg/calls-types": "^0.1.0"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@tanstack/react-query-devtools": "5.73.3",