aboutsummaryrefslogtreecommitdiff
path: root/src/client/apis
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/apis')
-rw-r--r--src/client/apis/GoogleAuthenticationManager.scss19
-rw-r--r--src/client/apis/GoogleAuthenticationManager.tsx140
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts38
3 files changed, 183 insertions, 14 deletions
diff --git a/src/client/apis/GoogleAuthenticationManager.scss b/src/client/apis/GoogleAuthenticationManager.scss
new file mode 100644
index 000000000..13bde822d
--- /dev/null
+++ b/src/client/apis/GoogleAuthenticationManager.scss
@@ -0,0 +1,19 @@
+.authorize-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .paste-target {
+ padding: 5px;
+ width: 100%;
+ }
+
+ .avatar {
+ border-radius: 50%;
+ }
+
+ .welcome {
+ font-style: italic;
+ margin-top: 15px;
+ }
+} \ No newline at end of file
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
new file mode 100644
index 000000000..01dac3996
--- /dev/null
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -0,0 +1,140 @@
+import { observable, action, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import MainViewModal from "../views/MainViewModal";
+import { Opt } from "../../new_fields/Doc";
+import { Identified } from "../Network";
+import { RouteStore } from "../../server/RouteStore";
+import "./GoogleAuthenticationManager.scss";
+
+const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
+const prompt = "Paste authorization code here...";
+
+@observer
+export default class GoogleAuthenticationManager extends React.Component<{}> {
+ public static Instance: GoogleAuthenticationManager;
+ @observable private openState = false;
+ private authenticationLink: Opt<string> = undefined;
+ @observable private authenticationCode: Opt<string> = undefined;
+ @observable private clickedState = false;
+ @observable private success: Opt<boolean> = undefined;
+ @observable private displayLauncher = true;
+ @observable private avatar: Opt<string> = undefined;
+ @observable private username: Opt<string> = undefined;
+
+ private set isOpen(value: boolean) {
+ runInAction(() => this.openState = value);
+ }
+
+ private set hasBeenClicked(value: boolean) {
+ runInAction(() => this.clickedState = value);
+ }
+
+ public fetchOrGenerateAccessToken = async () => {
+ let response = await Identified.FetchFromServer(RouteStore.readGoogleAccessToken);
+ // if this is an authentication url, activate the UI to register the new access token
+ if (new RegExp(AuthenticationUrl).test(response)) {
+ this.isOpen = true;
+ this.authenticationLink = response;
+ return new Promise<string>(async resolve => {
+ const disposer = reaction(
+ () => this.authenticationCode,
+ authenticationCode => {
+ if (authenticationCode) {
+ Identified.PostToServer(RouteStore.writeGoogleAccessToken, { authenticationCode }).then(
+ ({ access_token, avatar, name }) => {
+ runInAction(() => {
+ this.avatar = avatar;
+ this.username = name;
+ });
+ this.beginFadeout();
+ disposer();
+ resolve(access_token);
+ },
+ action(() => {
+ this.hasBeenClicked = false;
+ this.success = false;
+ })
+ );
+ }
+ }
+ );
+ });
+ }
+ // otherwise, we already have a valid, stored access token
+ return response;
+ }
+
+ beginFadeout = action(() => {
+ this.success = true;
+ this.authenticationCode = undefined;
+ this.displayLauncher = false;
+ this.hasBeenClicked = false;
+ setTimeout(action(() => {
+ this.isOpen = false;
+ setTimeout(action(() => {
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.avatar = undefined;
+ this.username = undefined;
+ }), 500);
+ }), 3000);
+ });
+
+ constructor(props: {}) {
+ super(props);
+ GoogleAuthenticationManager.Instance = this;
+ }
+
+ private handleClick = () => {
+ window.open(this.authenticationLink);
+ setTimeout(() => this.hasBeenClicked = true, 500);
+ }
+
+ private handlePaste = action((e: React.ChangeEvent<HTMLInputElement>) => {
+ this.authenticationCode = e.currentTarget.value;
+ });
+
+ private get renderPrompt() {
+ return (
+ <div className={'authorize-container'}>
+ {this.displayLauncher ? <button
+ className={"dispatch"}
+ onClick={this.handleClick}
+ style={{ marginBottom: this.clickedState ? 15 : 0 }}
+ >Authorize a Google account...</button> : (null)}
+ {this.clickedState ? <input
+ className={'paste-target'}
+ onChange={this.handlePaste}
+ placeholder={prompt}
+ /> : (null)}
+ {this.avatar ? <img
+ className={'avatar'}
+ src={this.avatar}
+ /> : (null)}
+ {this.username ? <span
+ className={'welcome'}
+ >Welcome to Dash, {this.username}
+ </span> : (null)}
+ </div>
+ );
+ }
+
+ private get dialogueBoxStyle() {
+ const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
+ return { borderColor, transition: "0.2s borderColor ease" };
+ }
+
+ render() {
+ return (
+ <MainViewModal
+ isDisplayed={this.openState}
+ interactive={true}
+ contents={this.renderPrompt}
+ overlayDisplayedOpacity={0.9}
+ dialogueBoxStyle={this.dialogueBoxStyle}
+ />
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 29cc042b6..e93fa6eb4 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -13,15 +13,12 @@ import { Docs, DocumentOptions } from "../../documents/Documents";
import { NewMediaItemResult, MediaItem } from "../../../server/apis/google/SharedTypes";
import { AssertionError } from "assert";
import { DocumentView } from "../../views/nodes/DocumentView";
-import { DocumentManager } from "../../util/DocumentManager";
import { Identified } from "../../Network";
+import GoogleAuthenticationManager from "../GoogleAuthenticationManager";
export namespace GooglePhotos {
- const endpoint = async () => {
- const accessToken = await Identified.FetchFromServer(RouteStore.googlePhotosAccessToken);
- return new Photos(accessToken);
- };
+ const endpoint = async () => new Photos(await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken());
export enum MediaType {
ALL_MEDIA = 'ALL_MEDIA',
@@ -89,9 +86,14 @@ export namespace GooglePhotos {
}
const resolved = title ? title : (StrCast(collection.title) || `Dash Collection (${collection[Id]}`);
const { id, productUrl } = await Create.Album(resolved);
- const newMediaItemResults = await Transactions.UploadImages(images, { id }, descriptionKey);
- if (newMediaItemResults) {
- const mediaItems = newMediaItemResults.map(item => item.mediaItem);
+ const response = await Transactions.UploadImages(images, { id }, descriptionKey);
+ if (response) {
+ const { results, failed } = response;
+ let index: Opt<number>;
+ while ((index = failed.pop()) !== undefined) {
+ Doc.RemoveDocFromList(dataDocument, "data", images.splice(index, 1)[0]);
+ }
+ const mediaItems: MediaItem[] = results.map(item => item.mediaItem);
if (mediaItems.length !== images.length) {
throw new AssertionError({ actual: mediaItems.length, expected: images.length });
}
@@ -99,6 +101,9 @@ export namespace GooglePhotos {
for (let i = 0; i < images.length; i++) {
const image = Doc.GetProto(images[i]);
const mediaItem = mediaItems[i];
+ if (!mediaItem) {
+ continue;
+ }
image.googlePhotosId = mediaItem.id;
image.googlePhotosAlbumUrl = productUrl;
image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl;
@@ -304,17 +309,22 @@ export namespace GooglePhotos {
};
export const UploadThenFetch = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption") => {
- const newMediaItems = await UploadImages(sources, album, descriptionKey);
- if (!newMediaItems) {
+ const response = await UploadImages(sources, album, descriptionKey);
+ if (!response) {
return undefined;
}
- const baseUrls: string[] = await Promise.all(newMediaItems.map(item => {
+ const baseUrls: string[] = await Promise.all(response.results.map(item => {
return new Promise<string>(resolve => Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl)));
}));
return baseUrls;
};
- export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption"): Promise<Opt<NewMediaItemResult[]>> => {
+ export interface ImageUploadResults {
+ results: NewMediaItemResult[];
+ failed: number[];
+ }
+
+ export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption"): Promise<Opt<ImageUploadResults>> => {
if (album && "title" in album) {
album = await Create.Album(album.title);
}
@@ -331,8 +341,8 @@ export namespace GooglePhotos {
media.push({ url, description });
}
if (media.length) {
- const uploads: NewMediaItemResult[] = await Identified.PostToServer(RouteStore.googlePhotosMediaUpload, { media, album });
- return uploads;
+ const results = await Identified.PostToServer(RouteStore.googlePhotosMediaUpload, { media, album });
+ return results;
}
};