diff options
Diffstat (limited to 'src/client/util/Import & Export/DirectoryImportBox.tsx')
-rw-r--r-- | src/client/util/Import & Export/DirectoryImportBox.tsx | 334 |
1 files changed, 176 insertions, 158 deletions
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 37571ae01..7f0c8a3e8 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -1,27 +1,27 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { BatchedArray } from "array-batcher"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { extname } from "path"; -import Measure, { ContentRect } from "react-measure"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { BoolCast, Cast, NumCast } from "../../../fields/Types"; -import { AcceptableMedia, Upload } from "../../../server/SharedMediaTypes"; -import { Utils } from "../../../Utils"; -import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; -import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; -import { Networking } from "../../Network"; -import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; -import { DocumentManager } from "../DocumentManager"; -import "./DirectoryImportBox.scss"; -import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry"; -import React = require("react"); +import { BatchedArray } from 'array-batcher'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { extname } from 'path'; +import Measure, { ContentRect } from 'react-measure'; +import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; +import { BoolCast, Cast, NumCast } from '../../../fields/Types'; +import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes'; +import { Utils } from '../../../Utils'; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; +import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; +import { Networking } from '../../Network'; +import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; +import { DocumentManager } from '../DocumentManager'; +import './DirectoryImportBox.scss'; +import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry'; +import React = require('react'); -const unsupported = ["text/html", "text/plain"]; +const unsupported = ['text/html', 'text/plain']; @observer export class DirectoryImportBox extends React.Component<FieldViewProps> { @@ -29,7 +29,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { @observable private top = 0; @observable private left = 0; private dimensions = 50; - @observable private phase = ""; + @observable private phase = ''; private disposer: Opt<IReactionDisposer>; @observable private entries: ImportMetadataEntry[] = []; @@ -40,7 +40,9 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { @observable private uploading = false; @observable private removeHover = false; - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DirectoryImportBox, fieldKey); + } constructor(props: FieldViewProps) { super(props); @@ -71,7 +73,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => { runInAction(() => { this.uploading = true; - this.phase = "Initializing download..."; + this.phase = 'Initializing download...'; }); const docs: Doc[] = []; @@ -79,7 +81,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { const files = e.target.files; if (!files || files.length === 0) return; - const directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0]; + const directory = (files.item(0) as any).webkitRelativePath.split('/', 1)[0]; const validated: File[] = []; for (let i = 0; i < files.length; i++) { @@ -100,7 +102,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { const sizes: number[] = []; const modifiedDates: number[] = []; - runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); + runInAction(() => (this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`)); const batched = BatchedArray.from(validated, { batchSize: 15 }); const uploads = await batched.batchedMapAsync<Upload.FileResponse<Upload.ImageInformation>>(async (batch, collector) => { @@ -109,23 +111,28 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { modifiedDates.push(file.lastModified); }); collector.push(...(await Networking.UploadFilesToServer<Upload.ImageInformation>(batch))); - runInAction(() => this.completed += batch.length); + runInAction(() => (this.completed += batch.length)); }); - await Promise.all(uploads.map(async response => { - const { source: { type }, result } = response; - if (result instanceof Error) { - return; - } - const { accessPaths, exifData } = result; - const path = Utils.prepend(accessPaths.agnostic.client); - const document = type && await DocUtils.DocumentFromType(type, path, { _width: 300 }); - const { data, error } = exifData; - if (document) { - Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); - docs.push(document); - } - })); + await Promise.all( + uploads.map(async response => { + const { + source: { type }, + result, + } = response; + if (result instanceof Error) { + return; + } + const { accessPaths, exifData } = result; + const path = Utils.prepend(accessPaths.agnostic.client); + const document = type && (await DocUtils.DocumentFromType(type, path, { _width: 300 })); + const { data, error } = exifData; + if (document) { + Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); + docs.push(document); + } + }) + ); for (let i = 0; i < docs.length; i++) { const doc = docs[i]; @@ -146,7 +153,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { _height: 500, _chromeHidden: true, x: NumCast(doc.x), - y: NumCast(doc.y) + offset + y: NumCast(doc.y) + offset, }; const parent = this.props.ContainingCollectionView; if (parent) { @@ -154,14 +161,14 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { if (docs.length < 50) { importContainer = Docs.Create.MasonryDocument(docs, options); } else { - const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("size")]; + const headers = [new SchemaHeaderField('title'), new SchemaHeaderField('size')]; importContainer = Docs.Create.SchemaDocument(headers, docs, options); } - runInAction(() => this.phase = 'External: uploading files to Google Photos...'); + runInAction(() => (this.phase = 'External: uploading files to Google Photos...')); await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer }); - Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); + Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer); !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); - DocumentManager.Instance.jumpToDocument(importContainer, true, undefined, []); + DocumentManager.Instance.jumpToDocument(importContainer, { willPanZoom: true }, undefined, []); } runInAction(() => { @@ -169,14 +176,14 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { this.quota = 1; this.completed = 0; }); - } + }; componentDidMount() { - this.selector.current!.setAttribute("directory", ""); - this.selector.current!.setAttribute("webkitdirectory", ""); + this.selector.current!.setAttribute('directory', ''); + this.selector.current!.setAttribute('webkitdirectory', ''); this.disposer = reaction( () => this.completed, - completed => runInAction(() => this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`) + completed => runInAction(() => (this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`)) ); } @@ -193,7 +200,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { const offset = this.dimensions / 2; this.left = bounds.width / 2 - offset; this.top = bounds.height / 2 - offset; - } + }; @action addMetadataEntry = async () => { @@ -201,8 +208,8 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { entryDoc.checked = false; entryDoc.key = keyPlaceholder; entryDoc.value = valuePlaceholder; - Doc.AddDocToList(this.props.Document, "data", entryDoc); - } + Doc.AddDocToList(this.props.Document, 'data', entryDoc); + }; @action remove = async (entry: ImportMetadataEntry) => { @@ -217,7 +224,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { } } } - } + }; render() { const dimensions = 50; @@ -228,193 +235,204 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { const uploading = this.uploading; const showRemoveLabel = this.removeHover; const persistent = this.persistent; - let percent = `${completed / quota * 100}`; - percent = percent.split(".")[0]; - percent = percent.startsWith("100") ? "99" : percent; + let percent = `${(completed / quota) * 100}`; + percent = percent.split('.')[0]; + percent = percent.startsWith('100') ? '99' : percent; const marginOffset = (percent.length === 1 ? 5 : 0) - 1.6; - const message = <span className={"phase"}>{this.phase}</span>; - const centerPiece = this.phase.includes("Google Photos") ? - <img src={"/assets/google_photos.png"} style={{ - transition: "0.4s opacity ease", - width: 30, - height: 30, - opacity: uploading ? 1 : 0, - pointerEvents: "none", - position: "absolute", - left: 12, - top: this.top + 10, - fontSize: 18, - color: "white", - marginLeft: this.left + marginOffset - }} /> - : <div + const message = <span className={'phase'}>{this.phase}</span>; + const centerPiece = this.phase.includes('Google Photos') ? ( + <img + src={'/assets/google_photos.png'} style={{ - transition: "0.4s opacity ease", + transition: '0.4s opacity ease', + width: 30, + height: 30, opacity: uploading ? 1 : 0, - pointerEvents: "none", - position: "absolute", + pointerEvents: 'none', + position: 'absolute', + left: 12, + top: this.top + 10, + fontSize: 18, + color: 'white', + marginLeft: this.left + marginOffset, + }} + /> + ) : ( + <div + style={{ + transition: '0.4s opacity ease', + opacity: uploading ? 1 : 0, + pointerEvents: 'none', + position: 'absolute', left: 10, top: this.top + 12.3, fontSize: 18, - color: "white", - marginLeft: this.left + marginOffset - }}>{percent}%</div>; + color: 'white', + marginLeft: this.left + marginOffset, + }}> + {percent}% + </div> + ); return ( <Measure offset onResize={this.preserveCentering}> - {({ measureRef }) => - <div ref={measureRef} style={{ width: "100%", height: "100%", pointerEvents: "all" }} > + {({ measureRef }) => ( + <div ref={measureRef} style={{ width: '100%', height: '100%', pointerEvents: 'all' }}> {message} <input - id={"selector"} + id={'selector'} ref={this.selector} onChange={this.handleSelection} type="file" style={{ - position: "absolute", - display: "none" - }} /> + position: 'absolute', + display: 'none', + }} + /> <label - htmlFor={"selector"} + htmlFor={'selector'} style={{ opacity: isEditing ? 0 : 1, - pointerEvents: isEditing ? "none" : "all", - transition: "0.4s ease opacity" - }} - > - <div style={{ - width: dimensions, - height: dimensions, - borderRadius: "50%", - background: "black", - position: "absolute", - left: this.left, - top: this.top - }} /> - <div style={{ - position: "absolute", - left: this.left + 8, - top: this.top + 10, - opacity: uploading ? 0 : 1, - transition: "0.4s opacity ease" + pointerEvents: isEditing ? 'none' : 'all', + transition: '0.4s ease opacity', }}> - <FontAwesomeIcon icon={"cloud-upload-alt"} color="#FFFFFF" size={"2x"} /> + <div + style={{ + width: dimensions, + height: dimensions, + borderRadius: '50%', + background: 'black', + position: 'absolute', + left: this.left, + top: this.top, + }} + /> + <div + style={{ + position: 'absolute', + left: this.left + 8, + top: this.top + 10, + opacity: uploading ? 0 : 1, + transition: '0.4s opacity ease', + }}> + <FontAwesomeIcon icon={'cloud-upload-alt'} color="#FFFFFF" size={'2x'} /> </div> <img style={{ width: 80, height: 80, - transition: "0.4s opacity ease", + transition: '0.4s opacity ease', opacity: uploading ? 0.7 : 0, - position: "absolute", + position: 'absolute', top: this.top - 15, - left: this.left - 15 + left: this.left - 15, }} - src={"/assets/loading.gif"}></img> + src={'/assets/loading.gif'}></img> </label> <input - type={"checkbox"} - onChange={e => runInAction(() => this.persistent = e.target.checked)} + type={'checkbox'} + onChange={e => runInAction(() => (this.persistent = e.target.checked))} style={{ margin: 0, - position: "absolute", + position: 'absolute', left: 10, bottom: 10, opacity: isEditing || uploading ? 0 : 1, - transition: "0.4s opacity ease", - pointerEvents: isEditing || uploading ? "none" : "all" + transition: '0.4s opacity ease', + pointerEvents: isEditing || uploading ? 'none' : 'all', }} checked={this.persistent} - onPointerEnter={action(() => this.removeHover = true)} - onPointerLeave={action(() => this.removeHover = false)} + onPointerEnter={action(() => (this.removeHover = true))} + onPointerLeave={action(() => (this.removeHover = false))} /> <p style={{ - position: "absolute", + position: 'absolute', left: 27, bottom: 8.4, fontSize: 12, opacity: showRemoveLabel ? 1 : 0, - transition: "0.4s opacity ease" - }}>Template will be <span style={{ textDecoration: "underline", textDecorationColor: persistent ? "green" : "red", color: persistent ? "green" : "red" }}>{persistent ? "kept" : "removed"}</span> after upload</p> + transition: '0.4s opacity ease', + }}> + Template will be <span style={{ textDecoration: 'underline', textDecorationColor: persistent ? 'green' : 'red', color: persistent ? 'green' : 'red' }}>{persistent ? 'kept' : 'removed'}</span> after upload + </p> {centerPiece} <div style={{ - position: "absolute", + position: 'absolute', top: 10, right: 10, - borderRadius: "50%", + borderRadius: '50%', width: 25, height: 25, - background: "black", - pointerEvents: uploading ? "none" : "all", + background: 'black', + pointerEvents: uploading ? 'none' : 'all', opacity: uploading ? 0 : 1, - transition: "0.4s opacity ease" + transition: '0.4s opacity ease', }} - title={isEditing ? "Back to Upload" : "Add Metadata"} - onClick={action(() => this.editingMetadata = !this.editingMetadata)} + title={isEditing ? 'Back to Upload' : 'Add Metadata'} + onClick={action(() => (this.editingMetadata = !this.editingMetadata))} /> <FontAwesomeIcon style={{ - pointerEvents: "none", - position: "absolute", + pointerEvents: 'none', + position: 'absolute', right: isEditing ? 14 : 15, top: isEditing ? 15.4 : 16, opacity: uploading ? 0 : 1, - transition: "0.4s opacity ease" + transition: '0.4s opacity ease', }} - icon={isEditing ? "cloud-upload-alt" : "tag"} + icon={isEditing ? 'cloud-upload-alt' : 'tag'} color="#FFFFFF" - size={"1x"} + size={'1x'} /> <div style={{ - transition: "0.4s ease opacity", - width: "100%", - height: "100%", - pointerEvents: isEditing ? "all" : "none", + transition: '0.4s ease opacity', + width: '100%', + height: '100%', + pointerEvents: isEditing ? 'all' : 'none', opacity: isEditing ? 1 : 0, - overflowY: "scroll" - }} - > + overflowY: 'scroll', + }}> <div style={{ - borderRadius: "50%", + borderRadius: '50%', width: 25, height: 25, marginLeft: 10, - position: "absolute", + position: 'absolute', right: 41, - top: 10 + top: 10, }} - title={"Add Metadata Entry"} - onClick={this.addMetadataEntry} - > + title={'Add Metadata Entry'} + onClick={this.addMetadataEntry}> <FontAwesomeIcon style={{ - pointerEvents: "none", + pointerEvents: 'none', marginLeft: 6.4, - marginTop: 5.2 + marginTop: 5.2, }} - icon={"plus"} - size={"1x"} + icon={'plus'} + size={'1x'} /> </div> - <p style={{ paddingLeft: 10, paddingTop: 8, paddingBottom: 7 }} >Add metadata to your import...</p> - <hr style={{ margin: "6px 10px 12px 10px" }} /> - {entries.map(doc => + <p style={{ paddingLeft: 10, paddingTop: 8, paddingBottom: 7 }}>Add metadata to your import...</p> + <hr style={{ margin: '6px 10px 12px 10px' }} /> + {entries.map(doc => ( <ImportMetadataEntry Document={doc} key={doc[Id]} remove={this.remove} - ref={(el) => { if (el) this.entries.push(el); }} + ref={el => { + if (el) this.entries.push(el); + }} next={this.addMetadataEntry} /> - )} + ))} </div> </div> - } + )} </Measure> ); } - -}
\ No newline at end of file +} |