@xipkg/calls-store 0.1.15 → 0.1.16

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
@@ -160,6 +160,12 @@ type LocalUserChoices = LocalUserChoices$1 & {
160
160
  noiseCancellationEnabled?: boolean;
161
161
  /** Режим шумоподавления: off | webrtc | krisp. */
162
162
  noiseCancellationMode?: NoiseCancellationMode;
163
+ /** Громкость микрофона (0..1), для preview/тестов и будущей apply в звонке. */
164
+ microphoneVolume?: number;
165
+ /** Громкость динамиков (0..1), для тестового воспроизведения и будущей apply в звонке. */
166
+ speakerVolume?: number;
167
+ /** Зеркальное отражение локального превью камеры. */
168
+ mirrorVideo?: boolean;
163
169
  };
164
170
  declare const useUserChoicesStore: zustand.UseBoundStore<Omit<zustand.StoreApi<LocalUserChoices>, "setState" | "persist"> & {
165
171
  setState(partial: LocalUserChoices | Partial<LocalUserChoices> | ((state: LocalUserChoices) => LocalUserChoices | Partial<LocalUserChoices>), replace?: false | undefined): unknown;
@@ -180,6 +186,12 @@ declare const useUserChoicesStore: zustand.UseBoundStore<Omit<zustand.StoreApi<L
180
186
  noiseCancellationEnabled?: boolean;
181
187
  /** Режим шумоподавления: off | webrtc | krisp. */
182
188
  noiseCancellationMode?: NoiseCancellationMode;
189
+ /** Громкость микрофона (0..1), для preview/тестов и будущей apply в звонке. */
190
+ microphoneVolume?: number;
191
+ /** Громкость динамиков (0..1), для тестового воспроизведения и будущей apply в звонке. */
192
+ speakerVolume?: number;
193
+ /** Зеркальное отражение локального превью камеры. */
194
+ mirrorVideo?: boolean;
183
195
  }, unknown>>) => void;
184
196
  clearStorage: () => void;
185
197
  rehydrate: () => Promise<void> | void;
@@ -201,6 +213,12 @@ declare const useUserChoicesStore: zustand.UseBoundStore<Omit<zustand.StoreApi<L
201
213
  noiseCancellationEnabled?: boolean;
202
214
  /** Режим шумоподавления: off | webrtc | krisp. */
203
215
  noiseCancellationMode?: NoiseCancellationMode;
216
+ /** Громкость микрофона (0..1), для preview/тестов и будущей apply в звонке. */
217
+ microphoneVolume?: number;
218
+ /** Громкость динамиков (0..1), для тестового воспроизведения и будущей apply в звонке. */
219
+ speakerVolume?: number;
220
+ /** Зеркальное отражение локального превью камеры. */
221
+ mirrorVideo?: boolean;
204
222
  }, unknown>>;
205
223
  };
206
224
  }>;
package/dist/index.mjs CHANGED
@@ -185,6 +185,9 @@ function getUserChoicesState() {
185
185
  videoSubscribeQuality: VideoQuality.HIGH,
186
186
  noiseCancellationEnabled: false,
187
187
  noiseCancellationMode: "webrtc",
188
+ microphoneVolume: 1,
189
+ speakerVolume: 1,
190
+ mirrorVideo: true,
188
191
  ...loadUserChoices()
189
192
  };
190
193
  }
@@ -1 +1 @@
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","../src/store/reactionsStore.ts"],"names":["create","persist"],"mappings":";;;;;;AAsEO,IAAM,eAAe,MAAA,EAAsB;AAAA,EAChD,OAAA;AAAA,IACE,CAAC,KAAK,GAAA,MAAS;AAAA,MACb,kBAAA,EAAoB,IAAA;AAAA,MACpB,iBAAA,EAAmB,IAAA;AAAA,MACnB,YAAA,EAAc,KAAA;AAAA,MACd,YAAA,EAAc,KAAA;AAAA,MACd,aAAA,EAAe,MAAA;AAAA,MACf,mBAAA,EAAqB,MAAA;AAAA,MACrB,aAAA,EAAe,MAAA;AAAA,MACf,OAAA,EAAS,MAAA;AAAA,MACT,SAAA,EAAW,MAAA;AAAA,MACX,YAAA,EAAc,KAAA;AAAA,MACd,IAAA,EAAM,MAAA;AAAA,MACN,YAAA,EAAc,MAAA;AAAA,MACd,oBAAA,EAAsB,YAAA;AAAA,MACtB,YAAA,EAAc,UAAA;AAAA,MACd,eAAA,EAAiB,OAAA;AAAA;AAAA,MAGjB,aAAA,EAAe,MAAA;AAAA,MACf,eAAA,EAAiB,MAAA;AAAA,MAEjB,aAAA,EAAe,KAAA;AAAA;AAAA,MAGf,KAAA,EAAO,MAAA;AAAA;AAAA,MAGP,aAAa,EAAC;AAAA,MACd,YAAA,EAAc,KAAA;AAAA,MAEd,mBAAmB,EAAC;AAAA,MAEpB,WAAA,EAAa,CAAC,IAAA,EAA2B,KAAA,KAAe,GAAA,CAAI,EAAE,CAAC,IAAI,GAAG,KAAA,EAAO,CAAA;AAAA,MAE7E,yBAAyB,CAAC,GAAA,EAAyB,WAAA,KACjD,GAAA,CAAI,CAAC,KAAA,KAAU;AACb,QAAA,MAAM,OAAA,GAAU,KAAA,CAAM,iBAAA,CAAkB,WAAW,KAAK,EAAC;AACzD,QAAA,MAAM,gBAAgB,OAAA,CAAQ,SAAA;AAAA,UAC5B,CAAC,SAAS,IAAA,CAAK,MAAA,KAAW,IAAI,MAAA,IAAU,IAAA,CAAK,WAAW,GAAA,CAAI;AAAA,SAC9D;AACA,QAAA,MAAM,IAAA,GAAO,EAAE,GAAG,KAAA,CAAM,iBAAA,EAAkB;AAE1C,QAAA,IAAI,iBAAiB,CAAA,EAAG;AACtB,UAAA,MAAM,UAAU,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,EAAG,KAAA,KAAU,UAAU,aAAa,CAAA;AACpE,UAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,YAAA,IAAA,CAAK,WAAW,CAAA,GAAI,OAAA;AAAA,UACtB,CAAA,MAAO;AACL,YAAA,OAAO,KAAK,WAAW,CAAA;AAAA,UACzB;AAAA,QACF,CAAA,MAAO;AACL,UAAA,IAAA,CAAK,WAAW,CAAA,GAAI,CAAC,GAAG,SAAS,GAAG,CAAA;AAAA,QACtC;AAEA,QAAA,OAAO,EAAE,mBAAmB,IAAA,EAAK;AAAA,MACnC,CAAC,CAAA;AAAA,MACH,yBAAyB,CAAC,GAAA,EAAyB,WAAA,KACjD,GAAA,CAAI,CAAC,KAAA,KAAU;AACb,QAAA,MAAM,OAAA,GAAU,KAAA,CAAM,iBAAA,CAAkB,WAAW,CAAA;AACnD,QAAA,IAAI,CAAC,OAAA,EAAS,MAAA,EAAQ,OAAO,KAAA;AAE7B,QAAA,MAAM,UAAU,OAAA,CAAQ,MAAA;AAAA,UACtB,CAAC,SAAS,EAAE,IAAA,CAAK,WAAW,GAAA,CAAI,MAAA,IAAU,IAAA,CAAK,MAAA,KAAW,GAAA,CAAI,MAAA;AAAA,SAChE;AACA,QAAA,MAAM,IAAA,GAAO,EAAE,GAAG,KAAA,CAAM,iBAAA,EAAkB;AAC1C,QAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,UAAA,IAAA,CAAK,WAAW,CAAA,GAAI,OAAA;AAAA,QACtB,CAAA,MAAO;AACL,UAAA,OAAO,KAAK,WAAW,CAAA;AAAA,QACzB;AACA,QAAA,OAAO,EAAE,mBAAmB,IAAA,EAAK;AAAA,MACnC,CAAC,CAAA;AAAA,MACH,mBAAA,EAAqB,CAAC,WAAA,KAAwB,GAAA,GAAM,iBAAA,CAAkB,WAAW,KAAK,EAAC;AAAA,MACvF,mBAAA,EAAqB,CAAC,GAAA,EAAyB,WAAA,KAAwB;AACrE,QAAA,MAAM,OAAO,GAAA,EAAI,CAAE,iBAAA,CAAkB,WAAW,KAAK,EAAC;AACtD,QAAA,OAAO,IAAA,CAAK,IAAA,CAAK,CAAC,IAAA,KAAS,IAAA,CAAK,MAAA,KAAW,GAAA,CAAI,MAAA,IAAU,IAAA,CAAK,MAAA,KAAW,GAAA,CAAI,MAAM,CAAA;AAAA,MACrF,CAAA;AAAA;AAAA,MAGA,aAAA,EAAe,CAAC,IAAA,KACd,GAAA,CAAI,CAAC,KAAA,KAAU;AAEb,QAAA,MAAM,YAAA,GAAe,MAAM,WAAA,CAAY,IAAA;AAAA,UACrC,CAAC,CAAA,KAAM,CAAA,CAAE,aAAA,KAAkB,IAAA,CAAK;AAAA,SAClC;AACA,QAAA,IAAI,YAAA,EAAc;AAEhB,UAAA,OAAO;AAAA,YACL,WAAA,EAAa,MAAM,WAAA,CAAY,GAAA;AAAA,cAAI,CAAC,CAAA,KAClC,CAAA,CAAE,aAAA,KAAkB,IAAA,CAAK,gBAAgB,IAAA,GAAO;AAAA;AAClD,WACF;AAAA,QACF;AAEA,QAAA,OAAO,EAAE,WAAA,EAAa,CAAC,GAAG,KAAA,CAAM,WAAA,EAAa,IAAI,CAAA,EAAE;AAAA,MACrD,CAAC,CAAA;AAAA,MACH,gBAAA,EAAkB,CAAC,aAAA,KACjB,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,QACd,WAAA,EAAa,MAAM,WAAA,CAAY,MAAA,CAAO,CAAC,IAAA,KAAS,IAAA,CAAK,kBAAkB,aAAa;AAAA,OACtF,CAAE,CAAA;AAAA,MACJ,gBAAA,EAAkB,MAAM,GAAA,CAAI,CAAC,KAAA,MAAW,EAAE,YAAA,EAAc,CAAC,KAAA,CAAM,YAAA,EAAa,CAAE,CAAA;AAAA,MAC9E,mBAAA,EAAqB,MAAM,GAAA,CAAI,EAAE,aAAa,EAAC,EAAG,YAAA,EAAc,KAAA,EAAO,CAAA;AAAA,MACvE,yBAAA,EAA2B,CAAC,aAAA,KAA0B;AACpD,QAAA,MAAM,QAAQ,GAAA,EAAI;AAClB,QAAA,OAAO,MAAM,WAAA,CAAY,IAAA,CAAK,CAAC,IAAA,KAAS,IAAA,CAAK,kBAAkB,aAAa,CAAA;AAAA,MAC9E;AAAA,KACF,CAAA;AAAA,IACA;AAAA,MACE,IAAA,EAAM,YAAA;AAAA,MACN,OAAA,EAAS,CAAA;AAAA,MACT,OAAA,EAAS,CAAC,SAAA,EAAW,OAAA,KAAY;AAC/B,QAAA,MAAM,KAAA,GAAQ,SAAA;AACd,QAAA,IAAI,UAAU,CAAA,EAAG;AACf,UAAA,MAAM,KAAK,KAAA,CAAM,YAAA;AACjB,UAAA,IAAI,OAAO,OAAA,EAAS;AAClB,YAAA,KAAA,CAAM,YAAA,GAAe,YAAA;AAAA,UACvB;AAAA,QACF;AACA,QAAA,IAAI,UAAU,CAAA,EAAG;AACf,UAAA,MAAM,KAAK,KAAA,CAAM,YAAA;AACjB,UAAA,KAAA,CAAM,oBAAA,GAAuB,EAAA,KAAO,YAAA,IAAgB,EAAA,KAAO,aAAa,EAAA,GAAK,YAAA;AAAA,QAC/E;AACA,QAAA,IAAI,UAAU,CAAA,EAAG;AACf,UAAA,MAAM,OAAO,KAAA,CAAM,eAAA;AACnB,UAAA,KAAA,CAAM,kBACJ,IAAA,KAAS,OAAA,IAAW,SAAS,UAAA,IAAc,IAAA,KAAS,UAAU,IAAA,GAAO,OAAA;AAAA,QACzE;AACA,QAAA,IAAI,UAAU,CAAA,EAAG;AACf,UAAA,KAAA,CAAM,iBAAA,GACJ,OAAO,KAAA,CAAM,iBAAA,KAAsB,QAAA,IAAY,MAAM,iBAAA,KAAsB,IAAA,GACvE,KAAA,CAAM,iBAAA,GACN,EAAC;AACP,UAAA,OAAO,KAAA,CAAM,WAAA;AAAA,QACf;AACA,QAAA,OAAO,KAAA;AAAA,MACT,CAAA;AAAA,MACA,UAAA,EAAY,CAAC,KAAA,MAAW;AAAA,QACtB,oBAAoB,KAAA,CAAM,kBAAA;AAAA,QAC1B,mBAAmB,KAAA,CAAM,iBAAA;AAAA,QACzB,cAAc,KAAA,CAAM,YAAA;AAAA,QACpB,cAAc,KAAA,CAAM,YAAA;AAAA,QACpB,eAAe,KAAA,CAAM,aAAA;AAAA,QACrB,eAAe,KAAA,CAAM,aAAA;AAAA,QACrB,cAAc,KAAA,CAAM,YAAA;AAAA,QACpB,sBAAsB,KAAA,CAAM,oBAAA;AAAA,QAC5B,cAAc,KAAA,CAAM,YAAA;AAAA,QACpB,iBAAiB,KAAA,CAAM,eAAA;AAAA,QACvB,mBAAmB,KAAA,CAAM;AAAA,OAC3B;AAAA;AACF;AAEJ;ACvMO,IAAM,sBAAsBA,MAAAA,EAAc;AAAA,EAC/CC,OAAAA;AAAA,IACE,CAAC,GAAG,GAAA,MAAS;AAAA,MACX,gBAAA,EAAkB,MAAA;AAAA,MAClB,oBAAA,EAAsB,MAAA;AAAA,MACtB,SAAA,EAAW,IAAA;AAAA,MACX,sBAAA,EAAwB,KAAA;AAAA,MAExB,IAAI,eAAA,GAAkB;AACpB,QAAA,OAAO,GAAA,GAAM,gBAAA,KAAqB,SAAA;AAAA,MACpC,CAAA;AAAA,MACA,IAAI,mBAAA,GAAsB;AACxB,QAAA,OAAO,GAAA,GAAM,oBAAA,KAAyB,SAAA;AAAA,MACxC,CAAA;AAAA,MACA,IAAI,cAAA,GAAiB;AACnB,QAAA,OAAO,GAAA,GAAM,gBAAA,KAAqB,QAAA;AAAA,MACpC,CAAA;AAAA,MACA,IAAI,kBAAA,GAAqB;AACvB,QAAA,OAAO,GAAA,GAAM,oBAAA,KAAyB,QAAA;AAAA,MACxC,CAAA;AAAA,MACA,IAAI,gBAAA,GAAmB;AACrB,QAAA,OAAO,GAAA,GAAM,gBAAA,KAAqB,QAAA;AAAA,MACpC,CAAA;AAAA,MACA,IAAI,oBAAA,GAAuB;AACzB,QAAA,OAAO,GAAA,GAAM,oBAAA,KAAyB,QAAA;AAAA,MACxC;AAAA,KACF,CAAA;AAAA,IACA;AAAA,MACE,IAAA,EAAM,qBAAA;AAAA,MACN,UAAA,EAAY,CAAC,KAAA,MAAW;AAAA,QACtB,kBAAkB,KAAA,CAAM,gBAAA;AAAA,QACxB,sBAAsB,KAAA,CAAM;AAAA,OAC9B;AAAA;AACF;AAEJ;AAEO,IAAM,wBAAwB,MAAM;AACzC,EAAA,mBAAA,CAAoB,QAAA,CAAS,EAAE,sBAAA,EAAwB,IAAA,EAAM,CAAA;AAC/D;AAEO,IAAM,yBAAyB,MAAM;AAC1C,EAAA,mBAAA,CAAoB,QAAA,CAAS,EAAE,sBAAA,EAAwB,KAAA,EAAO,CAAA;AAChE;AC1CA,SAAS,mBAAA,GAAwC;AAC/C,EAAA,OAAO;AAAA,IACL,qBAAA,EAAuB,KAAA;AAAA,IACvB,WAAA,EAAa,KAAA;AAAA,IACb,mBAAA,EAAqB,SAAA;AAAA,IACrB,sBAAA,EAAwB,MAAA;AAAA,IACxB,uBAAuB,YAAA,CAAa,IAAA;AAAA,IACpC,wBAAA,EAA0B,KAAA;AAAA,IAC1B,qBAAA,EAAuB,QAAA;AAAA,IACvB,GAAG,eAAA;AAAgB,GACrB;AACF;AAEO,IAAM,sBAAsBD,MAAAA,EAAyB;AAAA,EAC1DC,OAAAA;AAAA,IACE,OAAO;AAAA,MACL,GAAG,mBAAA;AAAoB,KACzB,CAAA;AAAA,IACA;AAAA,MACE,IAAA,EAAM,sBAAA;AAAA,MACN,kBAAA,EAAoB,MAAM,CAAC,KAAA,KAAU;AACnC,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,eAAA,CAAgB,OAAO,KAAK,CAAA;AAAA,QAC9B;AAAA,MACF;AAAA;AACF;AAEJ;ACvCO,SAAS,qBAAqB,WAAA,EAA8D;AACjG,EAAA,IAAI,YAAY,QAAA,EAAU;AACxB,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,QAAQ,CAAA;AAI5C,MAAA,IAAI,MAAM,OAAA,IAAW,IAAA,EAAM,OAAO,MAAA,CAAO,KAAK,OAAO,CAAA;AACrD,MAAA,IAAI,MAAM,EAAA,IAAM,IAAA,EAAM,OAAO,MAAA,CAAO,KAAK,EAAE,CAAA;AAAA,IAC7C,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACA,EAAA,OAAO,WAAA,CAAY,QAAA;AACrB;AAEO,SAAS,oBAAoB,KAAA,EAAwD;AAC1F,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,oBAAA,CAAqB,KAAA,CAAM,WAAW,CAAA;AAAA,IAC9C,MAAA,EAAQ,KAAA,CAAM,MAAA,IAAU,KAAA,CAAM,MAAA,CAAO;AAAA,GACvC;AACF;AAGO,IAAM,aAAA,GAAgB;AAEtB,SAAS,wBAAA,CACd,OACA,MAAA,EACS;AACT,EAAA,OACE,oBAAA,CAAqB,MAAM,WAAW,CAAA,KAAM,OAAO,MAAA,IAAU,KAAA,CAAM,WAAW,MAAA,CAAO,MAAA;AAEzF;AAGO,IAAM,kBAAA,GAAqB;AAE3B,SAAS,kBAAA,CACd,QACA,MAAA,EACyC;AACzC,EAAA,OAAO,OAAO,IAAA,CAAK,CAAC,UAAU,wBAAA,CAAyB,KAAA,EAAO,MAAM,CAAC,CAAA;AACvE;AAGO,SAAS,cAAA,CACd,QACA,IAAA,EAC+B;AAC/B,EAAA,IAAI,CAAC,IAAA,CAAK,MAAA,EAAQ,OAAO,MAAA;AAEzB,EAAA,MAAM,aAA4C,EAAC;AACnD,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAiC;AAElD,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,GAAA,GAAM,kBAAA,CAAmB,MAAA,EAAQ,GAAG,CAAA;AAC1C,IAAA,IAAI,GAAA,IAAO,CAAC,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG;AACzB,MAAA,UAAA,CAAW,KAAK,GAAG,CAAA;AACnB,MAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AAAA,IACd;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,UAAA,CAAW,MAAA,EAAQ,OAAO,MAAA;AAC/B,EAAA,OAAO,CAAC,GAAG,UAAA,EAAY,GAAG,MAAA,CAAO,MAAA,CAAO,CAAC,KAAA,KAAU,CAAC,IAAA,CAAK,GAAA,CAAI,KAAK,CAAC,CAAC,CAAA;AACtE;AAEO,SAAS,aAAA,CACd,QACA,MAAA,EAC+B;AAC/B,EAAA,OAAO,eAAe,MAAA,EAAQ,MAAA,GAAS,CAAC,MAAM,CAAA,GAAI,EAAE,CAAA;AACtD;AAGO,SAAS,sBACd,MAAA,EACyC;AACzC,EAAA,MAAM,cAAc,MAAA,CAAO,IAAA;AAAA,IACzB,CAAC,UAAU,KAAA,CAAM,MAAA,KAAW,MAAM,MAAA,CAAO,WAAA,IAAe,MAAM,WAAA,EAAa;AAAA,GAC7E;AACA,EAAA,IAAI,aAAa,OAAO,WAAA;AACxB,EAAA,OAAO,MAAA,CAAO,KAAK,CAAC,KAAA,KAAU,MAAM,MAAA,KAAW,KAAA,CAAM,OAAO,MAAM,CAAA;AACpE;AC7EA,IAAM,QAAA,GAAqC;AAAA,EACzC,WAAA,EAAa,IAAA;AAAA,EACb,SAAA,EAAW,IAAA;AAAA,EACX,QAAA,EAAU,IAAA;AAAA,EACV,QAAA,EAAU,IAAA;AAAA,EACV,aAAA,EAAe,CAAA;AAAA,EACf,cAAA,EAAgB;AAClB,CAAA;AAEA,IAAM,cAAA,GAA4D;AAAA,EAChE,WAAA,EAAa,mBAAA;AAAA,EACb,SAAA,EAAW,iBAAA;AAAA,EACX,QAAA,EAAU,gBAAA;AAAA,EACV,QAAA,EAAU,gBAAA;AAAA,EACV,aAAA,EAAe,qBAAA;AAAA,EACf,cAAA,EAAgB;AAClB,CAAA;AAEA,IAAM,KAAA,GAAQ,CAAC,KAAA,EAAe,GAAA,EAAa,GAAA,KAAgB,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,KAAK,CAAC,CAAA;AAEtF,IAAM,uBAAuBD,MAAAA,EAA0B;AAAA,EAC5DC,OAAAA;AAAA,IACE,CAAC,KAAK,GAAA,MAAS;AAAA,MACb,mBAAmB,QAAA,CAAS,WAAA;AAAA,MAC5B,iBAAiB,QAAA,CAAS,SAAA;AAAA,MAC1B,gBAAgB,QAAA,CAAS,QAAA;AAAA,MACzB,gBAAgB,QAAA,CAAS,QAAA;AAAA,MACzB,qBAAqB,QAAA,CAAS,aAAA;AAAA,MAC9B,sBAAsB,QAAA,CAAS,cAAA;AAAA,MAE/B,cAAA,EAAgB,CAAC,GAAA,EAAK,MAAA,KAAW;AAC/B,QAAA,MAAM,GAAA,GAAM,GAAA,KAAQ,eAAA,GAAkB,GAAA,GAAM,CAAA;AAC5C,QAAA,MAAM,OAAA,GAAU,KAAA,CAAM,MAAA,EAAQ,GAAA,EAAK,CAAC,CAAA;AACpC,QAAA,GAAA,CAAI,EAAE,CAAC,cAAA,CAAe,GAAG,CAAC,GAAG,SAAS,CAAA;AAAA,MACxC,CAAA;AAAA,MAEA,kBAAA,EAAoB,CAAC,GAAA,KAAQ;AAC3B,QAAA,MAAM,QAAA,GAAW,eAAe,GAAG,CAAA;AACnC,QAAA,OAAO,GAAA,GAAM,QAAQ,CAAA;AAAA,MACvB;AAAA,KACF,CAAA;AAAA,IACA;AAAA,MACE,IAAA,EAAM,qBAAA;AAAA,MACN,OAAA,EAAS,CAAA;AAAA,MACT,UAAA,EAAY,CAAC,KAAA,MAAW;AAAA,QACtB,mBAAmB,KAAA,CAAM,iBAAA;AAAA,QACzB,iBAAiB,KAAA,CAAM,eAAA;AAAA,QACvB,gBAAgB,KAAA,CAAM,cAAA;AAAA,QACtB,gBAAgB,KAAA,CAAM,cAAA;AAAA,QACtB,qBAAqB,KAAA,CAAM,mBAAA;AAAA,QAC3B,sBAAsB,KAAA,CAAM;AAAA,OAC9B;AAAA;AACF;AAEJ;AC3DA,IAAM,eAAA,GAA+B;AAAA,EACnC,WAAA,EAAa,IAAA;AAAA,EACb,UAAA,EAAY,IAAA;AAAA,EACZ,SAAA,EAAW,IAAA;AAAA,EACX,IAAA,EAAM,IAAA;AAAA,EACN,SAAA,EAAW;AACb,CAAA;AAEO,IAAM,gBAAA,GAAmBD,MAAAA,CAAsB,CAAC,GAAA,MAAS;AAAA,EAC9D,QAAA,EAAU,eAAA;AAAA,EAEV,YAAY,CAAC,GAAA,EAAK,KAAA,KAChB,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,IACd,QAAA,EAAU,EAAE,GAAG,KAAA,CAAM,UAAU,CAAC,GAAG,GAAG,KAAA;AAAM,GAC9C,CAAE,CAAA;AAAA,EAEJ,WAAA,EAAa,CAAC,QAAA,KACZ,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,IACd,UAAU,EAAE,GAAG,KAAA,CAAM,QAAA,EAAU,GAAG,QAAA;AAAS,GAC7C,CAAE;AACN,CAAA,CAAE;ACzBK,IAAM,iBAAA,GAAoBA,MAAAA,CAAuB,CAAC,GAAA,MAAS;AAAA,EAChE,SAAA,EAAW,KAAA;AAAA,EACX,cAAc,CAAC,KAAA,KAAU,IAAI,EAAE,SAAA,EAAW,OAAO,CAAA;AAAA,EACjD,eAAA,EAAiB,MAAM,GAAA,CAAI,CAAC,KAAA,MAAW,EAAE,SAAA,EAAW,CAAC,KAAA,CAAM,SAAA,EAAU,CAAE;AACzE,CAAA,CAAE;ACMF,IAAM,sBAAA,GAAyB,EAAA;AAGxB,IAAM,2BAAA,GAA8B;AAiBpC,IAAM,iBAAA,GAAoBA,MAAAA,EAA2B,CAAE,CAAC,KAAK,GAAA,MAAS;AAAA,EAC3E,mBAAmB,EAAC;AAAA,EACpB,sBAAsB,EAAC;AAAA,EAEvB,mBAAA,EAAqB,CAAC,QAAA,KAAgC;AACpD,IAAA,GAAA,EAAI,CAAE,oBAAA,CAAqB,CAAC,QAAQ,CAAC,CAAA;AAAA,EACvC,CAAA;AAAA,EAEA,oBAAA,EAAsB,CAAC,SAAA,KAAmC;AACxD,IAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAE5B,IAAA,MAAM,EAAE,iBAAA,EAAkB,GAAI,GAAA,EAAI;AAClC,IAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAI,iBAAA,CAAkB,IAAI,CAAC,IAAA,KAAS,IAAA,CAAK,EAAE,CAAC,CAAA;AACpE,IAAA,MAAM,MAAA,GAAS,SAAA,CAAU,MAAA,CAAO,CAAC,QAAA,KAAa,CAAC,WAAA,CAAY,GAAA,CAAI,QAAA,CAAS,EAAE,CAAC,CAAA;AAC3E,IAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AAEzB,IAAA,MAAM,IAAA,GAAO,CAAC,GAAG,iBAAA,EAAmB,GAAG,MAAM,CAAA;AAC7C,IAAA,MAAM,OAAA,GACJ,KAAK,MAAA,GAAS,sBAAA,GACV,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,GAAS,sBAAsB,CAAA,GAC/C,IAAA;AAEN,IAAA,GAAA,CAAI,EAAE,iBAAA,EAAmB,OAAA,EAAS,CAAA;AAAA,EACpC,CAAA;AAAA,EAEA,sBAAA,EAAwB,CAAC,EAAA,KACvB,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,IACd,iBAAA,EAAmB,MAAM,iBAAA,CAAkB,MAAA,CAAO,CAAC,IAAA,KAAS,IAAA,CAAK,OAAO,EAAE;AAAA,GAC5E,CAAE,CAAA;AAAA,EAEJ,wBAAwB,CAAC,aAAA,EAAuB,OAAe,SAAA,KAC7D,GAAA,CAAI,CAAC,KAAA,KAAU;AACb,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,oBAAA,CAAqB,aAAa,CAAA;AAEzD,IAAA,IAAI,QAAA,IAAY,QAAA,CAAS,SAAA,GAAY,SAAA,EAAW;AAC9C,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO;AAAA,MACL,oBAAA,EAAsB;AAAA,QACpB,GAAG,KAAA,CAAM,oBAAA;AAAA,QACT,CAAC,aAAa,GAAG,EAAE,OAAO,SAAA;AAAU;AACtC,KACF;AAAA,EACF,CAAC,CAAA;AAAA,EAEH,wBAAA,EAA0B,CAAC,aAAA,KACzB,GAAA,CAAI,CAAC,KAAA,KAAU;AACb,IAAA,IAAI,EAAE,aAAA,IAAiB,KAAA,CAAM,oBAAA,CAAA,EAAuB,OAAO,KAAA;AAC3D,IAAA,MAAM,IAAA,GAAO,EAAE,GAAG,KAAA,CAAM,oBAAA,EAAqB;AAC7C,IAAA,OAAO,KAAK,aAAa,CAAA;AACzB,IAAA,OAAO,EAAE,sBAAsB,IAAA,EAAK;AAAA,EACtC,CAAC,CAAA;AAAA,EAEH,iBAAA,EAAmB,MAAM,GAAA,CAAI,EAAE,iBAAA,EAAmB,EAAC,EAAG,oBAAA,EAAsB,EAAC,EAAG;AAClF,CAAA,CAAE","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' | 'handRaise' | 'userJoin' | 'userLeft' | 'boardTimerEnd' | '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' | 'reactions';\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 reactions: 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","import { create } from 'zustand';\n\nexport type FloatingReactionT = {\n id: string;\n emoji: string;\n senderId: string;\n senderName: string;\n timestamp: number;\n};\n\nexport type ParticipantReactionT = {\n emoji: string;\n timestamp: number;\n};\n\n// Максимум одновременных всплывающих реакций на экране: если реагирует сразу много\n// участников, лишние (самые старые) вытесняются, чтобы не разрастался DOM/анимации.\n// С учётом пачки (~4 смайлика на одну реакцию) лимит повыше, чтобы не срезать чужие пачки\nconst MAX_FLOATING_REACTIONS = 48;\n\n/** Как долго держится короткая метка реакции на плитке участника */\nexport const PARTICIPANT_REACTION_TTL_MS = 3000;\n\ntype useReactionsStoreT = {\n /** Очередь всплывающих реакций для оверлея поверх сетки видео */\n floatingReactions: FloatingReactionT[];\n /** Последняя активная реакция участника — для бейджа на плитке */\n participantReactions: Record<string, ParticipantReactionT>;\n\n addFloatingReaction: (reaction: FloatingReactionT) => void;\n /** Добавить несколько пузырьков одним апдейтом — иначе N set() рвут анимации на каждом рендере */\n addFloatingReactions: (reactions: FloatingReactionT[]) => void;\n removeFloatingReaction: (id: string) => void;\n setParticipantReaction: (participantId: string, emoji: string, timestamp: number) => void;\n clearParticipantReaction: (participantId: string) => void;\n clearAllReactions: () => void;\n};\n\nexport const useReactionsStore = create<useReactionsStoreT>()((set, get) => ({\n floatingReactions: [],\n participantReactions: {},\n\n addFloatingReaction: (reaction: FloatingReactionT) => {\n get().addFloatingReactions([reaction]);\n },\n\n addFloatingReactions: (reactions: FloatingReactionT[]) => {\n if (reactions.length === 0) return;\n\n const { floatingReactions } = get();\n const existingIds = new Set(floatingReactions.map((item) => item.id));\n const unique = reactions.filter((reaction) => !existingIds.has(reaction.id));\n if (unique.length === 0) return;\n\n const next = [...floatingReactions, ...unique];\n const trimmed =\n next.length > MAX_FLOATING_REACTIONS\n ? next.slice(next.length - MAX_FLOATING_REACTIONS)\n : next;\n\n set({ floatingReactions: trimmed });\n },\n\n removeFloatingReaction: (id: string) =>\n set((state) => ({\n floatingReactions: state.floatingReactions.filter((item) => item.id !== id),\n })),\n\n setParticipantReaction: (participantId: string, emoji: string, timestamp: number) =>\n set((state) => {\n const existing = state.participantReactions[participantId];\n // Не откатываем более свежую реакцию более старым (например, задержанным) сообщением\n if (existing && existing.timestamp > timestamp) {\n return state;\n }\n\n return {\n participantReactions: {\n ...state.participantReactions,\n [participantId]: { emoji, timestamp },\n },\n };\n }),\n\n clearParticipantReaction: (participantId: string) =>\n set((state) => {\n if (!(participantId in state.participantReactions)) return state;\n const next = { ...state.participantReactions };\n delete next[participantId];\n return { participantReactions: next };\n }),\n\n clearAllReactions: () => set({ floatingReactions: [], participantReactions: {} }),\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","../src/store/reactionsStore.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 /** Громкость микрофона (0..1), для preview/тестов и будущей apply в звонке. */\n microphoneVolume?: number;\n /** Громкость динамиков (0..1), для тестового воспроизведения и будущей apply в звонке. */\n speakerVolume?: number;\n /** Зеркальное отражение локального превью камеры. */\n mirrorVideo?: boolean;\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 microphoneVolume: 1,\n speakerVolume: 1,\n mirrorVideo: true,\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' | 'handRaise' | 'userJoin' | 'userLeft' | 'boardTimerEnd' | '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' | 'reactions';\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 reactions: 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","import { create } from 'zustand';\n\nexport type FloatingReactionT = {\n id: string;\n emoji: string;\n senderId: string;\n senderName: string;\n timestamp: number;\n};\n\nexport type ParticipantReactionT = {\n emoji: string;\n timestamp: number;\n};\n\n// Максимум одновременных всплывающих реакций на экране: если реагирует сразу много\n// участников, лишние (самые старые) вытесняются, чтобы не разрастался DOM/анимации.\n// С учётом пачки (~4 смайлика на одну реакцию) лимит повыше, чтобы не срезать чужие пачки\nconst MAX_FLOATING_REACTIONS = 48;\n\n/** Как долго держится короткая метка реакции на плитке участника */\nexport const PARTICIPANT_REACTION_TTL_MS = 3000;\n\ntype useReactionsStoreT = {\n /** Очередь всплывающих реакций для оверлея поверх сетки видео */\n floatingReactions: FloatingReactionT[];\n /** Последняя активная реакция участника — для бейджа на плитке */\n participantReactions: Record<string, ParticipantReactionT>;\n\n addFloatingReaction: (reaction: FloatingReactionT) => void;\n /** Добавить несколько пузырьков одним апдейтом — иначе N set() рвут анимации на каждом рендере */\n addFloatingReactions: (reactions: FloatingReactionT[]) => void;\n removeFloatingReaction: (id: string) => void;\n setParticipantReaction: (participantId: string, emoji: string, timestamp: number) => void;\n clearParticipantReaction: (participantId: string) => void;\n clearAllReactions: () => void;\n};\n\nexport const useReactionsStore = create<useReactionsStoreT>()((set, get) => ({\n floatingReactions: [],\n participantReactions: {},\n\n addFloatingReaction: (reaction: FloatingReactionT) => {\n get().addFloatingReactions([reaction]);\n },\n\n addFloatingReactions: (reactions: FloatingReactionT[]) => {\n if (reactions.length === 0) return;\n\n const { floatingReactions } = get();\n const existingIds = new Set(floatingReactions.map((item) => item.id));\n const unique = reactions.filter((reaction) => !existingIds.has(reaction.id));\n if (unique.length === 0) return;\n\n const next = [...floatingReactions, ...unique];\n const trimmed =\n next.length > MAX_FLOATING_REACTIONS\n ? next.slice(next.length - MAX_FLOATING_REACTIONS)\n : next;\n\n set({ floatingReactions: trimmed });\n },\n\n removeFloatingReaction: (id: string) =>\n set((state) => ({\n floatingReactions: state.floatingReactions.filter((item) => item.id !== id),\n })),\n\n setParticipantReaction: (participantId: string, emoji: string, timestamp: number) =>\n set((state) => {\n const existing = state.participantReactions[participantId];\n // Не откатываем более свежую реакцию более старым (например, задержанным) сообщением\n if (existing && existing.timestamp > timestamp) {\n return state;\n }\n\n return {\n participantReactions: {\n ...state.participantReactions,\n [participantId]: { emoji, timestamp },\n },\n };\n }),\n\n clearParticipantReaction: (participantId: string) =>\n set((state) => {\n if (!(participantId in state.participantReactions)) return state;\n const next = { ...state.participantReactions };\n delete next[participantId];\n return { participantReactions: next };\n }),\n\n clearAllReactions: () => set({ floatingReactions: [], participantReactions: {} }),\n}));\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xipkg/calls-store",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
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.14",
16
- "@xipkg/calls-types": "^0.1.15"
16
+ "@xipkg/calls-types": "^0.1.16"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@tanstack/react-query-devtools": "5.101.4",