aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/apis/GoogleAuthenticationManager.scss7
-rw-r--r--src/client/apis/GoogleAuthenticationManager.tsx125
-rw-r--r--src/client/apis/google_docs/GoogleApiClientUtils.ts2
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/MainView.tsx5
-rw-r--r--src/client/views/MainViewModal.tsx2
-rw-r--r--src/client/views/OverlayView.tsx6
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx19
8 files changed, 111 insertions, 57 deletions
diff --git a/src/client/apis/GoogleAuthenticationManager.scss b/src/client/apis/GoogleAuthenticationManager.scss
index 13bde822d..bd30dd94f 100644
--- a/src/client/apis/GoogleAuthenticationManager.scss
+++ b/src/client/apis/GoogleAuthenticationManager.scss
@@ -16,4 +16,11 @@
font-style: italic;
margin-top: 15px;
}
+
+ .disconnect {
+ font-size: 10px;
+ margin-top: 20px;
+ color: red;
+ cursor: grab;
+ }
} \ No newline at end of file
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 417dc3c3b..018c980d8 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -1,10 +1,11 @@
-import { observable, action, reaction, runInAction } from "mobx";
+import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import MainViewModal from "../views/MainViewModal";
import { Opt } from "../../new_fields/Doc";
import { Networking } from "../Network";
import "./GoogleAuthenticationManager.scss";
+import { Scripting } from "../util/Scripting";
const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
const prompt = "Paste authorization code here...";
@@ -15,64 +16,89 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
private authenticationLink: Opt<string> = undefined;
@observable private openState = false;
@observable private authenticationCode: Opt<string> = undefined;
- @observable private clickedState = false;
+ @observable private showPasteTargetState = false;
@observable private success: Opt<boolean> = undefined;
@observable private displayLauncher = true;
- @observable private avatar: Opt<string> = undefined;
- @observable private username: Opt<string> = undefined;
+ @observable private credentials: any;
+ private disposer: Opt<IReactionDisposer>;
private set isOpen(value: boolean) {
runInAction(() => this.openState = value);
}
- private set hasBeenClicked(value: boolean) {
- runInAction(() => this.clickedState = value);
+ private set shouldShowPasteTarget(value: boolean) {
+ runInAction(() => this.showPasteTargetState = value);
}
- public fetchOrGenerateAccessToken = async () => {
- const response = await Networking.FetchFromServer("/readGoogleAccessToken");
+ public cancel() {
+ this.openState && this.resetState(0, 0);
+ }
+
+ public fetchOrGenerateAccessToken = async (displayIfFound = false) => {
+ let response: any = await Networking.FetchFromServer("/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.disposer?.();
+ this.disposer = reaction(
() => this.authenticationCode,
async authenticationCode => {
- if (authenticationCode) {
- disposer();
- const { access_token, avatar, name } = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
+ if (authenticationCode && /\d{1}\/[\w-]{55}/.test(authenticationCode)) {
+ this.disposer?.();
+ const response = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
runInAction(() => {
- this.avatar = avatar;
- this.username = name;
- this.hasBeenClicked = false;
- this.success = false;
+ this.success = true;
+ this.credentials = response;
});
- this.beginFadeout();
- resolve(access_token);
+ this.resetState();
+ resolve(response.access_token);
}
}
);
});
}
- // otherwise, we already have a valid, stored access token
- return response;
+
+ // otherwise, we already have a valid, stored access token and user info
+ response = JSON.parse(response);
+ if (displayIfFound) {
+ runInAction(() => {
+ this.success = true;
+ this.credentials = response;
+ });
+ this.resetState(-1, -1);
+ this.isOpen = true;
+ }
+ return response.access_token;
}
- beginFadeout = action(() => {
- this.success = true;
+ resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
+ if (!visibleForMS && !fadesOutInMS) {
+ runInAction(() => {
+ this.isOpen = false;
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = undefined;
+ this.shouldShowPasteTarget = false;
+ this.authenticationCode = undefined;
+ });
+ return;
+ }
this.authenticationCode = undefined;
this.displayLauncher = false;
- this.hasBeenClicked = false;
- setTimeout(action(() => {
- this.isOpen = false;
+ this.shouldShowPasteTarget = false;
+ if (visibleForMS > 0 && fadesOutInMS > 0) {
setTimeout(action(() => {
- this.success = undefined;
- this.displayLauncher = true;
- this.avatar = undefined;
- this.username = undefined;
- }), 500);
- }), 3000);
+ this.isOpen = false;
+ setTimeout(action(() => {
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = undefined;
+ }), fadesOutInMS);
+ }), visibleForMS);
+ }
});
constructor(props: {}) {
@@ -83,27 +109,38 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
private get renderPrompt() {
return (
<div className={'authorize-container'}>
+
{this.displayLauncher ? <button
className={"dispatch"}
onClick={() => {
window.open(this.authenticationLink);
- setTimeout(() => this.hasBeenClicked = true, 500);
+ setTimeout(() => this.shouldShowPasteTarget = true, 500);
}}
- style={{ marginBottom: this.clickedState ? 15 : 0 }}
+ style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
>Authorize a Google account...</button> : (null)}
- {this.clickedState ? <input
+ {this.showPasteTargetState ? <input
className={'paste-target'}
onChange={action(e => this.authenticationCode = e.currentTarget.value)}
placeholder={prompt}
/> : (null)}
- {this.avatar ? <img
- className={'avatar'}
- src={this.avatar}
- /> : (null)}
- {this.username ? <span
- className={'welcome'}
- >Welcome to Dash, {this.username}
- </span> : (null)}
+ {this.credentials ?
+ <>
+ <img
+ className={'avatar'}
+ src={this.credentials.userInfo.picture}
+ />
+ <span
+ className={'welcome'}
+ >Welcome to Dash, {this.credentials.userInfo.name}
+ </span>
+ <div
+ className={'disconnect'}
+ onClick={async () => {
+ await Networking.FetchFromServer("/revokeGoogleAccessToken");
+ this.resetState(0, 0);
+ }}
+ >Disconnect Account</div>
+ </> : (null)}
</div>
);
}
@@ -125,4 +162,6 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
);
}
-} \ No newline at end of file
+}
+
+Scripting.addGlobal("GoogleAuthenticationManager", GoogleAuthenticationManager); \ No newline at end of file
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index fa67ddbef..2f3cac8d3 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -95,7 +95,7 @@ export namespace GoogleApiClientUtils {
export type ExtractResult = { text: string, paragraphs: DeconstructedParagraph[] };
export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): ExtractResult => {
const paragraphs = extractParagraphs(document);
- let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => run as docs_v1.Schema$TextRun).join("")).join("");
+ let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => (run as docs_v1.Schema$TextRun).content).join("")).join("");
text = text.substring(0, text.length - 1);
removeNewlines && text.replace(/\n/g, "");
return { text, paragraphs };
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 185222541..6cca4d69f 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -12,6 +12,7 @@ import { ScriptField } from "../../new_fields/ScriptField";
import { InkingControl } from "./InkingControl";
import { InkTool } from "../../new_fields/InkField";
import { DocumentView } from "./nodes/DocumentView";
+import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -79,6 +80,7 @@ export default class KeyManager {
SelectionManager.DeselectAll();
DictationManager.Controls.stop();
// RecommendationsBox.Instance.closeMenu();
+ GoogleAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
break;
case "delete":
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d60a9d64a..1a285d4ec 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faTerminal, faToggleOn, faFile as fileSolid, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons';
+import { faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
@@ -163,6 +163,7 @@ export class MainView extends React.Component {
library.add(faPhone);
library.add(faClipboard);
library.add(faStamp);
+ library.add(faExternalLinkAlt);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -583,7 +584,7 @@ export class MainView extends React.Component {
{SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
{SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
</svg>
- </div>
+ </div>;
}
render() {
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 9198fe3e3..a7bd5882d 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -4,7 +4,7 @@ import "./MainViewModal.scss";
export interface MainViewOverlayProps {
isDisplayed: boolean;
interactive: boolean;
- contents: string | JSX.Element;
+ contents: string | JSX.Element | null;
dialogueBoxStyle?: React.CSSProperties;
overlayStyle?: React.CSSProperties;
dialogueBoxDisplayedOpacity?: number;
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 20aa14f84..afb6bfb7d 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,7 +1,7 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
-import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../new_fields/Doc";
import { Id } from "../../new_fields/FieldSymbols";
import { NumCast } from "../../new_fields/Types";
import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils } from "../../Utils";
@@ -214,4 +214,6 @@ export class OverlayView extends React.Component {
}
}
// bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error
-Scripting.addGlobal(function addOverlayWindow(Tag: string, options: OverlayElementOptions) { const x = <ScriptingRepl />; OverlayView.Instance.addWindow(x, options); }); \ No newline at end of file
+Scripting.addGlobal(function addOverlayWindow(type: string, options: OverlayElementOptions) {
+ OverlayView.Instance.addWindow(<ScriptingRepl />, options);
+}); \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f5c28b350..ce096f81b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -59,6 +59,7 @@ import "./FormattedTextBox.scss";
import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
import React = require("react");
import { ScriptField } from '../../../../new_fields/ScriptField';
+import GoogleAuthenticationManager from '../../../apis/GoogleAuthenticationManager';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -784,7 +785,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let pullSuccess = false;
if (exportState !== undefined) {
pullSuccess = true;
- dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON()));
+ dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(exportState.state.toJSON()));
setTimeout(() => {
if (this._editorView) {
const state = this._editorView.state;
@@ -802,13 +803,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
- if (exportState && this._editorView) {
- const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc);
- const equalTitles = dataDoc.title === exportState.title;
- const unchanged = equalContent && equalTitles;
- dataDoc.unchanged = unchanged;
- DocumentButtonBar.Instance.setPullState(unchanged);
- }
+ GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken().then(() => {
+ if (exportState && this._editorView) {
+ const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc);
+ const equalTitles = dataDoc.title === exportState.title;
+ const unchanged = equalContent && equalTitles;
+ dataDoc.unchanged = unchanged;
+ DocumentButtonBar.Instance.setPullState(unchanged);
+ }
+ });
}
clipboardTextSerializer = (slice: Slice): string => {