@magmamath/students-features 0.6.7-rc.2 → 0.6.7-rc.4
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/commonjs/features/endOfAssignment/components/Modal/Modal.js +0 -1
- package/dist/commonjs/features/endOfAssignment/components/Modal/Modal.js.map +1 -1
- package/dist/commonjs/features/gifCelebrations/GifCelebrations.js +62 -0
- package/dist/commonjs/features/gifCelebrations/GifCelebrations.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/assets/confetti.webp +0 -0
- package/dist/commonjs/features/gifCelebrations/assets/gifFallback.gif +0 -0
- package/dist/commonjs/features/gifCelebrations/components/Backdrop.js +44 -0
- package/dist/commonjs/features/gifCelebrations/components/Backdrop.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/components/ContentContainer.js +46 -0
- package/dist/commonjs/features/gifCelebrations/components/ContentContainer.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/components/Gif.js +90 -0
- package/dist/commonjs/features/gifCelebrations/components/Gif.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/components/Progress.js +96 -0
- package/dist/commonjs/features/gifCelebrations/components/Progress.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/constants.js +34 -0
- package/dist/commonjs/features/gifCelebrations/constants.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/helpers.js +114 -0
- package/dist/commonjs/features/gifCelebrations/helpers.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/index.js +20 -0
- package/dist/commonjs/features/gifCelebrations/index.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/model/GifCelebrations.cache.js +30 -0
- package/dist/commonjs/features/gifCelebrations/model/GifCelebrations.cache.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/model/GifCelebrations.model.js +138 -0
- package/dist/commonjs/features/gifCelebrations/model/GifCelebrations.model.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/model/GifCelebrationsApi.js +27 -0
- package/dist/commonjs/features/gifCelebrations/model/GifCelebrationsApi.js.map +1 -0
- package/dist/commonjs/features/gifCelebrations/types/model.types.js +6 -0
- package/dist/commonjs/features/gifCelebrations/types/model.types.js.map +1 -0
- package/dist/commonjs/index.js +12 -0
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/lib/effector/createControllerEffect.js.map +1 -1
- package/dist/commonjs/lib/helpers/helpers.js.map +1 -1
- package/dist/commonjs/react-app-env.d.js +2 -0
- package/dist/commonjs/react-app-env.d.js.map +1 -0
- package/dist/commonjs/shared/translation/localization/en.json +5 -0
- package/dist/commonjs/shared/translation/localization/gb.json +5 -0
- package/dist/commonjs/shared/translation/localization/sw.json +5 -0
- package/dist/commonjs/types/common.types.js +2 -0
- package/dist/commonjs/types/common.types.js.map +1 -0
- package/dist/module/features/endOfAssignment/components/Modal/Modal.js +1 -2
- package/dist/module/features/endOfAssignment/components/Modal/Modal.js.map +1 -1
- package/dist/module/features/gifCelebrations/GifCelebrations.js +55 -0
- package/dist/module/features/gifCelebrations/GifCelebrations.js.map +1 -0
- package/dist/module/features/gifCelebrations/assets/confetti.webp +0 -0
- package/dist/module/features/gifCelebrations/assets/gifFallback.gif +0 -0
- package/dist/module/features/gifCelebrations/components/Backdrop.js +36 -0
- package/dist/module/features/gifCelebrations/components/Backdrop.js.map +1 -0
- package/dist/module/features/gifCelebrations/components/ContentContainer.js +38 -0
- package/dist/module/features/gifCelebrations/components/ContentContainer.js.map +1 -0
- package/dist/module/features/gifCelebrations/components/Gif.js +82 -0
- package/dist/module/features/gifCelebrations/components/Gif.js.map +1 -0
- package/dist/module/features/gifCelebrations/components/Progress.js +88 -0
- package/dist/module/features/gifCelebrations/components/Progress.js.map +1 -0
- package/dist/module/features/gifCelebrations/constants.js +30 -0
- package/dist/module/features/gifCelebrations/constants.js.map +1 -0
- package/dist/module/features/gifCelebrations/helpers.js +103 -0
- package/dist/module/features/gifCelebrations/helpers.js.map +1 -0
- package/dist/module/features/gifCelebrations/index.js +5 -0
- package/dist/module/features/gifCelebrations/index.js.map +1 -0
- package/dist/module/features/gifCelebrations/model/GifCelebrations.cache.js +25 -0
- package/dist/module/features/gifCelebrations/model/GifCelebrations.cache.js.map +1 -0
- package/dist/module/features/gifCelebrations/model/GifCelebrations.model.js +133 -0
- package/dist/module/features/gifCelebrations/model/GifCelebrations.model.js.map +1 -0
- package/dist/module/features/gifCelebrations/model/GifCelebrationsApi.js +22 -0
- package/dist/module/features/gifCelebrations/model/GifCelebrationsApi.js.map +1 -0
- package/dist/module/features/gifCelebrations/types/model.types.js +4 -0
- package/dist/module/features/gifCelebrations/types/model.types.js.map +1 -0
- package/dist/module/index.js +1 -0
- package/dist/module/index.js.map +1 -1
- package/dist/module/lib/effector/createControllerEffect.js.map +1 -1
- package/dist/module/react-app-env.d.js +2 -0
- package/dist/module/react-app-env.d.js.map +1 -0
- package/dist/module/shared/translation/localization/en.json +5 -0
- package/dist/module/shared/translation/localization/gb.json +5 -0
- package/dist/module/shared/translation/localization/sw.json +5 -0
- package/dist/module/types/common.types.js +2 -0
- package/dist/module/types/common.types.js.map +1 -0
- package/dist/typescript/commonjs/features/endOfAssignment/components/Modal/Modal.d.ts.map +1 -1
- package/dist/typescript/commonjs/features/gifCelebrations/GifCelebrations.d.ts +10 -0
- package/dist/typescript/commonjs/features/gifCelebrations/GifCelebrations.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/components/Backdrop.d.ts +10 -0
- package/dist/typescript/commonjs/features/gifCelebrations/components/Backdrop.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/components/ContentContainer.d.ts +10 -0
- package/dist/typescript/commonjs/features/gifCelebrations/components/ContentContainer.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/components/Gif.d.ts +10 -0
- package/dist/typescript/commonjs/features/gifCelebrations/components/Gif.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/components/Progress.d.ts +10 -0
- package/dist/typescript/commonjs/features/gifCelebrations/components/Progress.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/constants.d.ts +26 -0
- package/dist/typescript/commonjs/features/gifCelebrations/constants.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/helpers.d.ts +29 -0
- package/dist/typescript/commonjs/features/gifCelebrations/helpers.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/index.d.ts +3 -0
- package/dist/typescript/commonjs/features/gifCelebrations/index.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/model/GifCelebrations.cache.d.ts +18 -0
- package/dist/typescript/commonjs/features/gifCelebrations/model/GifCelebrations.cache.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/model/GifCelebrations.model.d.ts +25 -0
- package/dist/typescript/commonjs/features/gifCelebrations/model/GifCelebrations.model.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/model/GifCelebrationsApi.d.ts +7 -0
- package/dist/typescript/commonjs/features/gifCelebrations/model/GifCelebrationsApi.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/gifCelebrations/types/model.types.d.ts +45 -0
- package/dist/typescript/commonjs/features/gifCelebrations/types/model.types.d.ts.map +1 -0
- package/dist/typescript/commonjs/index.d.ts +1 -0
- package/dist/typescript/commonjs/index.d.ts.map +1 -1
- package/dist/typescript/commonjs/lib/effector/createControllerEffect.d.ts +1 -1
- package/dist/typescript/commonjs/lib/effector/createControllerEffect.d.ts.map +1 -1
- package/dist/typescript/commonjs/lib/helpers/helpers.d.ts.map +1 -1
- package/dist/typescript/commonjs/shared/translation/constants.d.ts +15 -0
- package/dist/typescript/commonjs/shared/translation/constants.d.ts.map +1 -1
- package/dist/typescript/commonjs/shared/translation/model.d.ts +10 -0
- package/dist/typescript/commonjs/shared/translation/model.d.ts.map +1 -1
- package/dist/typescript/commonjs/types/common.types.d.ts +5 -0
- package/dist/typescript/commonjs/types/common.types.d.ts.map +1 -0
- package/dist/typescript/module/features/endOfAssignment/components/Modal/Modal.d.ts.map +1 -1
- package/dist/typescript/module/features/gifCelebrations/GifCelebrations.d.ts +10 -0
- package/dist/typescript/module/features/gifCelebrations/GifCelebrations.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/components/Backdrop.d.ts +10 -0
- package/dist/typescript/module/features/gifCelebrations/components/Backdrop.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/components/ContentContainer.d.ts +10 -0
- package/dist/typescript/module/features/gifCelebrations/components/ContentContainer.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/components/Gif.d.ts +10 -0
- package/dist/typescript/module/features/gifCelebrations/components/Gif.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/components/Progress.d.ts +10 -0
- package/dist/typescript/module/features/gifCelebrations/components/Progress.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/constants.d.ts +26 -0
- package/dist/typescript/module/features/gifCelebrations/constants.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/helpers.d.ts +29 -0
- package/dist/typescript/module/features/gifCelebrations/helpers.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/index.d.ts +3 -0
- package/dist/typescript/module/features/gifCelebrations/index.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/model/GifCelebrations.cache.d.ts +18 -0
- package/dist/typescript/module/features/gifCelebrations/model/GifCelebrations.cache.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/model/GifCelebrations.model.d.ts +25 -0
- package/dist/typescript/module/features/gifCelebrations/model/GifCelebrations.model.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/model/GifCelebrationsApi.d.ts +7 -0
- package/dist/typescript/module/features/gifCelebrations/model/GifCelebrationsApi.d.ts.map +1 -0
- package/dist/typescript/module/features/gifCelebrations/types/model.types.d.ts +45 -0
- package/dist/typescript/module/features/gifCelebrations/types/model.types.d.ts.map +1 -0
- package/dist/typescript/module/index.d.ts +1 -0
- package/dist/typescript/module/index.d.ts.map +1 -1
- package/dist/typescript/module/lib/effector/createControllerEffect.d.ts +1 -1
- package/dist/typescript/module/lib/effector/createControllerEffect.d.ts.map +1 -1
- package/dist/typescript/module/lib/helpers/helpers.d.ts.map +1 -1
- package/dist/typescript/module/shared/translation/constants.d.ts +15 -0
- package/dist/typescript/module/shared/translation/constants.d.ts.map +1 -1
- package/dist/typescript/module/shared/translation/model.d.ts +10 -0
- package/dist/typescript/module/shared/translation/model.d.ts.map +1 -1
- package/dist/typescript/module/types/common.types.d.ts +5 -0
- package/dist/typescript/module/types/common.types.d.ts.map +1 -0
- package/package.json +3 -5
- package/src/features/endOfAssignment/components/Modal/Modal.tsx +1 -2
- package/src/features/gifCelebrations/GifCelebrations.tsx +75 -0
- package/src/features/gifCelebrations/assets/confetti.webp +0 -0
- package/src/features/gifCelebrations/assets/gifFallback.gif +0 -0
- package/src/features/gifCelebrations/components/Backdrop.tsx +41 -0
- package/src/features/gifCelebrations/components/ContentContainer.tsx +44 -0
- package/src/features/gifCelebrations/components/Gif.tsx +88 -0
- package/src/features/gifCelebrations/components/Progress.tsx +106 -0
- package/src/features/gifCelebrations/constants.ts +32 -0
- package/src/features/gifCelebrations/helpers.ts +144 -0
- package/src/features/gifCelebrations/index.ts +2 -0
- package/src/features/gifCelebrations/model/GifCelebrations.cache.ts +41 -0
- package/src/features/gifCelebrations/model/GifCelebrations.model.ts +177 -0
- package/src/features/gifCelebrations/model/GifCelebrationsApi.ts +26 -0
- package/src/features/gifCelebrations/types/model.types.ts +53 -0
- package/src/index.ts +1 -0
- package/src/lib/effector/createControllerEffect.ts +41 -41
- package/src/lib/helpers/helpers.ts +1 -1
- package/src/react-app-env.d.ts +6 -0
- package/src/shared/translation/localization/en.json +5 -0
- package/src/shared/translation/localization/gb.json +5 -0
- package/src/shared/translation/localization/sw.json +5 -0
- package/src/types/common.types.ts +4 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { TranslationKeys } from 'src/shared/translation/types'
|
|
2
|
+
import {
|
|
3
|
+
CustomGetCachePathMethod,
|
|
4
|
+
GifsGroups,
|
|
5
|
+
GifsStructure,
|
|
6
|
+
GroupNumber,
|
|
7
|
+
GroupNumberString,
|
|
8
|
+
ProblemsGifsTable,
|
|
9
|
+
} from './types/model.types'
|
|
10
|
+
import { MIN_WIDTH_FOR_ONE_DIGIT, MIN_WIDTH_FOR_TWO_DIGITS } from './constants'
|
|
11
|
+
import { Image } from 'react-native'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Number of problems When we show gif
|
|
15
|
+
* <6 Never
|
|
16
|
+
* 7-12 50% completion
|
|
17
|
+
* 13-18 33%, 66% completion
|
|
18
|
+
* 19-24 25%, 50%, 75 completion
|
|
19
|
+
* 25-30 20%, 40%, 60%, 80% completion
|
|
20
|
+
* .>30 15%, 30%, 45%, 60%, 75% completion
|
|
21
|
+
*
|
|
22
|
+
* We are also going to have different buckets with different gifs. The logic is as follows:
|
|
23
|
+
* Group 1: 15%, 20%, 25%, 30% - Early celebrations
|
|
24
|
+
* Group 2: 33%, 40%, 45%, 50% - Mid celebrations
|
|
25
|
+
* Group 3: 60%, 66%, 75%, 80% - Party
|
|
26
|
+
* or
|
|
27
|
+
* Group 1: <=30% - Early celebrations
|
|
28
|
+
* Group 2: >30% <60% - Mid celebrations
|
|
29
|
+
* Group 3: >=60% - Party
|
|
30
|
+
*/
|
|
31
|
+
export const generateProblemsGifsTable = (
|
|
32
|
+
problemsAmount: number,
|
|
33
|
+
resolvedProblems: number,
|
|
34
|
+
gifs: GifsStructure,
|
|
35
|
+
): ProblemsGifsTable => {
|
|
36
|
+
const percentageMilestones = getPercentageMilestones(problemsAmount)
|
|
37
|
+
|
|
38
|
+
const result: ProblemsGifsTable = {}
|
|
39
|
+
|
|
40
|
+
for (const percentage of percentageMilestones) {
|
|
41
|
+
const problemNumber = Math.ceil(problemsAmount * (percentage / 100))
|
|
42
|
+
|
|
43
|
+
if (problemNumber <= resolvedProblems) continue
|
|
44
|
+
|
|
45
|
+
const groupKey = findGroupForPercentage(percentage)
|
|
46
|
+
const gifsInGroup = gifs[groupKey]
|
|
47
|
+
|
|
48
|
+
if (!gifsInGroup || gifsInGroup.length === 0) continue
|
|
49
|
+
|
|
50
|
+
const unshownGif = gifsInGroup.find((gif) => !gif.isShown)
|
|
51
|
+
|
|
52
|
+
const selectedGif = unshownGif || gifsInGroup[0]
|
|
53
|
+
|
|
54
|
+
if (selectedGif) {
|
|
55
|
+
result[problemNumber] = {
|
|
56
|
+
url: selectedGif.url,
|
|
57
|
+
group: groupKey,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result
|
|
63
|
+
}
|
|
64
|
+
const getPercentageMilestones = (problemsAmount: number): number[] => {
|
|
65
|
+
if (problemsAmount <= 12) return [50]
|
|
66
|
+
|
|
67
|
+
if (problemsAmount <= 18) return [33, 66]
|
|
68
|
+
|
|
69
|
+
if (problemsAmount <= 24) return [25, 50, 75]
|
|
70
|
+
|
|
71
|
+
if (problemsAmount <= 30) return [20, 40, 60, 80]
|
|
72
|
+
|
|
73
|
+
return [15, 30, 45, 60, 75]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const generateGifsStructure = (gifs: GifsGroups): GifsStructure =>
|
|
77
|
+
Object.fromEntries(
|
|
78
|
+
Object.entries(gifs).map(([key, value]) => [
|
|
79
|
+
key,
|
|
80
|
+
value.map((url) => ({
|
|
81
|
+
url,
|
|
82
|
+
isShown: false,
|
|
83
|
+
group: key as GroupNumberString,
|
|
84
|
+
})),
|
|
85
|
+
]),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
export const getGifLabel = (relatedGroup: GroupNumber): TranslationKeys => {
|
|
89
|
+
if (relatedGroup === 2) {
|
|
90
|
+
return 'gifCelebrations.onTheWay'
|
|
91
|
+
}
|
|
92
|
+
if (relatedGroup === 3) {
|
|
93
|
+
return 'gifCelebrations.almostThere'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return 'gifCelebrations.greatStart'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const getProgressLineWidth = (completedPercent: number) => {
|
|
100
|
+
if (completedPercent <= 9) {
|
|
101
|
+
return MIN_WIDTH_FOR_ONE_DIGIT
|
|
102
|
+
}
|
|
103
|
+
if (completedPercent <= 15) {
|
|
104
|
+
return MIN_WIDTH_FOR_TWO_DIGITS
|
|
105
|
+
}
|
|
106
|
+
return completedPercent
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const findGroupForPercentage = (percentage: number) => {
|
|
110
|
+
if (percentage <= 30) {
|
|
111
|
+
return 'group1'
|
|
112
|
+
}
|
|
113
|
+
if (percentage < 60) {
|
|
114
|
+
return 'group2'
|
|
115
|
+
}
|
|
116
|
+
return 'group3'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const getImagePath = async (url: string, cacheMethod?: CustomGetCachePathMethod) => {
|
|
120
|
+
if (cacheMethod) {
|
|
121
|
+
const path = await cacheMethod(url)
|
|
122
|
+
if (path) return path
|
|
123
|
+
} else {
|
|
124
|
+
// @ts-ignore
|
|
125
|
+
const isImagesStored = await Image.queryCache([url]).then((images) => !!images[url])
|
|
126
|
+
if (isImagesStored) return url
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
throw new Error('Image not found in cache')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const constrainDimension = (value: number, min: number, max: number) =>
|
|
133
|
+
Math.max(min, Math.min(max, value))
|
|
134
|
+
|
|
135
|
+
export const shouldRefreshGifs = (timestamp: string | null | undefined): boolean => {
|
|
136
|
+
if (!timestamp) return true
|
|
137
|
+
|
|
138
|
+
const REFRESH_INTERVAL_HOURS = 24
|
|
139
|
+
const lastFetchedDate = new Date(timestamp)
|
|
140
|
+
const currentDate = new Date()
|
|
141
|
+
const diffInHours = (currentDate.getTime() - lastFetchedDate.getTime()) / (1000 * 60 * 60)
|
|
142
|
+
|
|
143
|
+
return diffInHours > REFRESH_INTERVAL_HOURS
|
|
144
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createEffect, createEvent, createStore } from 'effector'
|
|
2
|
+
import {
|
|
3
|
+
GifsGroups,
|
|
4
|
+
GifsStructure,
|
|
5
|
+
CustomGetCachePathMethod,
|
|
6
|
+
CustomPrefetchMethod,
|
|
7
|
+
} from '../types/model.types'
|
|
8
|
+
import { generateGifsStructure } from '../helpers'
|
|
9
|
+
import { Image } from 'react-native'
|
|
10
|
+
|
|
11
|
+
type GifCelebrationsCacheProps = {
|
|
12
|
+
customPrefetchMethod?: CustomPrefetchMethod
|
|
13
|
+
customGetCachePathMethod?: CustomGetCachePathMethod
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class GifCelebrationsCache {
|
|
17
|
+
public readonly initialSet = createEvent<GifsGroups>()
|
|
18
|
+
public readonly set = createEvent<GifsStructure>()
|
|
19
|
+
public readonly reset = createEvent()
|
|
20
|
+
|
|
21
|
+
public readonly getCachePathMethod?: CustomGetCachePathMethod
|
|
22
|
+
public readonly prefetchMethod: CustomPrefetchMethod | typeof Image.prefetch
|
|
23
|
+
|
|
24
|
+
public readonly prefetchGifs = createEffect<string[], void>((gifs) => {
|
|
25
|
+
if (!gifs || gifs.length === 0) return
|
|
26
|
+
|
|
27
|
+
gifs.forEach((gifUrl) => {
|
|
28
|
+
this.prefetchMethod(gifUrl)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
public readonly $gifs = createStore<GifsStructure | null>(null)
|
|
33
|
+
.reset(this.reset)
|
|
34
|
+
.on(this.initialSet, (_, gifs) => generateGifsStructure(gifs))
|
|
35
|
+
.on(this.set, (_, gifs) => gifs)
|
|
36
|
+
|
|
37
|
+
constructor({ customGetCachePathMethod, customPrefetchMethod }: GifCelebrationsCacheProps) {
|
|
38
|
+
this.prefetchMethod = customPrefetchMethod || Image.prefetch
|
|
39
|
+
this.getCachePathMethod = customGetCachePathMethod
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createEffect, createEvent, createStore, sample, Store } from 'effector'
|
|
2
|
+
import { GifCelebrationsApi } from './GifCelebrationsApi'
|
|
3
|
+
import {
|
|
4
|
+
CustomLocalStorage,
|
|
5
|
+
GifCelebrationsModelProps,
|
|
6
|
+
GifObject,
|
|
7
|
+
GifsStructure,
|
|
8
|
+
ProblemsGifsTable,
|
|
9
|
+
} from '../types/model.types'
|
|
10
|
+
import { GifCelebrationsCache } from './GifCelebrations.cache'
|
|
11
|
+
import { generateProblemsGifsTable, shouldRefreshGifs } from '../helpers'
|
|
12
|
+
import { MIN_PROBLEMS_TO_SHOW_GIF, GIFS_FETCHED_KEY } from '../constants'
|
|
13
|
+
|
|
14
|
+
type HandleGifSelectionProps = {
|
|
15
|
+
gifsGroups: GifsStructure
|
|
16
|
+
resolvedProblemsAmount: number
|
|
17
|
+
problemsGifsTable: ProblemsGifsTable
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class GifCelebrationsModel {
|
|
21
|
+
public readonly api: GifCelebrationsApi
|
|
22
|
+
public readonly cache: GifCelebrationsCache
|
|
23
|
+
|
|
24
|
+
public readonly $problemsAmount: Store<number>
|
|
25
|
+
public readonly $resolvedProblemsAmount: Store<number>
|
|
26
|
+
|
|
27
|
+
public readonly play = createEvent()
|
|
28
|
+
public readonly stop = createEvent()
|
|
29
|
+
|
|
30
|
+
public readonly resetGifTriggerable = createEvent()
|
|
31
|
+
public readonly setGifTriggerable = createEvent<boolean>()
|
|
32
|
+
public readonly $isGifTriggerable = createStore<boolean>(false)
|
|
33
|
+
.reset(this.resetGifTriggerable)
|
|
34
|
+
.reset(this.stop)
|
|
35
|
+
.on(
|
|
36
|
+
this.setGifTriggerable,
|
|
37
|
+
(_, isGifCelebrationMayBeTriggered) => isGifCelebrationMayBeTriggered,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
public readonly resetProblemsGifsTable = createEvent()
|
|
41
|
+
public readonly $problemsGifsTable = createStore<ProblemsGifsTable | null>(null).reset(
|
|
42
|
+
this.resetProblemsGifsTable,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
public readonly setActiveGif = createEvent<GifObject>()
|
|
46
|
+
public readonly $activeGif = createStore<GifObject | null>(null)
|
|
47
|
+
.reset(this.stop)
|
|
48
|
+
.reset(this.resetGifTriggerable)
|
|
49
|
+
.on(this.setActiveGif, (_, gif) => gif)
|
|
50
|
+
|
|
51
|
+
private readonly localStorage: typeof localStorage | CustomLocalStorage
|
|
52
|
+
|
|
53
|
+
public readonly initialize = createEvent()
|
|
54
|
+
|
|
55
|
+
constructor({
|
|
56
|
+
api,
|
|
57
|
+
problemsAmount,
|
|
58
|
+
resolvedProblemsAmount,
|
|
59
|
+
customLocalStorage,
|
|
60
|
+
customGetCachePathMethod,
|
|
61
|
+
customPrefetchMethod,
|
|
62
|
+
}: GifCelebrationsModelProps) {
|
|
63
|
+
this.api = new GifCelebrationsApi(api)
|
|
64
|
+
this.cache = new GifCelebrationsCache({ customGetCachePathMethod, customPrefetchMethod })
|
|
65
|
+
|
|
66
|
+
this.$problemsAmount = problemsAmount
|
|
67
|
+
this.$resolvedProblemsAmount = resolvedProblemsAmount
|
|
68
|
+
|
|
69
|
+
this.localStorage = customLocalStorage || localStorage
|
|
70
|
+
this.setupEventHandlers()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private setupEventHandlers() {
|
|
74
|
+
sample({
|
|
75
|
+
clock: this.initialize,
|
|
76
|
+
source: this.cache.$gifs,
|
|
77
|
+
filter: (gifs) => {
|
|
78
|
+
if (!gifs) return true
|
|
79
|
+
|
|
80
|
+
const gifsFetchedTimestamp = this.localStorage.getItem(GIFS_FETCHED_KEY)
|
|
81
|
+
return shouldRefreshGifs(gifsFetchedTimestamp)
|
|
82
|
+
},
|
|
83
|
+
target: this.api.getGifsFx,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
sample({
|
|
87
|
+
source: this.api.getGifsFx.doneData,
|
|
88
|
+
filter: (response) => response.ok,
|
|
89
|
+
fn: (response) => response.data,
|
|
90
|
+
target: this.cache.initialSet,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
sample({
|
|
94
|
+
source: this.api.getGifsFx.doneData,
|
|
95
|
+
filter: (response) => response.ok,
|
|
96
|
+
target: createEffect(() => {
|
|
97
|
+
this.localStorage.setItem(GIFS_FETCHED_KEY, new Date().toISOString())
|
|
98
|
+
}),
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
sample({
|
|
102
|
+
clock: [this.initialize, this.api.getGifsFx.doneData],
|
|
103
|
+
source: {
|
|
104
|
+
gifs: this.cache.$gifs,
|
|
105
|
+
problemsAmount: this.$problemsAmount,
|
|
106
|
+
resolvedProblemsAmount: this.$resolvedProblemsAmount,
|
|
107
|
+
},
|
|
108
|
+
filter: ({ problemsAmount, gifs }) => !!(problemsAmount >= MIN_PROBLEMS_TO_SHOW_GIF && gifs),
|
|
109
|
+
fn: ({ problemsAmount, resolvedProblemsAmount, gifs }) => {
|
|
110
|
+
if (!gifs) return null
|
|
111
|
+
return generateProblemsGifsTable(problemsAmount, resolvedProblemsAmount, gifs)
|
|
112
|
+
},
|
|
113
|
+
target: this.$problemsGifsTable,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
sample({
|
|
117
|
+
source: this.$problemsGifsTable,
|
|
118
|
+
filter: (problemsGifsTable) => !!problemsGifsTable,
|
|
119
|
+
fn: (problemsGifsTable) => {
|
|
120
|
+
if (!problemsGifsTable) return []
|
|
121
|
+
return Object.values(problemsGifsTable).map((gif) => gif.url)
|
|
122
|
+
},
|
|
123
|
+
target: this.cache.prefetchGifs,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
sample({
|
|
127
|
+
clock: this.play,
|
|
128
|
+
source: {
|
|
129
|
+
isGifCelebrationMayBeTriggered: this.$isGifTriggerable,
|
|
130
|
+
gifsGroups: this.cache.$gifs,
|
|
131
|
+
resolvedProblemsAmount: this.$resolvedProblemsAmount,
|
|
132
|
+
problemsGifsTable: this.$problemsGifsTable,
|
|
133
|
+
},
|
|
134
|
+
filter: ({
|
|
135
|
+
isGifCelebrationMayBeTriggered,
|
|
136
|
+
gifsGroups,
|
|
137
|
+
resolvedProblemsAmount,
|
|
138
|
+
problemsGifsTable,
|
|
139
|
+
}) =>
|
|
140
|
+
!!(
|
|
141
|
+
isGifCelebrationMayBeTriggered &&
|
|
142
|
+
gifsGroups &&
|
|
143
|
+
resolvedProblemsAmount !== undefined &&
|
|
144
|
+
problemsGifsTable
|
|
145
|
+
),
|
|
146
|
+
fn: (sources) => sources as HandleGifSelectionProps,
|
|
147
|
+
target: this.updateGifShownStatus,
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private readonly updateGifShownStatus = createEffect(
|
|
152
|
+
({ gifsGroups, resolvedProblemsAmount, problemsGifsTable }: HandleGifSelectionProps) => {
|
|
153
|
+
const gifToShow = problemsGifsTable[resolvedProblemsAmount]
|
|
154
|
+
const groupName = gifToShow.group
|
|
155
|
+
|
|
156
|
+
const gifsInGroup = gifsGroups[groupName]
|
|
157
|
+
const selectedGifIndex = gifsInGroup?.findIndex((gif) => gif.url === gifToShow.url)
|
|
158
|
+
|
|
159
|
+
if (!gifsInGroup || selectedGifIndex === -1) {
|
|
160
|
+
console.warn(`Gif with URL ${gifToShow.url} not found in group ${groupName}`)
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const updatedGifsInGroup = gifsInGroup.map((gif, index) => ({
|
|
165
|
+
...gif,
|
|
166
|
+
isShown: index === selectedGifIndex ? true : gif.isShown,
|
|
167
|
+
}))
|
|
168
|
+
|
|
169
|
+
this.cache.set({
|
|
170
|
+
...gifsGroups,
|
|
171
|
+
[groupName]: updatedGifsInGroup,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
this.setActiveGif(gifToShow)
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createControllerEffect } from './../../../lib/effector/createControllerEffect'
|
|
2
|
+
import { GifCelebrationApiRequests } from '../types/model.types'
|
|
3
|
+
import { GET_GIFS_TIMEOUT } from '../constants'
|
|
4
|
+
|
|
5
|
+
export class GifCelebrationsApi {
|
|
6
|
+
public readonly getGifsFx
|
|
7
|
+
|
|
8
|
+
private failedGetGifxTimeout: NodeJS.Timeout | null = null
|
|
9
|
+
|
|
10
|
+
constructor(api: GifCelebrationApiRequests) {
|
|
11
|
+
this.getGifsFx = createControllerEffect(() => api.getGifs())
|
|
12
|
+
|
|
13
|
+
this.getGifsFx.fail.watch(() => {
|
|
14
|
+
this.failedGetGifxTimeout = setTimeout(() => {
|
|
15
|
+
this.getGifsFx()
|
|
16
|
+
}, GET_GIFS_TIMEOUT)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
this.getGifsFx.done.watch(() => {
|
|
20
|
+
if (this.failedGetGifxTimeout) {
|
|
21
|
+
clearTimeout(this.failedGetGifxTimeout)
|
|
22
|
+
this.failedGetGifxTimeout = null
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Store } from 'effector'
|
|
2
|
+
|
|
3
|
+
export type CustomGetCachePathMethod = (uri: string) => Promise<string | null>
|
|
4
|
+
export type CustomPrefetchMethod = (gifs: string[] | string) => Promise<unknown>
|
|
5
|
+
export type CustomLocalStorage = {
|
|
6
|
+
setItem: (key: string, value: string) => void
|
|
7
|
+
getItem: (key: string) => string | null | undefined
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type GroupNumber = number
|
|
11
|
+
|
|
12
|
+
export type GifCelebrationApiRequests = {
|
|
13
|
+
getGifs: () => Promise<GifResponse>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type GifCelebrationsModelProps = {
|
|
17
|
+
api: GifCelebrationApiRequests
|
|
18
|
+
problemsAmount: Store<number>
|
|
19
|
+
resolvedProblemsAmount: Store<number>
|
|
20
|
+
customLocalStorage?: CustomLocalStorage
|
|
21
|
+
customPrefetchMethod?: CustomPrefetchMethod
|
|
22
|
+
customGetCachePathMethod?: CustomGetCachePathMethod
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type GifsGroup = string[]
|
|
26
|
+
export type GroupNumberString = `group${GroupNumber}`
|
|
27
|
+
export type GifsGroups = Record<GroupNumberString, GifsGroup>
|
|
28
|
+
|
|
29
|
+
export type GifObject = {
|
|
30
|
+
url: string
|
|
31
|
+
group: GroupNumberString
|
|
32
|
+
isShown?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type GifsStructure = Record<GroupNumberString, GifObject[]>
|
|
36
|
+
/**
|
|
37
|
+
* Usually, the response contains 3 groups of gifs:
|
|
38
|
+
* - group1: early celebrations
|
|
39
|
+
* - group2: mid celebrations
|
|
40
|
+
* - group3: party
|
|
41
|
+
*/
|
|
42
|
+
export type GifResponse = {
|
|
43
|
+
data: GifsGroups
|
|
44
|
+
ok: boolean
|
|
45
|
+
}
|
|
46
|
+
export type GifsPayload = {
|
|
47
|
+
group: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ProblemsGifsTable = Record<number, GifObject>
|
|
51
|
+
export type ProgressValue = 0 | 1 | 2
|
|
52
|
+
|
|
53
|
+
export type TextComponentType = (text: string) => React.ReactNode
|
package/src/index.ts
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
1
|
import { createEffect, type Effect } from 'effector'
|
|
2
2
|
|
|
3
3
|
type LikeCanceledError = {
|
|
4
|
-
|
|
4
|
+
code?: string
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export type ControlledEffect<P, T> = Effect<P, T> & {
|
|
8
|
-
|
|
8
|
+
controller: AbortController
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
type ControlledRequestProps<P> = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
data: P
|
|
13
|
+
controller?: AbortController
|
|
14
|
+
abortAfter?: number
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
type RequestOptionalConfig = { signal?: AbortSignal }
|
|
18
18
|
|
|
19
19
|
export function controlledRequest<R = void, P = void>(
|
|
20
|
-
|
|
20
|
+
request: (props: P, config?: RequestOptionalConfig) => Promise<R>,
|
|
21
21
|
) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
return async ({
|
|
23
|
+
data,
|
|
24
|
+
controller = new AbortController(),
|
|
25
|
+
abortAfter = 20000,
|
|
26
|
+
}: ControlledRequestProps<P>) => {
|
|
27
|
+
let timeout: ReturnType<typeof setTimeout> | null = null
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const response = await request(data, { signal: controller?.signal })
|
|
37
|
-
if (timeout !== null) clearTimeout(timeout)
|
|
38
|
-
return response
|
|
39
|
-
} catch (error: unknown) {
|
|
40
|
-
if ((error as LikeCanceledError).code === 'ERR_CANCELED') {
|
|
41
|
-
if (controller.signal.reason === 'timeout') {
|
|
42
|
-
throw new Error('Request timed out')
|
|
43
|
-
}
|
|
44
|
-
throw new Error('Request aborted manually')
|
|
45
|
-
}
|
|
29
|
+
if (abortAfter) {
|
|
30
|
+
timeout = setTimeout(() => {
|
|
31
|
+
controller.abort('timeout')
|
|
32
|
+
}, abortAfter)
|
|
33
|
+
}
|
|
46
34
|
|
|
47
|
-
|
|
35
|
+
try {
|
|
36
|
+
const response = await request(data, { signal: controller?.signal })
|
|
37
|
+
if (timeout !== null) clearTimeout(timeout)
|
|
38
|
+
return response
|
|
39
|
+
} catch (error: unknown) {
|
|
40
|
+
if ((error as LikeCanceledError).code === 'ERR_CANCELED') {
|
|
41
|
+
if (controller.signal.reason === 'timeout') {
|
|
42
|
+
throw new Error('Request timed out')
|
|
48
43
|
}
|
|
44
|
+
throw new Error('Request aborted manually')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw error
|
|
49
48
|
}
|
|
49
|
+
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export const createControllerEffect = <T, P>(
|
|
53
|
-
|
|
52
|
+
export const createControllerEffect = <T, P = void>(
|
|
53
|
+
fn: (props: P, config?: RequestOptionalConfig) => Promise<T>,
|
|
54
54
|
) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
const request = controlledRequest<T, P>(fn)
|
|
56
|
+
const effect = createEffect((data: P) =>
|
|
57
|
+
request({ data, controller: effect.controller }),
|
|
58
|
+
) as ControlledEffect<P, T>
|
|
59
|
+
effect.controller = new AbortController()
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
effect.fail.watch(() => {
|
|
62
|
+
effect.controller = new AbortController()
|
|
63
|
+
})
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
return effect
|
|
66
66
|
}
|
|
@@ -18,5 +18,10 @@
|
|
|
18
18
|
"errorMessage": "Something went wrong! Try again",
|
|
19
19
|
"howCanIHelp": "Hi! Do you want help?",
|
|
20
20
|
"showError": "Show error"
|
|
21
|
+
},
|
|
22
|
+
"gifCelebrations": {
|
|
23
|
+
"greatStart": "Great start!",
|
|
24
|
+
"onTheWay": "On the way!",
|
|
25
|
+
"almostThere": "Almost there"
|
|
21
26
|
}
|
|
22
27
|
}
|
|
@@ -18,5 +18,10 @@
|
|
|
18
18
|
"errorMessage": "Something went wrong! Try again",
|
|
19
19
|
"howCanIHelp": "Hi! Do you want help?",
|
|
20
20
|
"showError": "Show error"
|
|
21
|
+
},
|
|
22
|
+
"gifCelebrations": {
|
|
23
|
+
"greatStart": "Great start!",
|
|
24
|
+
"onTheWay": "On the way!",
|
|
25
|
+
"almostThere": "Almost there"
|
|
21
26
|
}
|
|
22
27
|
}
|
|
@@ -18,5 +18,10 @@
|
|
|
18
18
|
"errorMessage": "Något gick snett! Försök igen",
|
|
19
19
|
"howCanIHelp": "Hej! Hur kan jag hjälpa dig?",
|
|
20
20
|
"showError": "Visa fel"
|
|
21
|
+
},
|
|
22
|
+
"gifCelebrations": {
|
|
23
|
+
"greatStart": "Bra start!",
|
|
24
|
+
"onTheWay": "På god väg!",
|
|
25
|
+
"almostThere": "Nästan där"
|
|
21
26
|
}
|
|
22
27
|
}
|