Skip to content
This repository was archived by the owner on Feb 6, 2024. It is now read-only.

Commit b72d922

Browse files
feat: add Tenor API and retrieving some trending Gifs
1 parent efc21ad commit b72d922

File tree

9 files changed

+227
-10
lines changed

9 files changed

+227
-10
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
app-gif {
2+
ion-content {
3+
--background: var(--ion-color-light);
4+
}
5+
6+
div.gifs-container {
7+
display: flex;
8+
flex-direction: row;
9+
flex-wrap: wrap;
10+
11+
div.gifs-column {
12+
display: block;
13+
flex-grow: 1;
14+
flex-basis: 50%;
15+
max-width: 50%;
16+
17+
img {
18+
width: calc(100% - 4px);
19+
padding: 2px;
20+
}
21+
}
22+
}
23+
}

studio/src/app/modals/editor/app-gif/app-gif.tsx

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import {Component, Element, Listen} from '@stencil/core';
1+
import {Component, Element, Listen, State} from '@stencil/core';
2+
3+
import {GifService} from '../../../services/gif/gif.service';
24

35
@Component({
46
tag: 'app-gif',
@@ -8,8 +10,24 @@ export class AppGif {
810

911
@Element() el: HTMLElement;
1012

13+
private gifService: GifService;
14+
15+
@State()
16+
private gifsOdd: TenorGif[];
17+
18+
@State()
19+
private gifsEven: TenorGif[];
20+
21+
constructor() {
22+
this.gifService = GifService.getInstance();
23+
}
24+
1125
async componentDidLoad() {
1226
history.pushState({modal: true}, null);
27+
28+
const gifs: TenorGif[] = await this.gifService.getTrending();
29+
this.gifsOdd = gifs.filter((_a, i) => i % 2);
30+
this.gifsEven = gifs.filter((_a, i) => !(i % 2));
1331
}
1432

1533
@Listen('window:popstate')
@@ -30,13 +48,41 @@ export class AppGif {
3048
<ion-icon name="close"></ion-icon>
3149
</ion-button>
3250
</ion-buttons>
33-
<ion-title class="ion-text-uppercase">Ready to publish?</ion-title>
51+
<ion-title class="ion-text-uppercase">Pick a Gif</ion-title>
3452
</ion-toolbar>
3553
</ion-header>,
3654
<ion-content padding>
37-
Find cool Gifs
38-
</ion-content>
55+
<div class="gifs-container">
56+
<div class="gifs-column">
57+
{this.renderGifs(this.gifsOdd)}
58+
</div>
59+
<div class="gifs-column">
60+
{this.renderGifs(this.gifsEven)}
61+
</div>
62+
</div>
63+
</ion-content>,
64+
<ion-footer>
65+
<ion-toolbar>
66+
<ion-searchbar debounce={500} placeholder="Search Tenor"></ion-searchbar>
67+
</ion-toolbar>
68+
</ion-footer>
3969
];
4070
}
4171

72+
private renderGifs(gifs: TenorGif[]) {
73+
if (gifs && gifs.length > 0) {
74+
return (
75+
gifs.map((gif: TenorGif) => {
76+
if (gif.media && gif.media.length > 0 && gif.media[0].tinygif && gif.media[0].tinygif.url) {
77+
return <img src={gif.media[0].tinygif.url} alt={gif.title ? gif.title : gif.url}></img>
78+
} else {
79+
return undefined;
80+
}
81+
})
82+
);
83+
} else {
84+
return undefined;
85+
}
86+
}
87+
4288
}

studio/src/app/pages/editor/app-editor/app-editor.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,16 +286,31 @@ export class AppEditor {
286286
});
287287

288288
popover.onDidDismiss().then(async (detail: OverlayEventDetail) => {
289+
if (detail.data.template === SlideTemplate.GIF) {
290+
await this.openGifPicker();
291+
}
292+
289293
if (detail.data.slide) {
290294
await this.concatSlide(detail.data.slide);
291-
292295
await this.slideToLastSlide();
293296
}
294297
});
295298

296299
await popover.present();
297300
}
298301

302+
private async openGifPicker() {
303+
const modal: HTMLIonModalElement = await this.modalController.create({
304+
component: 'app-gif'
305+
});
306+
307+
modal.onDidDismiss().then(async (_detail: OverlayEventDetail) => {
308+
// TODO: apply gif
309+
});
310+
311+
await modal.present();
312+
}
313+
299314
@Listen('actionPublish')
300315
async onActionPublish() {
301316
// No slides, no publish

studio/src/app/popovers/editor/app-slide-type/app-slide-type.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ export class AppSlideType {
3333

3434
private async addSlide(template: SlideTemplate) {
3535
const slide: any = await EditorUtils.createSlide(template);
36-
await this.closePopover(slide);
36+
await this.closePopover(slide, template);
3737
}
3838

39-
async closePopover(slide: any) {
39+
async closePopover(slide: any, template: SlideTemplate) {
4040
await (this.el.closest('ion-popover') as HTMLIonModalElement).dismiss({
41-
slide: slide
41+
slide: slide,
42+
template: template
4243
});
4344
}
4445

studio/src/app/services/environment/environment-config.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface EnvironmentConfig {
1111
appUrl: string;
1212
apiUrl: string;
1313
firebase: EnvironmentFirebaseConfig;
14+
tenorKey: string
1415
}
1516

1617
export function setupConfig(config: EnvironmentConfig) {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {EnvironmentConfigService} from '../environment/environment-config.service';
2+
3+
import {get, set} from 'idb-keyval';
4+
5+
import {ErrorService} from '../error/error.service';
6+
7+
export class GifService {
8+
9+
private static instance: GifService;
10+
11+
private errorService: ErrorService;
12+
13+
private constructor() {
14+
// Private constructor, singleton
15+
this.errorService = ErrorService.getInstance();
16+
}
17+
18+
static getInstance() {
19+
if (!GifService.instance) {
20+
GifService.instance = new GifService();
21+
}
22+
return GifService.instance;
23+
}
24+
25+
// call the trending and category endpoints
26+
getTrending(): Promise<TenorGif[]> {
27+
return new Promise<TenorGif[]>(async (resolve, reject) => {
28+
const apiKey: string = EnvironmentConfigService.getInstance().get('tenorKey');
29+
const searchTerm: string = 'excited';
30+
31+
const anonymousId: string = await this.getAnonymousId();
32+
33+
const searchUrl = 'https://api.tenor.com/v1/search?tag=' + searchTerm + '&key=' +
34+
apiKey + '&ar_range=wide&limit=' + 8 + '&anon_id=' + anonymousId + '&media_filter=minimal';
35+
36+
try {
37+
const rawResponse: Response = await fetch(searchUrl);
38+
39+
const response: TenorTrendingResponse = JSON.parse(await rawResponse.text());
40+
41+
if (!response) {
42+
reject('Tenor trending could not be fetched');
43+
}
44+
45+
resolve(response.results);
46+
} catch (err) {
47+
this.errorService.error(err.message);
48+
reject(err);
49+
}
50+
});
51+
}
52+
53+
private getAnonymousId(): Promise<string> {
54+
return new Promise<string>(async (resolve, reject) => {
55+
const localAnonymousId: string = await get('tenor_anonid');
56+
57+
if (localAnonymousId) {
58+
resolve(localAnonymousId);
59+
return;
60+
}
61+
62+
const apiKey: string = EnvironmentConfigService.getInstance().get('tenorKey');
63+
64+
try {
65+
const rawResponse: Response = await fetch('https://api.tenor.com/v1/anonid?key=' + apiKey);
66+
67+
const response: TenorAnonymousResponse = JSON.parse(await rawResponse.text());
68+
69+
if (!response) {
70+
reject('Tenor anonymous ID could not be fetched');
71+
}
72+
73+
const anonymousId: string = response.anon_id;
74+
75+
await set('tenor_anonid', anonymousId);
76+
77+
resolve(anonymousId);
78+
} catch (err) {
79+
reject(err);
80+
}
81+
});
82+
}
83+
}

studio/src/app/utils/tenor.d.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
interface TenorMediaObject {
2+
preview: string;
3+
url: string;
4+
dims: number[];
5+
size: number;
6+
}
7+
8+
interface TenorMedia {
9+
gif: TenorMediaObject;
10+
mediumgif: TenorMediaObject;
11+
tinygif: TenorMediaObject;
12+
nanogif: TenorMediaObject;
13+
mp4: TenorMediaObject;
14+
loopedmp4: TenorMediaObject;
15+
tinymp4: TenorMediaObject;
16+
nanomp4: TenorMediaObject;
17+
webm: TenorMediaObject;
18+
tinywebm: TenorMediaObject;
19+
nanowebm: TenorMediaObject;
20+
}
21+
22+
interface TenorGif {
23+
id: string;
24+
25+
created: number;
26+
27+
hasaudio: boolean;
28+
shares: number;
29+
30+
title: string;
31+
tags: string[];
32+
33+
url: string;
34+
itemurl: string;
35+
36+
media: TenorMedia[];
37+
}
38+
39+
40+
interface TenorTrendingResponse {
41+
results: TenorGif[];
42+
}
43+
44+
interface TenorAnonymousResponse {
45+
anon_id: string;
46+
}

studio/src/global/app-dev.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ setupConfig({
1515
projectId: '<@FIREBASE_PROJECT_ID@>',
1616
storageBucket: '<@FIREBASE_STORAGE_BUCKET@>',
1717
messagingSenderId: '<@FIREBASE_MESSAGING_SENDER_ID@>'
18-
}
18+
},
19+
tenorKey: '<@TENOR_KEY@>'
1920
});

studio/src/global/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ setupConfig({
1515
projectId: '<@FIREBASE_PROJECT_ID@>',
1616
storageBucket: '<@FIREBASE_STORAGE_BUCKET@>',
1717
messagingSenderId: '<@FIREBASE_MESSAGING_SENDER_ID@>'
18-
}
18+
},
19+
tenorKey: '<@TENOR_KEY@>'
1920
});

0 commit comments

Comments
 (0)