@giphy/js-util 0.0.0-canary-20230620165510
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/README.md +27 -0
- package/dist/index.cjs +365 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +320 -0
- package/package.json +46 -0
- package/src/__tests__/.eslintrc +5 -0
- package/src/__tests__/gif-utils.test.ts +167 -0
- package/src/__tests__/git-util-best-rendition.test.ts +304 -0
- package/src/bestfit.ts +64 -0
- package/src/collections.ts +36 -0
- package/src/get-client-rect-from-el.ts +29 -0
- package/src/get-pingback-id.ts +42 -0
- package/src/gif-utils.ts +127 -0
- package/src/index.ts +16 -0
- package/src/log.ts +36 -0
- package/src/sdk-headers.ts +18 -0
- package/src/webp-check.ts +18 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { setRenditionScaleUpMaxPixels } from '../bestfit'
|
|
2
|
+
import { getBestRenditionUrl, getBestVideo } from '../gif-utils'
|
|
3
|
+
|
|
4
|
+
const testGifLandscape: any = {
|
|
5
|
+
images: {
|
|
6
|
+
fixed_height_still: {
|
|
7
|
+
url: '/media/200_s.gif',
|
|
8
|
+
width: '355',
|
|
9
|
+
height: '200',
|
|
10
|
+
},
|
|
11
|
+
original_still: {
|
|
12
|
+
url: '/media/giphy_s.gif',
|
|
13
|
+
width: '600',
|
|
14
|
+
height: '338',
|
|
15
|
+
},
|
|
16
|
+
fixed_width: {
|
|
17
|
+
url: '/media/200x113.gif',
|
|
18
|
+
width: '200',
|
|
19
|
+
height: '113',
|
|
20
|
+
size: '77670',
|
|
21
|
+
mp4: '/media/200x113.mp4',
|
|
22
|
+
mp4_size: '11777',
|
|
23
|
+
webp: '/media/200x113.webp',
|
|
24
|
+
webp_size: '52404',
|
|
25
|
+
},
|
|
26
|
+
fixed_height_small_still: {
|
|
27
|
+
url: '/media/100_s.gif',
|
|
28
|
+
width: '178',
|
|
29
|
+
height: '100',
|
|
30
|
+
},
|
|
31
|
+
fixed_height_small: {
|
|
32
|
+
url: '/media/178x100.gif',
|
|
33
|
+
width: '178',
|
|
34
|
+
height: '100',
|
|
35
|
+
size: '169486',
|
|
36
|
+
mp4: '/media/100.mp4',
|
|
37
|
+
mp4_size: '68837',
|
|
38
|
+
webp: '/media/100.webp',
|
|
39
|
+
webp_size: '43392',
|
|
40
|
+
},
|
|
41
|
+
fixed_width_small_still: {
|
|
42
|
+
url: '/media/100w_s.gif',
|
|
43
|
+
width: '100',
|
|
44
|
+
height: '56',
|
|
45
|
+
},
|
|
46
|
+
fixed_width_still: {
|
|
47
|
+
url: '/media/200w_s.gif',
|
|
48
|
+
width: '200',
|
|
49
|
+
height: '113',
|
|
50
|
+
},
|
|
51
|
+
fixed_width_small: {
|
|
52
|
+
url: '/media/100x56.gif',
|
|
53
|
+
width: '100',
|
|
54
|
+
height: '56',
|
|
55
|
+
size: '77670',
|
|
56
|
+
mp4: '/media/100x56.mp4',
|
|
57
|
+
mp4_size: '31328',
|
|
58
|
+
webp: '/media/100x56.webp',
|
|
59
|
+
webp_size: '17910',
|
|
60
|
+
},
|
|
61
|
+
original: {
|
|
62
|
+
url: '/media/600x338.gif',
|
|
63
|
+
width: '600',
|
|
64
|
+
height: '338',
|
|
65
|
+
size: '1020165',
|
|
66
|
+
frames: '14',
|
|
67
|
+
mp4: '/media/giphy.mp4',
|
|
68
|
+
mp4_size: '41339',
|
|
69
|
+
webp: '/media/giphy.webp',
|
|
70
|
+
webp_size: '295984',
|
|
71
|
+
},
|
|
72
|
+
fixed_height: {
|
|
73
|
+
url: '/media/355x200.gif',
|
|
74
|
+
width: '355',
|
|
75
|
+
height: '200',
|
|
76
|
+
size: '169486',
|
|
77
|
+
mp4: '/media/200.mp4',
|
|
78
|
+
mp4_size: '11945',
|
|
79
|
+
webp: '/media/200.webp',
|
|
80
|
+
webp_size: '129066',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const testVideo = {
|
|
86
|
+
...testGifLandscape,
|
|
87
|
+
video: {
|
|
88
|
+
assets: {
|
|
89
|
+
fixed_width: {
|
|
90
|
+
url: '/media/200x113.gif',
|
|
91
|
+
width: '200',
|
|
92
|
+
height: '113',
|
|
93
|
+
size: '77670',
|
|
94
|
+
mp4: '/media/200x113.mp4',
|
|
95
|
+
mp4_size: '11777',
|
|
96
|
+
webp: '/media/200x113.webp',
|
|
97
|
+
webp_size: '52404',
|
|
98
|
+
},
|
|
99
|
+
fixed_width_small: {
|
|
100
|
+
url: '/media/100x56.gif',
|
|
101
|
+
width: '100',
|
|
102
|
+
height: '56',
|
|
103
|
+
size: '77670',
|
|
104
|
+
mp4: '/media/100x56.mp4',
|
|
105
|
+
mp4_size: '31328',
|
|
106
|
+
webp: '/media/100x56.webp',
|
|
107
|
+
webp_size: '17910',
|
|
108
|
+
},
|
|
109
|
+
original: {
|
|
110
|
+
url: '/media/600x338.gif',
|
|
111
|
+
width: '600',
|
|
112
|
+
height: '338',
|
|
113
|
+
size: '1020165',
|
|
114
|
+
frames: '14',
|
|
115
|
+
mp4: '/media/giphy.mp4',
|
|
116
|
+
mp4_size: '41339',
|
|
117
|
+
webp: '/media/giphy.webp',
|
|
118
|
+
webp_size: '295984',
|
|
119
|
+
},
|
|
120
|
+
fixed_height: {
|
|
121
|
+
url: '/media/355x200.gif',
|
|
122
|
+
width: '355',
|
|
123
|
+
height: '200',
|
|
124
|
+
size: '169486',
|
|
125
|
+
mp4: '/media/200.mp4',
|
|
126
|
+
mp4_size: '11945',
|
|
127
|
+
webp: '/media/200.webp',
|
|
128
|
+
webp_size: '129066',
|
|
129
|
+
},
|
|
130
|
+
fixed_height_small: {
|
|
131
|
+
url: '/media/178x100.gif',
|
|
132
|
+
width: '178',
|
|
133
|
+
height: '100',
|
|
134
|
+
size: '169486',
|
|
135
|
+
mp4: '/media/100.mp4',
|
|
136
|
+
mp4_size: '68837',
|
|
137
|
+
webp: '/media/100.webp',
|
|
138
|
+
webp_size: '43392',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const testGifPortrait: any = {
|
|
145
|
+
images: {
|
|
146
|
+
fixed_height_still: {
|
|
147
|
+
url: '/media/200_s.gif',
|
|
148
|
+
width: '200',
|
|
149
|
+
height: '355',
|
|
150
|
+
},
|
|
151
|
+
original_still: {
|
|
152
|
+
url: '/media/giphy_s.gif',
|
|
153
|
+
width: '338',
|
|
154
|
+
height: '600',
|
|
155
|
+
},
|
|
156
|
+
fixed_width: {
|
|
157
|
+
url: '/media/113x200.gif',
|
|
158
|
+
width: '113',
|
|
159
|
+
height: '200',
|
|
160
|
+
size: '77670',
|
|
161
|
+
mp4: '/media/200x113.mp4',
|
|
162
|
+
mp4_size: '11777',
|
|
163
|
+
webp: '/media/200x113.webp',
|
|
164
|
+
webp_size: '52404',
|
|
165
|
+
},
|
|
166
|
+
fixed_height_small_still: {
|
|
167
|
+
url: '/media/100_s.gif',
|
|
168
|
+
width: '100',
|
|
169
|
+
height: '178',
|
|
170
|
+
},
|
|
171
|
+
fixed_height_small: {
|
|
172
|
+
url: '/media/100x178.gif',
|
|
173
|
+
width: '100',
|
|
174
|
+
height: '178',
|
|
175
|
+
size: '169486',
|
|
176
|
+
mp4: '/media/100.mp4',
|
|
177
|
+
mp4_size: '68837',
|
|
178
|
+
webp: '/media/100.webp',
|
|
179
|
+
webp_size: '43392',
|
|
180
|
+
},
|
|
181
|
+
fixed_width_small_still: {
|
|
182
|
+
url: '/media/100w_s.gif',
|
|
183
|
+
width: '56',
|
|
184
|
+
height: '100',
|
|
185
|
+
},
|
|
186
|
+
fixed_width_still: {
|
|
187
|
+
url: '/media/200w_s.gif',
|
|
188
|
+
width: '113',
|
|
189
|
+
height: '200',
|
|
190
|
+
},
|
|
191
|
+
fixed_width_small: {
|
|
192
|
+
url: '/media/56x100.gif',
|
|
193
|
+
width: '56',
|
|
194
|
+
height: '100',
|
|
195
|
+
size: '77670',
|
|
196
|
+
mp4: '/media/100x56.mp4',
|
|
197
|
+
mp4_size: '31328',
|
|
198
|
+
webp: '/media/100x56.webp',
|
|
199
|
+
webp_size: '17910',
|
|
200
|
+
},
|
|
201
|
+
original: {
|
|
202
|
+
url: '/media/338x600.gif',
|
|
203
|
+
width: '338',
|
|
204
|
+
height: '600',
|
|
205
|
+
size: '1020165',
|
|
206
|
+
frames: '14',
|
|
207
|
+
mp4: '/media/giphy.mp4',
|
|
208
|
+
mp4_size: '41339',
|
|
209
|
+
webp: '/media/giphy.webp',
|
|
210
|
+
webp_size: '295984',
|
|
211
|
+
},
|
|
212
|
+
fixed_height: {
|
|
213
|
+
url: '/media/200x355.gif',
|
|
214
|
+
width: '200',
|
|
215
|
+
height: '355',
|
|
216
|
+
size: '169486',
|
|
217
|
+
mp4: '/media/200.mp4',
|
|
218
|
+
mp4_size: '11945',
|
|
219
|
+
webp: '/media/200.webp',
|
|
220
|
+
webp_size: '129066',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const testGifVideo: any = {
|
|
226
|
+
type: 'video',
|
|
227
|
+
images: {},
|
|
228
|
+
video: { previews: testGifLandscape.images },
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
describe('gif util', () => {
|
|
232
|
+
beforeEach(() => {
|
|
233
|
+
setRenditionScaleUpMaxPixels(50)
|
|
234
|
+
})
|
|
235
|
+
test('getBestRenditionUrl ', () => {
|
|
236
|
+
expect(getBestRenditionUrl(testGifLandscape, 450, 350)).toEqual('/media/600x338.gif')
|
|
237
|
+
expect(getBestRenditionUrl(testGifLandscape, 400, 300)).toEqual('/media/600x338.gif')
|
|
238
|
+
expect(getBestRenditionUrl(testGifLandscape, 300, 200)).toEqual('/media/355x200.gif')
|
|
239
|
+
expect(getBestRenditionUrl(testGifLandscape, 200, 200)).toEqual('/media/355x200.gif')
|
|
240
|
+
expect(getBestRenditionUrl(testGifLandscape, 100, 100)).toEqual('/media/100x56.gif')
|
|
241
|
+
expect(getBestRenditionUrl(testGifLandscape, 300, 200)).toEqual('/media/355x200.gif')
|
|
242
|
+
// within the scaleup range
|
|
243
|
+
expect(getBestRenditionUrl(testGifLandscape, 200, 100)).toEqual('/media/178x100.gif')
|
|
244
|
+
expect(getBestRenditionUrl(testGifLandscape, 200, 100)).toEqual('/media/178x100.gif')
|
|
245
|
+
expect(getBestRenditionUrl(testGifLandscape, 178, 100)).toEqual('/media/178x100.gif')
|
|
246
|
+
expect(getBestRenditionUrl(testGifLandscape, 100, 100)).toEqual('/media/100x56.gif')
|
|
247
|
+
expect(getBestRenditionUrl(testGifLandscape, 1, 1)).toEqual('/media/100x56.gif')
|
|
248
|
+
expect(getBestRenditionUrl(testGifLandscape, 0, 1)).toEqual('')
|
|
249
|
+
})
|
|
250
|
+
test('getBestVideo ', () => {
|
|
251
|
+
expect(getBestVideo(testVideo.video, 450, 350)?.url).toEqual('/media/600x338.gif')
|
|
252
|
+
expect(getBestVideo(testVideo.video, 400, 300)?.url).toEqual('/media/600x338.gif')
|
|
253
|
+
expect(getBestVideo(testVideo.video, 300, 200)?.url).toEqual('/media/355x200.gif')
|
|
254
|
+
expect(getBestVideo(testVideo.video, 200, 200)?.url).toEqual('/media/355x200.gif')
|
|
255
|
+
expect(getBestVideo(testVideo.video, 100, 100)?.url).toEqual('/media/100x56.gif')
|
|
256
|
+
expect(getBestVideo(testVideo.video, 300, 200)?.url).toEqual('/media/355x200.gif')
|
|
257
|
+
// within the scaleup range
|
|
258
|
+
expect(getBestVideo(testVideo.video, 200, 100)?.url).toEqual('/media/178x100.gif')
|
|
259
|
+
expect(getBestVideo(testVideo.video, 200, 100)?.url).toEqual('/media/178x100.gif')
|
|
260
|
+
expect(getBestVideo(testVideo.video, 178, 100)?.url).toEqual('/media/178x100.gif')
|
|
261
|
+
expect(getBestVideo(testVideo.video, 100, 100)?.url).toEqual('/media/100x56.gif')
|
|
262
|
+
expect(getBestVideo(testVideo.video, 1, 1)?.url).toEqual('/media/100x56.gif')
|
|
263
|
+
// expect(getBestVideo(testVideo.video, 0, 1).u).toEqual('')
|
|
264
|
+
})
|
|
265
|
+
test('getBestRenditionUrl portrait target landscape gif ', () => {
|
|
266
|
+
expect(getBestRenditionUrl(testGifLandscape, 700, 450)).toEqual('/media/600x338.gif')
|
|
267
|
+
expect(getBestRenditionUrl(testGifLandscape, 350, 450)).toEqual('/media/600x338.gif')
|
|
268
|
+
expect(getBestRenditionUrl(testGifLandscape, 300, 400)).toEqual('/media/600x338.gif')
|
|
269
|
+
expect(getBestRenditionUrl(testGifLandscape, 200, 300)).toEqual('/media/600x338.gif')
|
|
270
|
+
expect(getBestRenditionUrl(testGifLandscape, 100, 200)).toEqual('/media/355x200.gif')
|
|
271
|
+
expect(getBestRenditionUrl(testGifLandscape, 50, 100)).toEqual('/media/100x56.gif')
|
|
272
|
+
expect(getBestRenditionUrl(testGifLandscape, 1, 1)).toEqual('/media/100x56.gif')
|
|
273
|
+
expect(getBestRenditionUrl(testGifLandscape, 0, 1)).toEqual('')
|
|
274
|
+
})
|
|
275
|
+
test('getBestRenditionUrl landscape target portrait gif ', () => {
|
|
276
|
+
expect(getBestRenditionUrl(testGifPortrait, 700, 700)).toEqual('/media/338x600.gif')
|
|
277
|
+
expect(getBestRenditionUrl(testGifPortrait, 338, 200)).toEqual('/media/338x600.gif')
|
|
278
|
+
expect(getBestRenditionUrl(testGifPortrait, 300, 200)).toEqual('/media/338x600.gif')
|
|
279
|
+
expect(getBestRenditionUrl(testGifPortrait, 200, 100)).toEqual('/media/200x355.gif')
|
|
280
|
+
expect(getBestRenditionUrl(testGifPortrait, 100, 50)).toEqual('/media/56x100.gif')
|
|
281
|
+
expect(getBestRenditionUrl(testGifPortrait, 1, 1)).toEqual('/media/56x100.gif')
|
|
282
|
+
expect(getBestRenditionUrl(testGifPortrait, 0, 1)).toEqual('')
|
|
283
|
+
})
|
|
284
|
+
test('getBestRenditionUrl type video ', () => {
|
|
285
|
+
expect(getBestRenditionUrl(testGifVideo, 450, 350)).toEqual('/media/600x338.gif')
|
|
286
|
+
expect(getBestRenditionUrl(testGifVideo, 400, 300)).toEqual('/media/600x338.gif')
|
|
287
|
+
})
|
|
288
|
+
test('getBestRenditionUrl higher quality: 20', () => {
|
|
289
|
+
setRenditionScaleUpMaxPixels(20)
|
|
290
|
+
expect(getBestRenditionUrl(testGifLandscape, 450, 350)).toEqual('/media/600x338.gif')
|
|
291
|
+
expect(getBestRenditionUrl(testGifLandscape, 400, 300)).toEqual('/media/600x338.gif')
|
|
292
|
+
expect(getBestRenditionUrl(testGifLandscape, 300, 200)).toEqual('/media/355x200.gif')
|
|
293
|
+
expect(getBestRenditionUrl(testGifLandscape, 200, 200)).toEqual('/media/355x200.gif')
|
|
294
|
+
expect(getBestRenditionUrl(testGifLandscape, 100, 100)).toEqual('/media/178x100.gif')
|
|
295
|
+
expect(getBestRenditionUrl(testGifLandscape, 300, 200)).toEqual('/media/355x200.gif')
|
|
296
|
+
// within the scaleup range
|
|
297
|
+
expect(getBestRenditionUrl(testGifLandscape, 200, 100)).toEqual('/media/200x113.gif')
|
|
298
|
+
expect(getBestRenditionUrl(testGifLandscape, 200, 100)).toEqual('/media/200x113.gif')
|
|
299
|
+
expect(getBestRenditionUrl(testGifLandscape, 178, 100)).toEqual('/media/178x100.gif')
|
|
300
|
+
expect(getBestRenditionUrl(testGifLandscape, 100, 100)).toEqual('/media/178x100.gif')
|
|
301
|
+
expect(getBestRenditionUrl(testGifLandscape, 1, 1)).toEqual('/media/100x56.gif')
|
|
302
|
+
expect(getBestRenditionUrl(testGifLandscape, 0, 1)).toEqual('')
|
|
303
|
+
})
|
|
304
|
+
})
|
package/src/bestfit.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { IRendition } from '@giphy/js-types'
|
|
2
|
+
import { Logger } from './log'
|
|
3
|
+
|
|
4
|
+
const closestArea = (width: number, height: number, renditions: IRendition[]) => {
|
|
5
|
+
let currentBest = Infinity
|
|
6
|
+
let result: IRendition
|
|
7
|
+
// sort the renditions so we can avoid scaling up low resolutions
|
|
8
|
+
renditions.forEach((rendition: IRendition) => {
|
|
9
|
+
const widthPercentage = rendition.width / width
|
|
10
|
+
const heightPercentage = rendition.height / height
|
|
11
|
+
// a width percentage of 1 is exact, 2 is double, .5 half etc
|
|
12
|
+
const areaPercentage = widthPercentage * heightPercentage
|
|
13
|
+
// img could be bigger or smaller
|
|
14
|
+
const testBest = Math.abs(1 - areaPercentage) // the closer to 0 the better
|
|
15
|
+
if (testBest < currentBest) {
|
|
16
|
+
currentBest = testBest
|
|
17
|
+
result = rendition
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
return result!
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let SCALE_UP_MAX_PIXELS = 50
|
|
24
|
+
export const setRenditionScaleUpMaxPixels = (pixels: number) => {
|
|
25
|
+
Logger.debug(`@giphy/js-util set rendition selection scale up max pixels to ${pixels}`)
|
|
26
|
+
SCALE_UP_MAX_PIXELS = pixels
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Finds image rendition that best fits a given container preferring images
|
|
30
|
+
* ##### Note: all renditions are assumed to have the same aspect ratio
|
|
31
|
+
*
|
|
32
|
+
* When we have a portrait target and landscape gif, we choose a higher rendition to match
|
|
33
|
+
* the height of the portrait target, otherwise it's blurry (same applies for landscape to portrait)
|
|
34
|
+
*
|
|
35
|
+
* @name bestfit
|
|
36
|
+
* @function
|
|
37
|
+
* @param {Array.<Object>} renditions available image renditions each having a width and height property
|
|
38
|
+
* @param {Number} width
|
|
39
|
+
* @param {Number} height
|
|
40
|
+
* @param {Number} scaleUpMaxPixels the maximum pixels an asset should be scaled up
|
|
41
|
+
*/
|
|
42
|
+
function bestfit(
|
|
43
|
+
renditions: Array<IRendition>,
|
|
44
|
+
width: number,
|
|
45
|
+
height: number,
|
|
46
|
+
scaleUpMaxPixels: number = SCALE_UP_MAX_PIXELS
|
|
47
|
+
) {
|
|
48
|
+
let [largestRendition] = renditions
|
|
49
|
+
// filter out renditions that are smaller than the target width and height by scaleUpMaxPixels value
|
|
50
|
+
const testRenditions = renditions.filter(rendition => {
|
|
51
|
+
if (rendition.width * rendition.height > largestRendition.width * largestRendition.height) {
|
|
52
|
+
largestRendition = rendition
|
|
53
|
+
}
|
|
54
|
+
return width - rendition.width <= scaleUpMaxPixels && height - rendition.height <= scaleUpMaxPixels
|
|
55
|
+
})
|
|
56
|
+
// if all are too small, use the largest we have
|
|
57
|
+
if (testRenditions.length === 0) {
|
|
58
|
+
return largestRendition
|
|
59
|
+
}
|
|
60
|
+
// find the closest area of the filtered renditions
|
|
61
|
+
return closestArea(width, height, testRenditions)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default bestfit
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function mapValues(object: any, mapFn: (val: any, key: string) => any): any {
|
|
2
|
+
if (Array.isArray(object)) {
|
|
3
|
+
throw `This map is just for objects, just use array.map for arrays`
|
|
4
|
+
}
|
|
5
|
+
return Object.keys(object).reduce((result: any, key: string) => {
|
|
6
|
+
result[key] = mapFn(object[key], key)
|
|
7
|
+
return result
|
|
8
|
+
}, {})
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function forEach(object: any, mapFn: (val: any, key: any) => void): void {
|
|
12
|
+
if (Array.isArray(object)) {
|
|
13
|
+
throw `This map is just for objects, just use array.forEach for arrays`
|
|
14
|
+
}
|
|
15
|
+
return Object.keys(object).forEach((key: string) => {
|
|
16
|
+
mapFn(object[key], key)
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function take<T>(arr: T[], count = 0): T[] {
|
|
21
|
+
return arr.slice(0, count)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function without<T>(arr: T[], values: T[]): T[] {
|
|
25
|
+
return arr.filter(val => values.indexOf(val) === -1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function pick<T extends object, U extends keyof T>(object: T, pick: Array<U>): Pick<T, U> {
|
|
29
|
+
const res: Partial<T> = {}
|
|
30
|
+
pick.forEach((key: U) => {
|
|
31
|
+
if (object[key] !== undefined) {
|
|
32
|
+
res[key] = object[key]
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
return res as Pick<T, U>
|
|
36
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param el HTMLElement
|
|
3
|
+
* @returns calculated properties of DOMReact
|
|
4
|
+
*/
|
|
5
|
+
const getClientRect = (el: HTMLElement): DOMRectReadOnly => {
|
|
6
|
+
let left = 0
|
|
7
|
+
let top = 0
|
|
8
|
+
const width = el.offsetWidth
|
|
9
|
+
const height = el.offsetHeight
|
|
10
|
+
// no layout thrash
|
|
11
|
+
do {
|
|
12
|
+
left += el.offsetLeft
|
|
13
|
+
top += el.offsetTop
|
|
14
|
+
el = el.offsetParent as HTMLElement
|
|
15
|
+
} while (el)
|
|
16
|
+
const result = {
|
|
17
|
+
left,
|
|
18
|
+
top,
|
|
19
|
+
width,
|
|
20
|
+
height,
|
|
21
|
+
right: left + width,
|
|
22
|
+
bottom: top + height,
|
|
23
|
+
x: left,
|
|
24
|
+
y: top,
|
|
25
|
+
}
|
|
26
|
+
return { ...result, toJSON: () => JSON.stringify(result) }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default getClientRect
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid'
|
|
2
|
+
let pingbackId = ''
|
|
3
|
+
|
|
4
|
+
const idLength = 16
|
|
5
|
+
|
|
6
|
+
/* istanbul ignore next */
|
|
7
|
+
const noUUIDRandom = () => {
|
|
8
|
+
let result = ''
|
|
9
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
10
|
+
const charactersLength = characters.length
|
|
11
|
+
for (let i = 0; i < idLength; i++) {
|
|
12
|
+
result += characters.charAt(Math.floor(Math.random() * charactersLength))
|
|
13
|
+
}
|
|
14
|
+
return result
|
|
15
|
+
}
|
|
16
|
+
const getPingbackId = () => {
|
|
17
|
+
// it exists in memory
|
|
18
|
+
if (!pingbackId) {
|
|
19
|
+
try {
|
|
20
|
+
// it exists in storage
|
|
21
|
+
pingbackId = sessionStorage.getItem('giphyPingbackId') as string
|
|
22
|
+
} catch (_) {}
|
|
23
|
+
if (!pingbackId) {
|
|
24
|
+
// we need to create it
|
|
25
|
+
const hexTime = new Date().getTime().toString(16) // was told to mimic what we had
|
|
26
|
+
try {
|
|
27
|
+
// React Native doesn't support uuid without a polyfill
|
|
28
|
+
pingbackId = `${hexTime}${uuid().replace(/-/g, '')}`.substring(0, idLength) // 16 character max
|
|
29
|
+
} catch (error) {
|
|
30
|
+
/* istanbul ignore next */
|
|
31
|
+
pingbackId = noUUIDRandom()
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
// save in storage
|
|
35
|
+
sessionStorage.setItem('giphyPingbackId', pingbackId)
|
|
36
|
+
} catch (_) {}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return pingbackId
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default getPingbackId
|
package/src/gif-utils.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { IGif, IImage, IImages, ImageAllTypes, IRendition } from '@giphy/js-types'
|
|
2
|
+
import IVideo from '@giphy/js-types/dist/video'
|
|
3
|
+
import bestfit from './bestfit'
|
|
4
|
+
import { pick, take, without } from './collections'
|
|
5
|
+
import { SUPPORTS_WEBP } from './webp-check'
|
|
6
|
+
|
|
7
|
+
export const getSpecificRendition = (
|
|
8
|
+
{ images, is_sticker: isSticker }: IGif,
|
|
9
|
+
renditionLabel: string,
|
|
10
|
+
isStill = false,
|
|
11
|
+
useVideo = false
|
|
12
|
+
) => {
|
|
13
|
+
if (!images || !renditionLabel) return ''
|
|
14
|
+
isStill = isStill && !useVideo
|
|
15
|
+
// @ts-ignore come back to this
|
|
16
|
+
const rendition = images[`${renditionLabel}${isStill ? '_still' : ''}`] as ImageAllTypes
|
|
17
|
+
|
|
18
|
+
if (rendition) {
|
|
19
|
+
if (isSticker || isStill) {
|
|
20
|
+
return rendition.url
|
|
21
|
+
}
|
|
22
|
+
const webP = SUPPORTS_WEBP && rendition.webp
|
|
23
|
+
return useVideo ? rendition.mp4 : webP || rendition.url
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return ''
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface IRenditionWithName extends IRendition {
|
|
30
|
+
renditionName: keyof IImages
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const getBestVideo = (video: IGif['video'], width: number, height: number) => {
|
|
34
|
+
let assets = video?.assets
|
|
35
|
+
if (assets) {
|
|
36
|
+
assets = { ...assets }
|
|
37
|
+
// @ts-ignore we don't show source according to the existing code
|
|
38
|
+
delete assets.source
|
|
39
|
+
const filteredAssets = Object.values(assets).sort((a: IRendition, b: IRendition) => a.width - b.width)
|
|
40
|
+
return bestfit(filteredAssets, width, height) as IImage
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const getRenditions = (type: IGif['type'], images: IImages, video?: IVideo) =>
|
|
45
|
+
type === 'video' && video && video.previews && !Object.keys(images).length ? video.previews : images
|
|
46
|
+
|
|
47
|
+
export const getBestRendition = (
|
|
48
|
+
images: IImages,
|
|
49
|
+
gifWidth: number,
|
|
50
|
+
gifHeight: number,
|
|
51
|
+
scaleUpMaxPixels?: number
|
|
52
|
+
): IRenditionWithName => {
|
|
53
|
+
const checkRenditions = pick(images, [
|
|
54
|
+
'original',
|
|
55
|
+
'fixed_width',
|
|
56
|
+
'fixed_height',
|
|
57
|
+
'fixed_width_small',
|
|
58
|
+
'fixed_height_small',
|
|
59
|
+
])
|
|
60
|
+
const testImages = Object.entries(checkRenditions).map(([renditionName, val]) => ({
|
|
61
|
+
renditionName,
|
|
62
|
+
...val,
|
|
63
|
+
})) as IRenditionWithName[]
|
|
64
|
+
return bestfit(testImages, gifWidth, gifHeight, scaleUpMaxPixels) as IRenditionWithName
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type Options = {
|
|
68
|
+
isStill?: boolean
|
|
69
|
+
useVideo?: boolean
|
|
70
|
+
scaleUpMaxPixels?: number
|
|
71
|
+
}
|
|
72
|
+
export const getBestRenditionUrl = (
|
|
73
|
+
{ images, video, type }: IGif,
|
|
74
|
+
gifWidth: number,
|
|
75
|
+
gifHeight: number,
|
|
76
|
+
options: Options = { isStill: false, useVideo: false }
|
|
77
|
+
): keyof IImages | '' => {
|
|
78
|
+
if (!gifWidth || !gifHeight || !images) return ''
|
|
79
|
+
const { useVideo, isStill, scaleUpMaxPixels } = options
|
|
80
|
+
const renditions = getRenditions(type, images, video)
|
|
81
|
+
const { renditionName } = getBestRendition(renditions, gifWidth, gifHeight, scaleUpMaxPixels)
|
|
82
|
+
|
|
83
|
+
// still, video, webp or gif
|
|
84
|
+
const key = `${renditionName}${isStill && !useVideo ? '_still' : ''}` as keyof IImages
|
|
85
|
+
const rendition = renditions[key] as ImageAllTypes
|
|
86
|
+
|
|
87
|
+
const match = useVideo ? rendition.mp4 : SUPPORTS_WEBP && rendition.webp ? rendition.webp : rendition.url
|
|
88
|
+
return (match || '') as keyof IImages
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const getGifHeight = ({ images }: IGif, gifWidth: number) => {
|
|
92
|
+
const { fixed_width } = images
|
|
93
|
+
if (fixed_width) {
|
|
94
|
+
const { width, height } = fixed_width
|
|
95
|
+
const aspectRatio = width / height
|
|
96
|
+
return Math.round(gifWidth / aspectRatio)
|
|
97
|
+
}
|
|
98
|
+
return 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const getGifWidth = ({ images }: IGif, gifHeight: number) => {
|
|
102
|
+
const { fixed_width } = images
|
|
103
|
+
if (fixed_width) {
|
|
104
|
+
const { width, height } = fixed_width
|
|
105
|
+
const aspectRatio = width / height
|
|
106
|
+
return Math.round(gifHeight * aspectRatio)
|
|
107
|
+
}
|
|
108
|
+
return 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* GIF Text - Alt Text: Generates alt text for
|
|
113
|
+
* GIF images based on username and tags.
|
|
114
|
+
* @prop {Gif}
|
|
115
|
+
* @return {String} GIF alt text.
|
|
116
|
+
*/
|
|
117
|
+
export const getAltText = ({ alt_text, user, tags = [], is_sticker = false, title = '' }: IGif): string => {
|
|
118
|
+
if (alt_text) {
|
|
119
|
+
return alt_text
|
|
120
|
+
}
|
|
121
|
+
if (title) {
|
|
122
|
+
return title
|
|
123
|
+
}
|
|
124
|
+
const username = (user && user.username) || ''
|
|
125
|
+
const filteredTags = take(without(tags, ['transparent']), username ? 4 : 5)
|
|
126
|
+
return `${username ? `${username} ` : ``}${filteredTags.join(' ')} ${is_sticker ? 'Sticker' : 'GIF'}`
|
|
127
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { default as bestfit, setRenditionScaleUpMaxPixels } from './bestfit'
|
|
2
|
+
export * from './collections'
|
|
3
|
+
export { default as getClientRect } from './get-client-rect-from-el'
|
|
4
|
+
export { default as getPingbackId } from './get-pingback-id'
|
|
5
|
+
export {
|
|
6
|
+
getAltText,
|
|
7
|
+
getBestRendition,
|
|
8
|
+
getBestRenditionUrl,
|
|
9
|
+
getBestVideo,
|
|
10
|
+
getGifHeight,
|
|
11
|
+
getGifWidth,
|
|
12
|
+
getSpecificRendition,
|
|
13
|
+
} from './gif-utils'
|
|
14
|
+
export * from './log'
|
|
15
|
+
export * from './sdk-headers'
|
|
16
|
+
export { checkIfWebP } from './webp-check'
|
package/src/log.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* istanbul ignore next */
|
|
2
|
+
export enum LogLevel {
|
|
3
|
+
DEBUG = 0,
|
|
4
|
+
INFO = 1,
|
|
5
|
+
WARN = 2,
|
|
6
|
+
ERROR = 3,
|
|
7
|
+
}
|
|
8
|
+
/* istanbul ignore next */
|
|
9
|
+
export const Logger = {
|
|
10
|
+
ENABLED:
|
|
11
|
+
typeof window !== 'undefined' &&
|
|
12
|
+
typeof location !== 'undefined' &&
|
|
13
|
+
location.search.indexOf('giphy-debug') !== -1,
|
|
14
|
+
LEVEL: 0,
|
|
15
|
+
PREFIX: 'GiphyJS',
|
|
16
|
+
debug: (...msg: any) => {
|
|
17
|
+
if (Logger.ENABLED && Logger.LEVEL <= LogLevel.DEBUG) {
|
|
18
|
+
console.debug(Logger.PREFIX, ...msg) // eslint-disable-line no-console
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
info: (...msg: any) => {
|
|
22
|
+
if (Logger.ENABLED && Logger.LEVEL <= LogLevel.INFO) {
|
|
23
|
+
console.info(Logger.PREFIX, ...msg) // eslint-disable-line no-console
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
warn: (...msg: any) => {
|
|
27
|
+
if (Logger.ENABLED && Logger.LEVEL <= LogLevel.WARN) {
|
|
28
|
+
console.warn(Logger.PREFIX, ...msg) // eslint-disable-line no-console
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
error: (...msg: any) => {
|
|
32
|
+
if (Logger.ENABLED && Logger.LEVEL <= LogLevel.ERROR) {
|
|
33
|
+
console.error(Logger.PREFIX, ...msg) // eslint-disable-line no-console
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type GiphySDKRequestHeader = 'X-GIPHY-SDK-NAME' | 'X-GIPHY-SDK-VERSION' | 'X-GIPHY-SDK-PLATFORM'
|
|
2
|
+
const gl = ((typeof window !== 'undefined' ? window : global) || {}) as any
|
|
3
|
+
// define _GIPHY_SDK_HEADERS_ if they don't exist
|
|
4
|
+
gl._GIPHY_SDK_HEADERS_ =
|
|
5
|
+
gl._GIPHY_SDK_HEADERS_ ||
|
|
6
|
+
(gl.Headers
|
|
7
|
+
? new gl.Headers({
|
|
8
|
+
'X-GIPHY-SDK-PLATFORM': 'web',
|
|
9
|
+
})
|
|
10
|
+
: undefined)
|
|
11
|
+
|
|
12
|
+
export const getGiphySDKRequestHeaders = (): Headers | undefined => gl._GIPHY_SDK_HEADERS_
|
|
13
|
+
|
|
14
|
+
export const appendGiphySDKRequestHeader = (key: GiphySDKRequestHeader, value: string) =>
|
|
15
|
+
getGiphySDKRequestHeaders()?.set(key, value)
|
|
16
|
+
|
|
17
|
+
export const appendGiphySDKRequestParam = (key: GiphySDKRequestHeader, value: string) =>
|
|
18
|
+
getGiphySDKRequestHeaders()?.set(key, value)
|