@streamlayer/feature-gamification 0.19.5 → 0.21.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.
@@ -3,6 +3,7 @@ import type { GetApiResponseType } from '@streamlayer/sdk-web-api';
3
3
  import '@streamlayer/sdk-web-core/store';
4
4
  import { ReadableAtom, WritableAtom } from 'nanostores';
5
5
  import * as queries from './queries';
6
+ import { detail } from './detail';
6
7
  /**
7
8
  * get GamificationBackground singleton
8
9
  */
@@ -20,9 +21,10 @@ export declare class GamificationBackground {
20
21
  /** opened question, using to download statistics */
21
22
  openedQuestionId: WritableAtom<string | undefined>;
22
23
  /** opened question statistics */
23
- openedQuestion: ApiStore<GetApiResponseType<typeof queries.$questionByUser>>;
24
+ openedQuestion: ReturnType<typeof detail>;
24
25
  /** last active question in feed */
25
26
  activeQuestionId: ApiStore<GetApiResponseType<typeof queries.$activeQuestion>>;
27
+ feedList: ApiStore<GetApiResponseType<typeof queries.$feedList>>;
26
28
  /** moderation id */
27
29
  moderationId: ReadableAtom<string | undefined>;
28
30
  /** moderation */
package/lib/background.js CHANGED
@@ -2,6 +2,7 @@ import { ApiStore, SingleStore, createSingleStore } from '@streamlayer/sdk-web-i
2
2
  import { createLogger } from '@streamlayer/sdk-web-logger';
3
3
  import '@streamlayer/sdk-web-core/store';
4
4
  import * as queries from './queries';
5
+ import { detail } from './detail';
5
6
  /**
6
7
  * get GamificationBackground singleton
7
8
  */
@@ -27,6 +28,7 @@ export class GamificationBackground {
27
28
  openedQuestion;
28
29
  /** last active question in feed */
29
30
  activeQuestionId;
31
+ feedList;
30
32
  /** moderation id */
31
33
  moderationId;
32
34
  /** moderation */
@@ -49,15 +51,16 @@ export class GamificationBackground {
49
51
  this.openedQuestionId = new SingleStore(createSingleStore(undefined), 'openedQuestionId').getStore();
50
52
  this.notifications = instance.notifications;
51
53
  this.moderation = new ApiStore(queries.$moderation(this.slStreamId, instance.transport), 'gamification:moderation');
54
+ this.feedList = new ApiStore(queries.$feedList(this.slStreamId, instance.transport), 'gamification:feedList');
52
55
  this.activeQuestionId = new ApiStore(queries.$activeQuestion(this.slStreamId, instance.transport), 'gamification:activeQuestionId');
53
- this.openedQuestion = new ApiStore(queries.$questionByUser(this.openedQuestionId, instance.transport), 'gamification:openedQuestion');
56
+ this.openedQuestion = detail(instance.transport, this.openedQuestionId, this.feedList.getStore());
54
57
  this.openedQuestionId.listen((questionId) => {
55
58
  this.log.debug({ questionId }, 'received question');
56
59
  if (questionId) {
57
60
  this.questionSubscription = queries.questionSubscription(questionId, instance.transport);
58
61
  this.questionSubscription.addListener('feed-subscription-opened-question', (response) => {
59
62
  window.requestAnimationFrame(() => {
60
- this.openedQuestion.getStore().mutate(response.data?.attributes?.question);
63
+ this.openedQuestion.updateExtendedQuestion(response.data?.attributes?.question);
61
64
  });
62
65
  });
63
66
  this.questionSubscription.connect();
@@ -0,0 +1,16 @@
1
+ import type { Transport } from '@streamlayer/sdk-web-api';
2
+ import { ReadableAtom } from 'nanostores';
3
+ import { getQuestionByUser } from './queries';
4
+ import { type GamificationBackground } from './background';
5
+ type ExtendedQuestion = Awaited<ReturnType<typeof getQuestionByUser>>;
6
+ type ExtendedQuestionStore = {
7
+ data?: ExtendedQuestion;
8
+ loading?: boolean;
9
+ error?: string;
10
+ };
11
+ export declare const detail: (transport: Transport, $openedQuestionId: ReadableAtom<string | undefined>, $feedList: ReturnType<GamificationBackground['feedList']['getStore']>) => {
12
+ $store: ReadableAtom<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem | undefined>;
13
+ $extendedStore: import("nanostores").MapStore<ExtendedQuestionStore>;
14
+ updateExtendedQuestion: (question: ExtendedQuestion) => void;
15
+ };
16
+ export {};
package/lib/detail.js ADDED
@@ -0,0 +1,33 @@
1
+ import { createMapStore } from '@streamlayer/sdk-web-interfaces';
2
+ import { computed } from 'nanostores';
3
+ import { getQuestionByUser } from './queries';
4
+ export const detail = (transport, $openedQuestionId, $feedList) => {
5
+ const $store = computed([$openedQuestionId, $feedList], (openedQuestion, feedList) => {
6
+ if (openedQuestion) {
7
+ if (feedList.data) {
8
+ return feedList.data.find((item) => item.id === openedQuestion);
9
+ }
10
+ }
11
+ return undefined;
12
+ });
13
+ const $extendedStore = createMapStore({
14
+ data: undefined,
15
+ loading: undefined,
16
+ error: undefined,
17
+ });
18
+ $store.subscribe(async (item) => {
19
+ if (item) {
20
+ if (item.type === 'question') {
21
+ $extendedStore.setKey('loading', true);
22
+ const question = await getQuestionByUser(item.id, transport);
23
+ $extendedStore.set({ data: question, loading: false });
24
+ return;
25
+ }
26
+ }
27
+ $extendedStore.set({ data: undefined, loading: false });
28
+ });
29
+ const updateExtendedQuestion = (question) => {
30
+ $extendedStore.set({ data: question });
31
+ };
32
+ return { $store, $extendedStore, updateExtendedQuestion };
33
+ };
@@ -1,11 +1,13 @@
1
- import { AbstractFeature, ApiStore, FeatureSource, type FeatureProps, type StreamLayerContext, createComputedStore } from '@streamlayer/sdk-web-interfaces';
2
- import { type GamesOverlaySettings, ExtendedQuestion } from '@streamlayer/sdk-web-types';
1
+ import { AbstractFeature, ApiStore, FeatureSource, type FeatureProps, type StreamLayerContext } from '@streamlayer/sdk-web-interfaces';
2
+ import { type GamesOverlaySettings } from '@streamlayer/sdk-web-types';
3
3
  import type { GetApiResponseType } from '@streamlayer/sdk-web-api';
4
4
  import '@streamlayer/sdk-web-core/store';
5
5
  import type { PlainMessage } from '@bufbuild/protobuf';
6
6
  import { WritableAtom } from 'nanostores';
7
7
  import * as queries from './queries';
8
8
  import { leaderboard } from './leaderboard';
9
+ import { LeaderboardItem } from './queries/leaderboard';
10
+ import { GamificationBackground } from './';
9
11
  /**
10
12
  * Required: in-app should be displayed and questions not available
11
13
  * Optional: in-app should be displayed but questions are available
@@ -33,8 +35,8 @@ export declare enum OnboardingStatus {
33
35
  export declare class Gamification extends AbstractFeature<'games', PlainMessage<GamesOverlaySettings>> {
34
36
  /** user statistics (leaderboard panel) */
35
37
  userSummary: ApiStore<GetApiResponseType<typeof queries.$userSummary>>;
36
- /** questions list (pick history) */
37
- questions: ApiStore<GetApiResponseType<typeof queries.$pickHistory>>;
38
+ /** feed list (pick history) */
39
+ feedList: ApiStore<GetApiResponseType<typeof queries.$feedList>>;
38
40
  /** pinned leaderboard id */
39
41
  leaderboardId: WritableAtom<string | undefined>;
40
42
  /** leaderboard list */
@@ -42,10 +44,9 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
42
44
  /** onboarding status */
43
45
  onboardingStatus: WritableAtom<OnboardingStatus | undefined>;
44
46
  /** opened question */
45
- openedQuestion: ReturnType<typeof createComputedStore<{
46
- loading: boolean;
47
- data?: ExtendedQuestion;
48
- }>>;
47
+ openedQuestion: GamificationBackground['openedQuestion'];
48
+ /** pinned leaderboard id */
49
+ openedUser: WritableAtom<LeaderboardItem | undefined>;
49
50
  private notifications;
50
51
  private transport;
51
52
  private closeFeature;
@@ -68,4 +69,6 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
68
69
  skipQuestion: (questionId: string) => Promise<void>;
69
70
  openQuestion: (questionId: string) => void;
70
71
  closeQuestion: (questionId?: string) => void;
72
+ openUser: (userId: string) => void;
73
+ closeUser: () => void;
71
74
  }
@@ -1,4 +1,4 @@
1
- import { AbstractFeature, ApiStore, FeatureStatus, SingleStore, createSingleStore, createComputedStore, } from '@streamlayer/sdk-web-interfaces';
1
+ import { AbstractFeature, ApiStore, FeatureStatus, SingleStore, createSingleStore, } from '@streamlayer/sdk-web-interfaces';
2
2
  import { QuestionStatus, QuestionType, FeatureType, SilenceSetting, } from '@streamlayer/sdk-web-types';
3
3
  import { NotificationType } from '@streamlayer/sdk-web-notifications';
4
4
  import '@streamlayer/sdk-web-core/store';
@@ -36,8 +36,8 @@ export var OnboardingStatus;
36
36
  export class Gamification extends AbstractFeature {
37
37
  /** user statistics (leaderboard panel) */
38
38
  userSummary;
39
- /** questions list (pick history) */
40
- questions;
39
+ /** feed list (pick history) */
40
+ feedList;
41
41
  /** pinned leaderboard id */
42
42
  leaderboardId;
43
43
  /** leaderboard list */
@@ -46,6 +46,8 @@ export class Gamification extends AbstractFeature {
46
46
  onboardingStatus;
47
47
  /** opened question */
48
48
  openedQuestion;
49
+ /** pinned leaderboard id */
50
+ openedUser;
49
51
  notifications;
50
52
  transport;
51
53
  closeFeature;
@@ -59,19 +61,15 @@ export class Gamification extends AbstractFeature {
59
61
  this.background = gamificationBackground(instance);
60
62
  this.storage = new GamificationStorage();
61
63
  this.userSummary = new ApiStore(queries.$userSummary(this.background.slStreamId, this.background.userId, instance.transport), 'gamification:userSummary');
62
- this.questions = new ApiStore(queries.$pickHistory(this.background.slStreamId, instance.transport), 'gamification:questions');
64
+ this.feedList = this.background.feedList;
65
+ this.openedUser = createSingleStore(undefined);
63
66
  this.leaderboardId = new SingleStore(createSingleStore(this.settings.getValue('pinnedLeaderboardId')), 'pinnedLeaderboardId').getStore();
64
67
  this.onboardingStatus = new SingleStore(createSingleStore(OnboardingStatus.Unset), 'onboardingStatus').getStore();
65
68
  this.notifications = instance.notifications;
66
69
  this.transport = instance.transport;
67
70
  this.closeFeature = instance.sdk.closeFeature;
68
71
  this.openFeature = () => instance.sdk.openFeature(FeatureType.GAMES);
69
- this.openedQuestion = createComputedStore(this.background.openedQuestion.getStore(), (openedQuestion) => {
70
- if (openedQuestion.data?.type && GamificationQuestionTypes.has(openedQuestion.data.type)) {
71
- return { loading: false, data: openedQuestion.data };
72
- }
73
- return { loading: openedQuestion.loading, data: undefined };
74
- });
72
+ this.openedQuestion = this.background.openedQuestion;
75
73
  this.leaderboardList = leaderboard(this.transport, this.background.slStreamId);
76
74
  this.onboardingStatus.subscribe((onboardingStatus) => {
77
75
  if (onboardingStatus === OnboardingStatus.Optional || OnboardingStatus.Required) {
@@ -194,7 +192,7 @@ export class Gamification extends AbstractFeature {
194
192
  connect = (transport) => {
195
193
  this.userSummary.invalidate();
196
194
  this.leaderboardList.invalidate();
197
- this.questions.invalidate();
195
+ this.feedList.invalidate();
198
196
  this.background.feedSubscription.addListener('feed-subscription-prediction-close', (response) => {
199
197
  window.requestAnimationFrame(async () => {
200
198
  const question = response.data?.attributes?.question;
@@ -225,7 +223,7 @@ export class Gamification extends AbstractFeature {
225
223
  });
226
224
  this.background.feedSubscription.addListener('feed-subscription-questions-list', () => {
227
225
  window.requestAnimationFrame(() => {
228
- this.questions?.invalidate();
226
+ this.feedList?.invalidate();
229
227
  });
230
228
  });
231
229
  };
@@ -248,11 +246,11 @@ export class Gamification extends AbstractFeature {
248
246
  submitAnswer = async (questionId, answerId) => {
249
247
  await actions.submitAnswer(this.transport, { questionId, answerId });
250
248
  // Todo: add invalidate openedQuestion
251
- this.questions?.invalidate();
249
+ this.feedList?.invalidate();
252
250
  };
253
251
  skipQuestion = async (questionId) => {
254
252
  await actions.skipQuestion(this.transport, questionId);
255
- this.questions?.invalidate();
253
+ this.feedList?.invalidate();
256
254
  };
257
255
  openQuestion = (questionId) => {
258
256
  this.openFeature();
@@ -261,4 +259,11 @@ export class Gamification extends AbstractFeature {
261
259
  closeQuestion = (questionId) => {
262
260
  return this.background.closeQuestion(questionId);
263
261
  };
262
+ openUser = (userId) => {
263
+ const user = this.leaderboardList.$store.get().data?.find((item) => item.userId === userId);
264
+ this.openedUser.set(user);
265
+ };
266
+ closeUser = () => {
267
+ this.openedUser.set(undefined);
268
+ };
264
269
  }
@@ -1,13 +1,13 @@
1
- import { AbstractFeature, ApiStore, FeatureSource, type FeatureProps, type StreamLayerContext, createComputedStore } from '@streamlayer/sdk-web-interfaces';
2
- import { ExtendedQuestion } from '@streamlayer/sdk-web-types';
1
+ import { AbstractFeature, ApiStore, FeatureSource, type FeatureProps, type StreamLayerContext } from '@streamlayer/sdk-web-interfaces';
3
2
  import type { GetApiResponseType } from '@streamlayer/sdk-web-api';
4
3
  import '@streamlayer/sdk-web-core/store';
5
4
  import * as queries from './queries';
5
+ import { GamificationBackground } from './';
6
6
  export declare class Highlights extends AbstractFeature<undefined> {
7
7
  insights?: ApiStore<GetApiResponseType<typeof queries.$insightHistory>>;
8
8
  closeFeature: () => void;
9
9
  openFeature: () => void;
10
- openedInsight: ReturnType<typeof createComputedStore<ExtendedQuestion | undefined>>;
10
+ openedInsight: GamificationBackground['openedQuestion'];
11
11
  private notifications;
12
12
  private transport;
13
13
  private background;
package/lib/highlights.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AbstractFeature, ApiStore, FeatureStatus, createComputedStore, } from '@streamlayer/sdk-web-interfaces';
1
+ import { AbstractFeature, ApiStore, FeatureStatus, } from '@streamlayer/sdk-web-interfaces';
2
2
  import { QuestionStatus, QuestionType, FeatureType, SilenceSetting } from '@streamlayer/sdk-web-types';
3
3
  import { NotificationType } from '@streamlayer/sdk-web-notifications';
4
4
  import '@streamlayer/sdk-web-core/store';
@@ -49,12 +49,7 @@ export class Highlights extends AbstractFeature {
49
49
  });
50
50
  }
51
51
  });
52
- this.openedInsight = createComputedStore(this.background.openedQuestion.getStore(), (openedQuestion) => {
53
- if (openedQuestion.data?.type === QuestionType.FACTOID) {
54
- return openedQuestion.data;
55
- }
56
- return undefined;
57
- });
52
+ this.openedInsight = this.background.openedQuestion;
58
53
  }
59
54
  connect = () => {
60
55
  if (!this.insights) {
@@ -329,6 +329,7 @@ export declare const questionSubscription: (questionId: string, transport: Trans
329
329
  export declare const getQuestionByUser: (questionId: string, transport: Transport) => Promise<import("packages/sdk-web-types/lib").ExtendedQuestion | undefined>;
330
330
  export declare const $questionByUser: ($questionId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("packages/sdk-web-types/lib").ExtendedQuestion | undefined, any>;
331
331
  export declare const $pickHistory: (slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<(import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").PickHistory | undefined)[], any>;
332
+ export declare const $feedList: (slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").FeedItem[], any>;
332
333
  export declare const $insightHistory: (slStreamId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<(import("@streamlayer/sl-eslib/interactive/feed/interactive.feed_pb").InsightHistory | undefined)[] | undefined, any>;
333
334
  export { $userSummary, $leaderboardList } from './leaderboard';
334
335
  export { $moderation } from './moderation';
@@ -67,6 +67,20 @@ export const $pickHistory = (slStreamId, transport) => {
67
67
  },
68
68
  });
69
69
  };
70
+ export const $feedList = (slStreamId, transport) => {
71
+ const { client, queryKey } = transport.createPromiseClient(Feed, { method: 'list', params: [slStreamId] });
72
+ return transport.nanoquery.createFetcherStore(queryKey, {
73
+ fetcher: async (_, __, eventId) => {
74
+ if (!eventId) {
75
+ return [];
76
+ }
77
+ const res = await client.list({
78
+ eventId: eventId,
79
+ });
80
+ return res.data;
81
+ },
82
+ });
83
+ };
70
84
  export const $insightHistory = (slStreamId, transport) => {
71
85
  const { client, queryKey } = transport.createPromiseClient(Feed, { method: 'insightHistory', params: [slStreamId] });
72
86
  return transport.nanoquery.createFetcherStore(queryKey, {
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@streamlayer/feature-gamification",
3
- "version": "0.19.5",
3
+ "version": "0.21.0",
4
4
  "peerDependencies": {
5
5
  "@bufbuild/protobuf": "^1.4.2",
6
6
  "@streamlayer/sl-eslib": "^5.53.5",
7
7
  "nanostores": "^0.9.5",
8
- "@streamlayer/sdk-web-interfaces": "^0.18.9",
9
- "@streamlayer/sdk-web-core": "^0.17.2",
8
+ "@streamlayer/sdk-web-interfaces": "^0.18.10",
9
+ "@streamlayer/sdk-web-core": "^0.17.3",
10
10
  "@streamlayer/sdk-web-api": "^0.0.1",
11
- "@streamlayer/sdk-web-types": "^0.17.1",
11
+ "@streamlayer/sdk-web-types": "^0.18.0",
12
12
  "@streamlayer/sdk-web-storage": "^0.3.10",
13
- "@streamlayer/sdk-web-logger": "^0.5.10",
14
- "@streamlayer/sdk-web-notifications": "^0.10.8"
13
+ "@streamlayer/sdk-web-notifications": "^0.10.9",
14
+ "@streamlayer/sdk-web-logger": "^0.5.10"
15
15
  },
16
16
  "devDependencies": {
17
17
  "tslib": "^2.6.2"