blob: 55b4794b7a9ef560d50167c7410e106355e836b6 [file] [log] [blame]
/**
* @module skottie-drive-sk
* @description <h2><code>skottie-drive-sk</code></h2>
*
* Presents a page for previewing Lottie files that are coming
* from Google Drive.
*
* All the work in the app is done in the browser, i.e. no server side
* work is done. Authentication is done via the Google JS client library
* and rendering of the Lottie files is done using the WASM version of
* Skia.
*
* A link from Google Drive to preview a JSON file will include a query
* parameter named 'state' that looks like:
*
* {
* "ids": ["0Bz0bd"],
* "action":"open",
* "userId":"103354693083460731603"
* }
*
* Where 'ids' is a list of Google Drive document ids that can be used
* via the Google Drive API to retrieve the document or metadata about
* the document.
*
*/
import '../skottie-player-sk';
import '../../../elements-sk/modules/error-toast-sk';
import { html } from 'lit-html';
import { until } from 'lit-html/directives/until.js';
import { $$ } from '../../../infra-sk/modules/dom';
import { define } from '../../../elements-sk/modules/define';
import { errorMessage } from '../../../elements-sk/modules/errorMessage';
import { ElementSk } from '../../../infra-sk/modules/ElementSk';
import {
SkottiePlayerConfig,
SkottiePlayerSk,
} from '../skottie-player-sk/skottie-player-sk';
import { LottieAnimation } from '../types';
import '../../../infra-sk/modules/theme-chooser-sk';
import '../../../infra-sk/modules/app-sk';
// See https://stackoverflow.com/a/45352250
interface WindowWithGAPILoaded extends Window {
gapi: any;
}
declare let window: WindowWithGAPILoaded;
// It is assumed that this symbol is being provided by a version.js file loaded in before this
// file.
declare const SKIA_VERSION: string;
// gapiLoaded is a promise that resolves when the 'gapi' JS library is
// finished loading.
const gapiLoaded = new Promise<void>((resolve) => {
const check = () => {
if (window.gapi !== undefined) {
resolve();
} else {
setTimeout(check, 10);
}
};
setTimeout(check, 10);
});
interface ErrorResponse {
result: {
error: {
message: string;
};
};
}
interface DriveGetResponse {
headers: Record<string, string>;
result: LottieAnimation;
}
interface URLState {
ids: string[];
}
export class SkottieDriveSk extends ElementSk {
private static template = (ele: SkottieDriveSk) => html`
<app-sk>
<header>
<h2>Skia Lottie Drive Previewer</h2>
<span>
<a href="https://skia.googlesource.com/skia/+show/${SKIA_VERSION}"
>${SKIA_VERSION.slice(0, 7)}</a
>
<theme-chooser-sk></theme-chooser-sk>
</span>
</header>
<main>${ele.players()}</main>
<footer>
<error-toast-sk></error-toast-sk>
</footer>
</app-sk>
`;
// caption returns a promise that resolves to the filename of the document
// with the given drive id.
private static caption = (id: string) =>
gapiLoaded.then(() =>
window.gapi.client.drive.files
.get({
alt: 'json',
fileId: id,
})
.then(
(response: DriveGetResponse) =>
html`<pre>${response.result.name}</pre>`
)
.catch((err: ErrorResponse) => {
errorMessage(err.result.error.message, 0);
})
);
// players returns one <skottie-player-sk> for each id.
private players = () =>
this.ids.map(
(id, i) => html` <figure>
<skottie-player-sk id="x${i}"></skottie-player-sk>
<figcaption>
<p>${until(SkottieDriveSk.caption(id), 'Loading...')}</p>
<p class="errors" id="errors${i}"></p>
</figcaption>
</figure>`
);
// The list of ids of documents to display.
private ids: string[] = [];
constructor() {
super(SkottieDriveSk.template);
}
connectedCallback(): void {
super.connectedCallback();
this._render();
gapiLoaded.then(() => {
// Load both the JS Apiary client library and OAuth 2 client library.
window.gapi.load('client:auth2', () => {
this.initClient();
});
});
}
initClient(): void {
const postInit = () => {
const isSignedIn = window.gapi.auth2.getAuthInstance().isSignedIn;
// Listen for sign-in state changes.
isSignedIn.listen(this.updateSigninStatus);
// Handle the initial sign-in state.
this.updateSigninStatus(isSignedIn.get());
};
window.gapi.client
.init({
// eslint-disable-next-line no-useless-concat
apiKey: 'AIzaSyD2US0bcYT2Vh' + 'guMezYgDa4lbZc6rIQbDg', // API Key is locked to https://skottie.skia.org, so it's safe to hardcode here.
clientId:
'145247227042-fetft5vnkf582o817e1t553cm3tjvobl.apps.googleusercontent.com', // Not protected info (clientSecret would be).
discoveryDocs: [
'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest',
],
scope:
'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/drive.install',
})
.then(postInit);
}
// updateSigninStatus is called when the signed in status changes.
updateSigninStatus(isSignedIn: boolean): void {
if (!isSignedIn) {
window.gapi.auth2.getAuthInstance().signIn();
} else {
this.loadFiles();
}
}
loadFiles(): void {
// The state parameter is JSON that looks like:
//
// {
// "ids": ["0Bz0bd"],
// "action":"open",
// "userId":"103354693083460731603"
// }
//
// See https://developers.google.com/drive/api/v3/integrate-open for more
// details.
let ids = ['12M0hlsK-zYCrKU6TG-Bji5Kcr9hWoyJw'];
const stateParam = new URL(document.location.href).searchParams.get(
'state'
);
if (stateParam) {
ids = (JSON.parse(stateParam) as URLState).ids;
}
// Render a <skottie-player-sk> for each id.
this.ids = ids;
this._render();
// Now kick off a fetch request for each player that retrieves the JSON
// and populates the player.
ids.forEach((id: string, i: number) => {
// Media.
window.gapi.client.drive.files
.get({
alt: 'media',
fileId: id,
})
.then((response: DriveGetResponse) => {
if (response.headers['Content-Type'] !== 'application/json') {
$$<HTMLParagraphElement>(`#errors${i}`, this)!.textContent =
'Error: Not a JSON file.';
errorMessage('Can only process JSON files.', 0);
return;
}
const lottie = response.result;
const init: SkottiePlayerConfig = {
width: +lottie.w || 128,
height: +lottie.h || 128,
lottie: lottie,
fps: 0,
};
$$<SkottiePlayerSk>(`#x${i}`, this)!
.initialize(init)
.catch((msg) => {
$$<HTMLParagraphElement>(`#errors${i}`, this)!.textContent =
'Error: Not a valid Lottie file.';
errorMessage(msg, 0);
});
})
.catch((err: ErrorResponse) => {
errorMessage(err.result.error.message, 0);
});
});
}
}
define('skottie-drive-sk', SkottieDriveSk);