| /** |
| * @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); |