diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/DocumentDecorations.scss | 6 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 92 | ||||
-rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 3 | ||||
-rw-r--r-- | src/new_fields/RichTextField.ts | 7 |
4 files changed, 67 insertions, 41 deletions
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 0b7411fca..ef7159370 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -255,4 +255,8 @@ $linkGap : 3px; input { margin-right: 10px; } -}
\ No newline at end of file +} + +@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } } +@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } +@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }
\ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 05239073b..7645af054 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,5 +1,5 @@ import { library, IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons'; +import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt, faSyncAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; @@ -41,8 +41,10 @@ library.add(faArrowAltCircleUp); library.add(faStopCircle); library.add(faCheckCircle); library.add(faCloudUploadAlt); +library.add(faSyncAlt); const cloud: IconProp = "cloud-upload-alt"; +const fetch: IconProp = "sync-alt"; @observer export class DocumentDecorations extends React.Component<{}, { value: string }> { @@ -76,48 +78,47 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @observable public pushIcon: IconProp = "arrow-alt-circle-up"; @observable public pullIcon: IconProp = "arrow-alt-circle-down"; @observable public pullColor: string = "white"; + @observable public isAnimatingFetch = false; public pullColorAnimating = false; private pullAnimating = false; private pushAnimating = false; public startPullOutcome = action((success: boolean) => { - if (this.pullAnimating) { - return; + if (!this.pullAnimating) { + this.pullAnimating = true; + this.pullIcon = success ? "check-circle" : "stop-circle"; + setTimeout(() => runInAction(() => { + this.pullIcon = "arrow-alt-circle-down"; + this.pullAnimating = false; + }), 1000); } - this.pullAnimating = true; - this.pullIcon = success ? "check-circle" : "stop-circle"; - setTimeout(() => runInAction(() => { - this.pullIcon = "arrow-alt-circle-down"; - this.pullAnimating = false; - }), 1000); }); public startPushOutcome = action((success: boolean) => { - if (this.pushAnimating) { - return; + if (!this.pushAnimating) { + this.pushAnimating = true; + this.pushIcon = success ? "check-circle" : "stop-circle"; + setTimeout(() => runInAction(() => { + this.pushIcon = "arrow-alt-circle-up"; + this.pushAnimating = false; + }), 1000); } - this.pushAnimating = true; - this.pushIcon = success ? "check-circle" : "stop-circle"; - setTimeout(() => runInAction(() => { - this.pushIcon = "arrow-alt-circle-up"; - this.pushAnimating = false; - }), 1000); }); - public setPullState = (unchanged: boolean) => { - if (this.pullColorAnimating) { - return; + public setPullState = action((unchanged: boolean) => { + this.isAnimatingFetch = false; + if (!this.pullColorAnimating) { + this.pullColorAnimating = true; + this.pullColor = unchanged ? "lawngreen" : "red"; + setTimeout(this.clearPullColor, 1000); } - this.pullColorAnimating = true; - this.pullColor = unchanged ? "lawngreen" : "red"; - setTimeout(() => { - runInAction(() => { - this.pullColor = "white"; - this.pullColorAnimating = false; - }); - }, 2000); - } + }); + + private clearPullColor = action(() => { + this.pullColor = "white"; + this.pullColorAnimating = false; + }); constructor(props: Readonly<{}>) { super(props); @@ -679,7 +680,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; if (!canPush) return (null); let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined; - let icon: IconProp = published ? (this.pushIcon as any) : (cloud as any); + let icon: IconProp = published ? (this.pushIcon as any) : cloud; return ( <div className={"linkButtonWrapper"}> <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => { @@ -695,14 +696,33 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> considerGoogleDocsPull = () => { let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField; let dataDoc = Doc.GetProto(this.targetDoc); - if (!canPull || !dataDoc[GoogleRef] || dataDoc.unchanged) return (null); + if (!canPull || !dataDoc[GoogleRef]) return (null); + let icon = !dataDoc.unchanged ? (this.pullIcon as any) : fetch; + let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; return ( <div className={"linkButtonWrapper"}> - <div style={{ backgroundColor: this.pullColor, transition: "1s ease all" }} title="Pull From Google Docs" className="linkButton-linker" onClick={() => { - DocumentDecorations.hasPulledHack = false; - this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; - }}> - <FontAwesomeIcon className="documentdecorations-icon" icon={this.pullIcon} size="sm" /> + <div + title="Pull From Google Docs" + className="linkButton-linker" + style={{ + backgroundColor: this.pullColor, + transition: "0.2s ease all" + }} + onClick={() => { + this.clearPullColor(); + DocumentDecorations.hasPulledHack = false; + this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1; + dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); + }}> + <FontAwesomeIcon + style={{ + WebkitAnimation: animation, + MozAnimation: animation + }} + className="documentdecorations-icon" + icon={icon} + size="sm" + /> </div> </div> ); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8a355a425..e06be7079 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -354,7 +354,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe () => { if (!DocumentDecorations.hasPulledHack) { DocumentDecorations.hasPulledHack = true; - this.pullFromGoogleDoc(this.updateState); + let unchanged = this.dataDoc.unchanged; + this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); } } ); diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index ab58329f9..cae5623e6 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -38,9 +38,10 @@ export class RichTextField extends ObjectField { let blockText = (block: any) => block.text; let concatenateParagraph = (p: any) => (p.content ? p.content.map(blockText).join(joiner) : "") + delimiter; - // Concatentate paragraphs and string the result together. Trim the last newline, an artifact. - let textParagraphs = paragraphs.map(concatenateParagraph); - return textParagraphs.join(joiner).trimEnd(delimiter); + // Concatentate paragraphs and string the result together + let textParagraphs: string[] = paragraphs.map(concatenateParagraph); + let plainText = textParagraphs.join(joiner); + return plainText.substring(0, plainText.length - 1); } [FromPlainText](plainText: string) { |