@streamlayer/feature-gamification 0.39.0 → 0.40.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/lib/background.d.ts +2 -1
- package/lib/background.js +28 -11
- package/lib/detail.js +3 -0
- package/lib/friendSummary.d.ts +5 -0
- package/lib/friendSummary.js +18 -0
- package/lib/gamification.d.ts +6 -4
- package/lib/gamification.js +104 -25
- package/lib/leaderboard.d.ts +3 -1
- package/lib/leaderboard.js +28 -28
- package/lib/onboarding.js +1 -0
- package/lib/queries/leaderboard.d.ts +2 -1
- package/lib/queries/leaderboard.js +4 -0
- package/lib/userSummary.d.ts +10 -0
- package/lib/userSummary.js +45 -0
- package/package.json +4 -4
package/lib/background.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare class GamificationBackground {
|
|
|
34
34
|
/** opened question statistics */
|
|
35
35
|
openedQuestion: ReturnType<typeof detail>;
|
|
36
36
|
/** last active question in feed */
|
|
37
|
-
activeQuestionId:
|
|
37
|
+
activeQuestionId: ReturnType<typeof queries.$activeQuestion>;
|
|
38
38
|
feedList: ApiStore<GetApiResponseType<typeof queries.$feedList>>;
|
|
39
39
|
/** moderation id */
|
|
40
40
|
moderationId: ReadableAtom<string | undefined>;
|
|
@@ -47,6 +47,7 @@ export declare class GamificationBackground {
|
|
|
47
47
|
private notifications;
|
|
48
48
|
private log;
|
|
49
49
|
private transport;
|
|
50
|
+
private cancels;
|
|
50
51
|
constructor(instance: StreamLayerContext);
|
|
51
52
|
/**
|
|
52
53
|
* Get id for notifications and link with current session
|
package/lib/background.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ApiStore, SingleStore, createSingleStore } from '@streamlayer/sdk-web-interfaces';
|
|
2
2
|
import { createLogger } from '@streamlayer/sdk-web-logger';
|
|
3
|
+
import { QuestionStatus } from '@streamlayer/sdk-web-types';
|
|
3
4
|
import '@streamlayer/sdk-web-core/store';
|
|
4
5
|
import * as queries from './queries';
|
|
5
6
|
import { detail } from './detail';
|
|
@@ -41,6 +42,7 @@ export class GamificationBackground {
|
|
|
41
42
|
notifications;
|
|
42
43
|
log;
|
|
43
44
|
transport;
|
|
45
|
+
cancels = new Set();
|
|
44
46
|
constructor(instance) {
|
|
45
47
|
this.transport = instance.transport;
|
|
46
48
|
this.log = createLogger('gamification-background');
|
|
@@ -53,9 +55,9 @@ export class GamificationBackground {
|
|
|
53
55
|
this.notifications = instance.notifications;
|
|
54
56
|
this.moderation = new ApiStore(queries.$moderation(this.slStreamId, instance.transport), 'gamification:moderation');
|
|
55
57
|
this.feedList = new ApiStore(queries.$feedList(this.slStreamId, this.interactiveAllowed, instance.transport), 'gamification:feedList');
|
|
56
|
-
this.activeQuestionId =
|
|
58
|
+
this.activeQuestionId = queries.$activeQuestion(this.slStreamId, instance.transport);
|
|
57
59
|
this.openedQuestion = detail(instance.transport, this.openedQuestionId, this.feedList.getStore());
|
|
58
|
-
this.openedQuestionId.listen((item) => {
|
|
60
|
+
this.cancels.add(this.openedQuestionId.listen((item) => {
|
|
59
61
|
this.log.debug({ item }, 'received question');
|
|
60
62
|
if (item?.questionId) {
|
|
61
63
|
this.questionSubscription = queries.questionSubscription(item.questionId, instance.transport);
|
|
@@ -72,16 +74,22 @@ export class GamificationBackground {
|
|
|
72
74
|
this.questionSubscription = undefined;
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
|
-
});
|
|
77
|
+
}));
|
|
76
78
|
this.feedSubscription = queries.feedSubscription(this.slStreamId, instance.transport);
|
|
77
|
-
this.feedSubscription.addListener('feed-subscription-active-question', (response) => {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
this.cancels.add(this.feedSubscription.addListener('feed-subscription-active-question', (response) => {
|
|
80
|
+
const activeQuestionId = this.activeQuestionId.get().data?.question?.id;
|
|
81
|
+
const question = response.data?.attributes?.question;
|
|
82
|
+
if (!question) {
|
|
83
|
+
return;
|
|
81
84
|
}
|
|
82
|
-
|
|
85
|
+
// skip update question, avoid race condition
|
|
86
|
+
if (activeQuestionId && question.status === QuestionStatus.RESOLVED && question.id !== activeQuestionId) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.activeQuestionId.mutate(response.data?.attributes);
|
|
90
|
+
}));
|
|
83
91
|
// refresh moderation if question empty, it`s mean that moderation was changed
|
|
84
|
-
this.feedSubscription.addListener('moderation update', (response) => {
|
|
92
|
+
this.cancels.add(this.feedSubscription.addListener('moderation update', (response) => {
|
|
85
93
|
window.requestAnimationFrame(() => {
|
|
86
94
|
if (response.data?.attributes?.question === undefined) {
|
|
87
95
|
if (response.data?.attributes?.moderation) {
|
|
@@ -89,17 +97,26 @@ export class GamificationBackground {
|
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
99
|
});
|
|
92
|
-
});
|
|
100
|
+
}));
|
|
93
101
|
this.feedSubscription.connect();
|
|
94
102
|
/**
|
|
95
103
|
* invalidate active question on interactiveAllowed change
|
|
96
104
|
* close question if interactiveAllowed changed to disallowed
|
|
97
105
|
* open question if interactiveAllowed changed to allowed
|
|
98
106
|
*/
|
|
99
|
-
this.interactiveAllowed.listen(() => {
|
|
107
|
+
this.cancels.add(this.interactiveAllowed.listen(() => {
|
|
100
108
|
window.requestAnimationFrame(() => {
|
|
101
109
|
this.activeQuestionId.invalidate();
|
|
102
110
|
});
|
|
111
|
+
}));
|
|
112
|
+
instance.sdk.onMount(() => {
|
|
113
|
+
return () => {
|
|
114
|
+
this.activeQuestionId.off();
|
|
115
|
+
for (const cancel of this.cancels) {
|
|
116
|
+
cancel();
|
|
117
|
+
this.cancels.delete(cancel);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
103
120
|
});
|
|
104
121
|
}
|
|
105
122
|
/**
|
package/lib/detail.js
CHANGED
|
@@ -38,6 +38,9 @@ export const detail = (transport, $openedQuestionId, $feedList) => {
|
|
|
38
38
|
};
|
|
39
39
|
$extendedStore.mutate({
|
|
40
40
|
...question,
|
|
41
|
+
options: currentQuestion?.options,
|
|
42
|
+
subject: currentQuestion?.subject,
|
|
43
|
+
appearance: currentQuestion?.appearance,
|
|
41
44
|
answers: mergeQuestionAnswers(currentQuestion?.answers, question?.answers),
|
|
42
45
|
});
|
|
43
46
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Transport } from '@streamlayer/sdk-web-api';
|
|
2
|
+
import { ReadableAtom } from 'nanostores';
|
|
3
|
+
import { LeaderboardItem } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
|
|
4
|
+
import { Gamification } from '.';
|
|
5
|
+
export declare const friendSummary: ($eventId: ReadableAtom<string | undefined>, $userId: ReadableAtom<string | undefined>, $friends: Gamification['friends'], friendId: string, transport: Transport) => Promise<LeaderboardItem | undefined>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createUserSummaryFetch } from './queries/leaderboard';
|
|
2
|
+
export const friendSummary = async ($eventId, $userId, $friends, friendId, transport) => {
|
|
3
|
+
const fetch = createUserSummaryFetch(transport);
|
|
4
|
+
const eventId = $eventId.get();
|
|
5
|
+
const userId = $userId.get();
|
|
6
|
+
const usersIds = $friends
|
|
7
|
+
.getStore()
|
|
8
|
+
.get()
|
|
9
|
+
.data?.map((friend) => friend.slId) || [];
|
|
10
|
+
const request = {
|
|
11
|
+
eventId: eventId,
|
|
12
|
+
userId: friendId,
|
|
13
|
+
usersIds: [...usersIds, userId],
|
|
14
|
+
};
|
|
15
|
+
const res = await fetch(request);
|
|
16
|
+
const summary = res.data?.attributes?.summary;
|
|
17
|
+
return summary;
|
|
18
|
+
};
|
package/lib/gamification.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { deepLink } from './deepLink';
|
|
|
10
10
|
import { OnboardingStatus } from './onboarding';
|
|
11
11
|
import { LeaderboardItem } from './queries/leaderboard';
|
|
12
12
|
import { GamificationBackground } from './background';
|
|
13
|
+
import { summary } from './userSummary';
|
|
13
14
|
/**
|
|
14
15
|
* Gamification (Games) Overlay
|
|
15
16
|
* Includes:
|
|
@@ -21,7 +22,7 @@ import { GamificationBackground } from './background';
|
|
|
21
22
|
*/
|
|
22
23
|
export declare class Gamification extends AbstractFeature<'games', PlainMessage<GamesOverlaySettings>> {
|
|
23
24
|
/** user statistics (leaderboard panel) */
|
|
24
|
-
userSummary:
|
|
25
|
+
userSummary: ReturnType<typeof summary>;
|
|
25
26
|
/** feed list (pick history) */
|
|
26
27
|
feedList: ApiStore<GetApiResponseType<typeof queries.$feedList>>;
|
|
27
28
|
/** friends list */
|
|
@@ -54,17 +55,18 @@ export declare class Gamification extends AbstractFeature<'games', PlainMessage<
|
|
|
54
55
|
/** Browser cache */
|
|
55
56
|
private storage;
|
|
56
57
|
private submitAnswerTimeout;
|
|
58
|
+
private cancels;
|
|
57
59
|
constructor(config: FeatureProps, source: FeatureSource, instance: StreamLayerContext);
|
|
58
60
|
get isInteractiveAllowed(): boolean;
|
|
59
61
|
checkInteractiveFlag: () => void;
|
|
60
62
|
connect: () => void;
|
|
61
63
|
disconnect: () => void;
|
|
62
64
|
submitAnswer: (questionId: string, answerId: string) => Promise<void>;
|
|
63
|
-
openQuestion: (questionId
|
|
65
|
+
openQuestion: (questionId?: string, question?: FeedItem & {
|
|
64
66
|
openedFrom?: 'list' | 'notification';
|
|
65
|
-
}) => void;
|
|
67
|
+
}) => void | (() => void);
|
|
66
68
|
closeQuestion: (questionId?: string) => void;
|
|
67
|
-
openUser: (
|
|
69
|
+
openUser: (friendId: string) => Promise<void>;
|
|
68
70
|
closeUser: () => void;
|
|
69
71
|
/**
|
|
70
72
|
* Show in-app notification for active question
|
package/lib/gamification.js
CHANGED
|
@@ -12,6 +12,8 @@ import { OnboardingStatus, onboarding } from './onboarding';
|
|
|
12
12
|
import { GamificationBackground, InteractiveAllowed } from './background';
|
|
13
13
|
import { ERROR } from './constants';
|
|
14
14
|
import { $questionByUser } from './queries';
|
|
15
|
+
import { summary } from './userSummary';
|
|
16
|
+
import { friendSummary } from './friendSummary';
|
|
15
17
|
const InteractiveQuestionTypes = new Set([QuestionType.POLL, QuestionType.PREDICTION, QuestionType.TRIVIA]);
|
|
16
18
|
/**
|
|
17
19
|
* Gamification (Games) Overlay
|
|
@@ -54,6 +56,7 @@ export class Gamification extends AbstractFeature {
|
|
|
54
56
|
/** Browser cache */
|
|
55
57
|
storage;
|
|
56
58
|
submitAnswerTimeout;
|
|
59
|
+
cancels = new Set();
|
|
57
60
|
constructor(config, source, instance) {
|
|
58
61
|
super(config, source);
|
|
59
62
|
this.background = new GamificationBackground(instance);
|
|
@@ -61,7 +64,6 @@ export class Gamification extends AbstractFeature {
|
|
|
61
64
|
this.activeQuestionId = this.background.activeQuestionId;
|
|
62
65
|
this.openedQuestionId = this.background.openedQuestionId;
|
|
63
66
|
this.storage = new GamificationStorage();
|
|
64
|
-
this.userSummary = new ApiStore(queries.$userSummary(this.background.slStreamId, this.background.userId, instance.transport), 'gamification:userSummary');
|
|
65
67
|
this.feedList = this.background.feedList;
|
|
66
68
|
this.friends = new ApiStore(queries.$friends(this.background.userId, instance.transport), 'gamification:friends');
|
|
67
69
|
this.currentUserId = this.background.userId;
|
|
@@ -74,24 +76,48 @@ export class Gamification extends AbstractFeature {
|
|
|
74
76
|
this.openFeature = () => instance.sdk.openFeature(FeatureType.GAMES);
|
|
75
77
|
this.openedQuestion = this.background.openedQuestion;
|
|
76
78
|
this.deepLink = deepLink(this.transport, this.background.slStreamId, instance.stores.providerStreamId.getStore(), this.background.userId);
|
|
79
|
+
this.userSummary = summary(this.background.slStreamId, this.background.userId, this.friends, this.transport);
|
|
77
80
|
this.leaderboardList = leaderboard(this.transport, this.background.slStreamId, this.background.userId, this.friends);
|
|
78
81
|
this.connect();
|
|
79
82
|
// refresh leaderboard on user summary update after earning points
|
|
80
|
-
this.userSummary.listen((userSummary) => {
|
|
81
|
-
if (
|
|
83
|
+
this.cancels.add(this.userSummary.$store.listen((userSummary, prevValue) => {
|
|
84
|
+
if (prevValue?.summary && userSummary?.summary && !userSummary.fromLeaderboard) {
|
|
82
85
|
this.leaderboardList.invalidate(); // verified, it's necessary
|
|
83
86
|
}
|
|
84
|
-
});
|
|
87
|
+
}));
|
|
88
|
+
this.cancels.add(this.leaderboardList.$store.subscribe((leaderboard) => {
|
|
89
|
+
const userSummary = { ...(this.userSummary.$store.get() || {}) };
|
|
90
|
+
const userId = userSummary?.summary?.userId;
|
|
91
|
+
if (leaderboard.data.length && userId) {
|
|
92
|
+
const userRank = leaderboard.data.find((item) => item.userId === userId)?.rank;
|
|
93
|
+
if (userRank !== undefined) {
|
|
94
|
+
if (userSummary?.summary) {
|
|
95
|
+
userSummary.fromLeaderboard = true;
|
|
96
|
+
userSummary.summary.friendsRank = userRank;
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
this.userSummary.$store.set(userSummary);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}));
|
|
85
103
|
/**
|
|
86
104
|
* listen for active question and show in-app notification
|
|
87
105
|
*/
|
|
88
|
-
this.background.activeQuestionId.listen(this.showInApp);
|
|
106
|
+
this.cancels.add(this.background.activeQuestionId.listen(this.showInApp));
|
|
89
107
|
/**
|
|
90
108
|
* listen for onboarding status, moderation onboarding changes and opt-in settings
|
|
91
109
|
*/
|
|
92
|
-
this.onboardingStatus.$store.listen(this.checkInteractiveFlag);
|
|
93
|
-
this.background.moderation.getStore().listen(this.checkInteractiveFlag);
|
|
94
|
-
this.settings.subscribe(this.checkInteractiveFlag);
|
|
110
|
+
this.cancels.add(this.onboardingStatus.$store.listen(this.checkInteractiveFlag));
|
|
111
|
+
this.cancels.add(this.background.moderation.getStore().listen(this.checkInteractiveFlag));
|
|
112
|
+
this.cancels.add(this.settings.subscribe(this.checkInteractiveFlag));
|
|
113
|
+
instance.sdk.onMount(() => {
|
|
114
|
+
return () => {
|
|
115
|
+
for (const cancel of this.cancels) {
|
|
116
|
+
cancel();
|
|
117
|
+
this.cancels.delete(cancel);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
});
|
|
95
121
|
}
|
|
96
122
|
get isInteractiveAllowed() {
|
|
97
123
|
return this.background.interactiveAllowed.get() === InteractiveAllowed.ALLOWED;
|
|
@@ -105,7 +131,7 @@ export class Gamification extends AbstractFeature {
|
|
|
105
131
|
this.background.interactiveAllowed.set(allowed ? InteractiveAllowed.ALLOWED : InteractiveAllowed.DISALLOWED);
|
|
106
132
|
};
|
|
107
133
|
connect = () => {
|
|
108
|
-
this.background.feedSubscription.addListener('feed-subscription-prediction-close', async (response) => {
|
|
134
|
+
this.cancels.add(this.background.feedSubscription.addListener('feed-subscription-prediction-close', async (response) => {
|
|
109
135
|
if (!this.isInteractiveAllowed) {
|
|
110
136
|
return;
|
|
111
137
|
}
|
|
@@ -193,18 +219,23 @@ export class Gamification extends AbstractFeature {
|
|
|
193
219
|
}
|
|
194
220
|
this.userSummary.invalidate(); // verified, it's necessary
|
|
195
221
|
}
|
|
196
|
-
});
|
|
222
|
+
}));
|
|
197
223
|
// update feed list on question update received from subscription
|
|
198
224
|
// add new question to the top of the list
|
|
199
|
-
this.background.feedSubscription.addListener('feed-subscription-questions-list', (response) => {
|
|
225
|
+
this.cancels.add(this.background.feedSubscription.addListener('feed-subscription-questions-list', (response) => {
|
|
200
226
|
const feedList = [...(this.feedList.getStore().value?.data || [])];
|
|
201
227
|
const feedItem = response.data?.attributes?.feedItem;
|
|
202
228
|
const questionIndex = feedList.findIndex((item) => item.id === feedItem?.id);
|
|
203
|
-
if (!feedItem) {
|
|
229
|
+
if (!feedItem?.attributes) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// skip questions with status other than active or resolved
|
|
233
|
+
if (feedItem.attributes.status !== QuestionStatus.ACTIVE &&
|
|
234
|
+
feedItem.attributes.status !== QuestionStatus.RESOLVED) {
|
|
204
235
|
return;
|
|
205
236
|
}
|
|
206
237
|
if (questionIndex !== -1) {
|
|
207
|
-
if (feedItem.attributes
|
|
238
|
+
if (feedItem.attributes.attributes.case === 'question' &&
|
|
208
239
|
feedList[questionIndex].attributes?.attributes.case === 'question') {
|
|
209
240
|
const prev = feedList[questionIndex];
|
|
210
241
|
if (prev.attributes) {
|
|
@@ -235,7 +266,24 @@ export class Gamification extends AbstractFeature {
|
|
|
235
266
|
}
|
|
236
267
|
}
|
|
237
268
|
else {
|
|
238
|
-
feedList[questionIndex]
|
|
269
|
+
const prev = feedList[questionIndex];
|
|
270
|
+
if (prev.attributes && feedItem.attributes) {
|
|
271
|
+
feedList[questionIndex] = {
|
|
272
|
+
...feedList[questionIndex],
|
|
273
|
+
attributes: {
|
|
274
|
+
...prev.attributes,
|
|
275
|
+
attributes: {
|
|
276
|
+
...prev.attributes.attributes,
|
|
277
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
278
|
+
// @ts-ignore
|
|
279
|
+
value: {
|
|
280
|
+
...prev.attributes.attributes.value,
|
|
281
|
+
...feedItem.attributes.attributes.value,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
239
287
|
}
|
|
240
288
|
}
|
|
241
289
|
if (questionIndex === -1) {
|
|
@@ -249,7 +297,7 @@ export class Gamification extends AbstractFeature {
|
|
|
249
297
|
});
|
|
250
298
|
}
|
|
251
299
|
this.feedList.getStore().mutate(feedList);
|
|
252
|
-
});
|
|
300
|
+
}));
|
|
253
301
|
};
|
|
254
302
|
// not used
|
|
255
303
|
disconnect = () => {
|
|
@@ -295,6 +343,9 @@ export class Gamification extends AbstractFeature {
|
|
|
295
343
|
this.feedList.getStore().mutate([...feedList]);
|
|
296
344
|
extendedQuestion.answers[votedAnswerIdx].correct = correctAnswer?.id === answerId;
|
|
297
345
|
extendedQuestion.answers[votedAnswerIdx].youVoted = true;
|
|
346
|
+
extendedQuestion.answers.forEach((answer) => {
|
|
347
|
+
answer.percentageDecimal = 0;
|
|
348
|
+
});
|
|
298
349
|
if (correctAnswer?.id === answerId) {
|
|
299
350
|
extendedQuestion.answers[votedAnswerIdx].pointsEarned =
|
|
300
351
|
extendedQuestion.status === QuestionStatus.RESOLVED ? 0 : correctAnswer.points;
|
|
@@ -324,6 +375,9 @@ export class Gamification extends AbstractFeature {
|
|
|
324
375
|
}
|
|
325
376
|
};
|
|
326
377
|
openQuestion = (questionId, question) => {
|
|
378
|
+
if (!questionId) {
|
|
379
|
+
return () => { };
|
|
380
|
+
}
|
|
327
381
|
this.notifications.close(this.background.getCurrentSessionId({
|
|
328
382
|
prefix: 'notification',
|
|
329
383
|
entity: questionId,
|
|
@@ -346,9 +400,34 @@ export class Gamification extends AbstractFeature {
|
|
|
346
400
|
closeQuestion = (questionId) => {
|
|
347
401
|
return this.background.closeQuestion(questionId);
|
|
348
402
|
};
|
|
349
|
-
openUser = (
|
|
350
|
-
const user = this.leaderboardList.$store.get().data?.find((item) => item.userId ===
|
|
351
|
-
|
|
403
|
+
openUser = async (friendId) => {
|
|
404
|
+
const user = this.leaderboardList.$store.get().data?.find((item) => item.userId === friendId);
|
|
405
|
+
if (!user) {
|
|
406
|
+
this.openedUser.set(user);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (user.summaryLoaded) {
|
|
410
|
+
this.openedUser.set(user);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const userCopy = { ...user };
|
|
414
|
+
try {
|
|
415
|
+
const friendDetail = await friendSummary(this.background.slStreamId, this.background.userId, this.friends, friendId, this.transport);
|
|
416
|
+
if (friendDetail?.inTop !== undefined) {
|
|
417
|
+
this.leaderboardList.$store.setKey('data', this.leaderboardList.$store.get().data?.map((item) => {
|
|
418
|
+
if (item.userId === friendId) {
|
|
419
|
+
item.inTop = friendDetail.inTop;
|
|
420
|
+
}
|
|
421
|
+
return item;
|
|
422
|
+
}));
|
|
423
|
+
userCopy.inTop = friendDetail.inTop;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
console.error(err);
|
|
428
|
+
}
|
|
429
|
+
// @ts-ignore
|
|
430
|
+
this.openedUser.set(userCopy);
|
|
352
431
|
};
|
|
353
432
|
closeUser = () => {
|
|
354
433
|
this.openedUser.set(undefined);
|
|
@@ -371,8 +450,8 @@ export class Gamification extends AbstractFeature {
|
|
|
371
450
|
if (this.isInteractiveAllowed) {
|
|
372
451
|
this.notifications.add({
|
|
373
452
|
type: NotificationType.QUESTION,
|
|
374
|
-
action: () =>
|
|
375
|
-
close: () =>
|
|
453
|
+
action: () => this.openQuestion(question.data?.question?.id, question.data?.feedItem),
|
|
454
|
+
close: () => this.closeQuestion(question.data?.question?.id),
|
|
376
455
|
autoHideDuration: 1000 * 60,
|
|
377
456
|
id: this.background.getCurrentSessionId({
|
|
378
457
|
prefix: 'notification',
|
|
@@ -394,7 +473,7 @@ export class Gamification extends AbstractFeature {
|
|
|
394
473
|
const instantView = {
|
|
395
474
|
heading: question.data.question.notification.title,
|
|
396
475
|
body: question.data.question.notification.body,
|
|
397
|
-
imageMode: optionsValue
|
|
476
|
+
imageMode: optionsValue?.imageMode,
|
|
398
477
|
image: optionsValue?.image,
|
|
399
478
|
video: {
|
|
400
479
|
id: optionsValue?.video?.id || '',
|
|
@@ -408,8 +487,8 @@ export class Gamification extends AbstractFeature {
|
|
|
408
487
|
};
|
|
409
488
|
this.notifications.add({
|
|
410
489
|
type: NotificationType.QUESTION,
|
|
411
|
-
action: () =>
|
|
412
|
-
close: () =>
|
|
490
|
+
action: () => this.openQuestion(question?.data?.question?.id, question?.data?.feedItem),
|
|
491
|
+
close: () => this.closeQuestion(question?.data?.question?.id),
|
|
413
492
|
autoHideDuration: 1000 * 120,
|
|
414
493
|
emitEvent: true,
|
|
415
494
|
id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
|
|
@@ -433,8 +512,8 @@ export class Gamification extends AbstractFeature {
|
|
|
433
512
|
};
|
|
434
513
|
this.notifications.add({
|
|
435
514
|
type: NotificationType.QUESTION,
|
|
436
|
-
action: () =>
|
|
437
|
-
close: () =>
|
|
515
|
+
action: () => this.openQuestion(question.data?.question?.id, question.data?.feedItem),
|
|
516
|
+
close: () => this.closeQuestion(question.data?.question?.id),
|
|
438
517
|
autoHideDuration: 1000 * 120,
|
|
439
518
|
emitEvent: true,
|
|
440
519
|
id: this.background.getCurrentSessionId({ prefix: 'notification', entity: question.data.question.id }),
|
package/lib/leaderboard.d.ts
CHANGED
package/lib/leaderboard.js
CHANGED
|
@@ -34,19 +34,20 @@ export const leaderboard = (transport, $eventId, $userId, $friends, options) =>
|
|
|
34
34
|
const request = {
|
|
35
35
|
eventId: eventId,
|
|
36
36
|
usersIds: friendsIds,
|
|
37
|
-
pagination: { page: 0, pageSize: options?.pageSize || defaultOptions.pageSize },
|
|
38
37
|
};
|
|
39
38
|
const newData = await fetch(request);
|
|
40
39
|
$store.set({
|
|
41
|
-
data: newData.data.map((item) =>
|
|
42
|
-
|
|
40
|
+
data: newData.data.map((item, i) => ({
|
|
41
|
+
...item.attributes,
|
|
42
|
+
rank: i + 1,
|
|
43
|
+
})),
|
|
44
|
+
hasMore: false,
|
|
43
45
|
key: Date.now(),
|
|
44
46
|
loading: false,
|
|
45
47
|
});
|
|
46
48
|
if (newData.meta) {
|
|
47
49
|
maxPage = Math.round(newData.meta.count / newData.meta.pageSize);
|
|
48
50
|
}
|
|
49
|
-
$pagination.set(request.pagination);
|
|
50
51
|
}
|
|
51
52
|
};
|
|
52
53
|
const invalidate = () => {
|
|
@@ -65,33 +66,32 @@ export const leaderboard = (transport, $eventId, $userId, $friends, options) =>
|
|
|
65
66
|
onMount($store, () => {
|
|
66
67
|
const cancelRefetchListener = $eventId.listen(refetch);
|
|
67
68
|
const cancelRefetchByFriendsListener = $friends.listen(refetch);
|
|
68
|
-
const cancelPaginationListener = $pagination.listen(async (pagination) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
69
|
+
// const cancelPaginationListener = $pagination.listen(async (pagination) => {
|
|
70
|
+
// const eventId = $eventId.get()
|
|
71
|
+
// if (pagination.page > 0 && eventId) {
|
|
72
|
+
// if (pagination.page < maxPage) {
|
|
73
|
+
// $store.setKey('loading', true)
|
|
74
|
+
// const request = {
|
|
75
|
+
// eventId: eventId as unknown as bigint,
|
|
76
|
+
// pagination,
|
|
77
|
+
// }
|
|
78
|
+
// const newData = await fetch(request)
|
|
79
|
+
// const prevData = $store.get().data || []
|
|
80
|
+
// $store.set({
|
|
81
|
+
// data: [...prevData, ...newData.data.map((item) => item.attributes as LeaderboardItem)],
|
|
82
|
+
// key: $store.get().key,
|
|
83
|
+
// loading: false,
|
|
84
|
+
// hasMore: true,
|
|
85
|
+
// })
|
|
86
|
+
// } else {
|
|
87
|
+
// $store.setKey('hasMore', false)
|
|
88
|
+
// }
|
|
89
|
+
// }
|
|
90
|
+
// })
|
|
91
91
|
return () => {
|
|
92
92
|
cancelRefetchListener();
|
|
93
93
|
cancelRefetchByFriendsListener();
|
|
94
|
-
cancelPaginationListener()
|
|
94
|
+
// cancelPaginationListener()
|
|
95
95
|
};
|
|
96
96
|
});
|
|
97
97
|
return { $store, fetchMore, invalidate };
|
package/lib/onboarding.js
CHANGED
|
@@ -154,6 +154,7 @@ export const onboarding = (service, background, transport, notifications) => {
|
|
|
154
154
|
action: 'onboardingPassed',
|
|
155
155
|
payload: {},
|
|
156
156
|
});
|
|
157
|
+
service.openFeature();
|
|
157
158
|
const notificationId = background.getCurrentSessionId({ prefix: 'onboarding' });
|
|
158
159
|
notifications.close(notificationId);
|
|
159
160
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Transport } from '@streamlayer/sdk-web-api';
|
|
2
2
|
import { ReadableAtom } from 'nanostores';
|
|
3
|
-
import { ListRequest } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
|
|
3
|
+
import { ListRequest, SummaryRequest } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
|
|
4
4
|
import { PartialMessage } from '@bufbuild/protobuf';
|
|
5
5
|
export { LeaderboardItem } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
|
|
6
6
|
export declare const $userSummary: ($eventId: ReadableAtom<string | undefined>, $userId: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb").LeaderboardSummaryItem | undefined, any>;
|
|
7
7
|
export declare const $leaderboardList: ($eventId: ReadableAtom<string | undefined>, _: ReadableAtom<string | undefined>, transport: Transport) => import("@nanostores/query").FetcherStore<import("@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb").ListResponse_ListResponseData[], any>;
|
|
8
8
|
export declare const createLeaderboardListFetch: (transport: Transport) => (params: PartialMessage<ListRequest>) => Promise<import("@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb").ListResponse>;
|
|
9
|
+
export declare const createUserSummaryFetch: (transport: Transport) => (params: PartialMessage<SummaryRequest>) => Promise<import("@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb").SummaryResponse>;
|
|
@@ -34,3 +34,7 @@ export const createLeaderboardListFetch = (transport) => {
|
|
|
34
34
|
const { client } = transport.createPromiseClient(Leaderboard, { method: 'list' });
|
|
35
35
|
return (params) => client.list(params);
|
|
36
36
|
};
|
|
37
|
+
export const createUserSummaryFetch = (transport) => {
|
|
38
|
+
const { client } = transport.createPromiseClient(Leaderboard, { method: 'summary' });
|
|
39
|
+
return (params) => client.summary(params);
|
|
40
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Transport } from '@streamlayer/sdk-web-api';
|
|
2
|
+
import { ReadableAtom } from 'nanostores';
|
|
3
|
+
import { LeaderboardSummaryItem } from '@streamlayer/sl-eslib/interactive/leaderboard/interactive.leaderboard_pb';
|
|
4
|
+
import { Gamification } from '.';
|
|
5
|
+
export declare const summary: ($eventId: ReadableAtom<string | undefined>, $userId: ReadableAtom<string | undefined>, $friends: Gamification['friends'], transport: Transport) => {
|
|
6
|
+
$store: import("nanostores").MapStore<(LeaderboardSummaryItem & {
|
|
7
|
+
fromLeaderboard?: boolean | undefined;
|
|
8
|
+
}) | undefined>;
|
|
9
|
+
invalidate: () => void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createMapStore } from '@streamlayer/sdk-web-interfaces';
|
|
2
|
+
import { onMount } from 'nanostores';
|
|
3
|
+
import { createUserSummaryFetch } from './queries/leaderboard';
|
|
4
|
+
export const summary = ($eventId, $userId, $friends, transport) => {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
const $store = createMapStore(undefined);
|
|
8
|
+
const fetch = createUserSummaryFetch(transport);
|
|
9
|
+
const refetch = async () => {
|
|
10
|
+
const eventId = $eventId.get();
|
|
11
|
+
const userId = $userId.get();
|
|
12
|
+
const usersIds = $friends
|
|
13
|
+
.getStore()
|
|
14
|
+
.get()
|
|
15
|
+
.data?.map((friend) => friend.slId);
|
|
16
|
+
if (!usersIds) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const request = {
|
|
20
|
+
eventId: eventId,
|
|
21
|
+
userId: userId,
|
|
22
|
+
usersIds: [...usersIds, userId],
|
|
23
|
+
};
|
|
24
|
+
const res = await fetch(request);
|
|
25
|
+
const prevData = $store.get()?.summary?.friendsRank;
|
|
26
|
+
if (res.data?.attributes?.summary?.friendsRank && prevData !== undefined) {
|
|
27
|
+
res.data.attributes.summary.friendsRank = prevData;
|
|
28
|
+
}
|
|
29
|
+
$store.set(res.data?.attributes);
|
|
30
|
+
};
|
|
31
|
+
const invalidate = () => {
|
|
32
|
+
void refetch();
|
|
33
|
+
};
|
|
34
|
+
onMount($store, () => {
|
|
35
|
+
const cancelRefetchListener = $eventId.listen(refetch);
|
|
36
|
+
const cancelRefetchByFriendsListener = $friends.listen(refetch);
|
|
37
|
+
const cancelRefetchByUserListener = $userId.listen(refetch);
|
|
38
|
+
return () => {
|
|
39
|
+
cancelRefetchListener();
|
|
40
|
+
cancelRefetchByFriendsListener();
|
|
41
|
+
cancelRefetchByUserListener();
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
return { $store, invalidate };
|
|
45
|
+
};
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamlayer/feature-gamification",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@bufbuild/protobuf": "^1.7.2",
|
|
6
6
|
"@fastify/deepmerge": "^1.3.0",
|
|
7
7
|
"@streamlayer/sl-eslib": "^5.83.1",
|
|
8
8
|
"nanostores": "^0.10.0",
|
|
9
|
-
"@streamlayer/sdk-web-api": "^0.24.
|
|
10
|
-
"@streamlayer/sdk-web-core": "^0.22.
|
|
9
|
+
"@streamlayer/sdk-web-api": "^0.24.2",
|
|
10
|
+
"@streamlayer/sdk-web-core": "^0.22.2",
|
|
11
11
|
"@streamlayer/sdk-web-interfaces": "^0.21.0",
|
|
12
12
|
"@streamlayer/sdk-web-logger": "^0.5.18",
|
|
13
|
-
"@streamlayer/sdk-web-notifications": "^0.15.
|
|
13
|
+
"@streamlayer/sdk-web-notifications": "^0.15.1",
|
|
14
14
|
"@streamlayer/sdk-web-storage": "^0.4.5",
|
|
15
15
|
"@streamlayer/sdk-web-types": "^0.23.0"
|
|
16
16
|
},
|