import "fs"; import React = require("react"); import { Doc, Opt, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { DocServer } from "../../DocServer"; import { RouteStore } from "../../../server/RouteStore"; import { action, observable, autorun, runInAction, computed } from "mobx"; import { FieldViewProps, FieldView } from "../../views/nodes/FieldView"; import Measure, { ContentRect } from "react-measure"; import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTag, faPlus, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons'; import { Docs, DocumentOptions } from "../../documents/Documents"; import { observer } from "mobx-react"; import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry"; import { Utils } from "../../../Utils"; import { DocumentManager } from "../DocumentManager"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { Cast, BoolCast, NumCast } from "../../../new_fields/Types"; import { listSpec } from "../../../new_fields/Schema"; const unsupported = ["text/html", "text/plain"]; @observer export default class DirectoryImportBox extends React.Component { private selector = React.createRef(); @observable private top = 0; @observable private left = 0; private dimensions = 50; @observable private entries: ImportMetadataEntry[] = []; @observable private quota = 1; @observable private remaining = 1; @observable private uploading = false; @observable private removeHover = false; public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); } constructor(props: FieldViewProps) { super(props); library.add(faTag, faPlus); let doc = this.props.Document; this.editingMetadata = this.editingMetadata || false; this.persistent = this.persistent || false; !Cast(doc.data, listSpec(Doc)) && (doc.data = new List()); } @computed private get editingMetadata() { return BoolCast(this.props.Document.editingMetadata); } private set editingMetadata(value: boolean) { this.props.Document.editingMetadata = value; } @computed private get persistent() { return BoolCast(this.props.Document.persistent); } private set persistent(value: boolean) { this.props.Document.persistent = value; } handleSelection = async (e: React.ChangeEvent) => { runInAction(() => this.uploading = true); let promises: Promise[] = []; let docs: Doc[] = []; let files = e.target.files; if (!files || files.length === 0) return; let directory = (files.item(0) as any).webkitRelativePath.split("/", 1); let validated: File[] = []; for (let i = 0; i < files.length; i++) { let file = files.item(i); file && !unsupported.includes(file.type) && validated.push(file); } runInAction(() => this.quota = validated.length); let sizes = []; let modifiedDates = []; for (let uploaded_file of validated) { let formData = new FormData(); formData.append('file', uploaded_file); let dropFileName = uploaded_file ? uploaded_file.name : "-empty-"; let type = uploaded_file.type; sizes.push(uploaded_file.size); modifiedDates.push(uploaded_file.lastModified); runInAction(() => this.remaining++); let prom = fetch(Utils.prepend(RouteStore.upload), { method: 'POST', body: formData }).then(async (res: Response) => { (await res.json()).map(action((file: any) => { let docPromise = Docs.Get.DocumentFromType(type, Utils.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName }); docPromise.then(doc => { doc && docs.push(doc) && runInAction(() => this.remaining--); }); })); }); promises.push(prom); } await Promise.all(promises); for (let i = 0; i < docs.length; i++) { let doc = docs[i]; doc.size = sizes[i]; doc.modified = modifiedDates[i]; this.entries.forEach(entry => { let target = entry.onDataDoc ? Doc.GetProto(doc) : doc; target[entry.key] = entry.value; }); } let doc = this.props.Document; let height: number = NumCast(doc.height) || 0; let offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0; let options: DocumentOptions = { title: `Import of ${directory}`, width: 1105, height: 500, x: NumCast(doc.x), y: NumCast(doc.y) + offset }; let parent = this.props.ContainingCollectionView; if (parent) { let importContainer = Docs.Create.StackingDocument(docs, options); importContainer.singleColumn = false; Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); DocumentManager.Instance.jumpToDocument(importContainer, true); } runInAction(() => { this.uploading = false; this.quota = 1; this.remaining = 1; }); } componentDidMount() { this.selector.current!.setAttribute("directory", ""); this.selector.current!.setAttribute("webkitdirectory", ""); } @action preserveCentering = (rect: ContentRect) => { let bounds = rect.offset!; if (bounds.width === 0 || bounds.height === 0) { return; } let offset = this.dimensions / 2; this.left = bounds.width / 2 - offset; this.top = bounds.height / 2 - offset; } @action addMetadataEntry = async () => { let entryDoc = new Doc(); entryDoc.checked = false; entryDoc.key = keyPlaceholder; entryDoc.value = valuePlaceholder; Doc.AddDocToList(this.props.Document, "data", entryDoc); } @action remove = async (entry: ImportMetadataEntry) => { let metadata = await DocListCastAsync(this.props.Document.data); if (metadata) { let index = this.entries.indexOf(entry); if (index !== -1) { runInAction(() => this.entries.splice(index, 1)); index = metadata.indexOf(entry.props.Document); if (index !== -1) { metadata.splice(index, 1); } } } } render() { let dimensions = 50; let entries = DocListCast(this.props.Document.data); let isEditing = this.editingMetadata; let remaining = this.remaining; let quota = this.quota; let uploading = this.uploading; let showRemoveLabel = this.removeHover; let persistent = this.persistent; let percent = `${100 - (remaining / quota * 100)}`; percent = percent.split(".")[0]; percent = percent.startsWith("100") ? "99" : percent; let marginOffset = (percent.length === 1 ? 5 : 0) - 1.6; return ( {({ measureRef }) =>