aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts4
-rw-r--r--src/client/documents/Documents.ts57
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx376
-rw-r--r--src/client/util/Import & Export/ImportMetadataEntry.tsx149
-rw-r--r--src/client/util/SelectionManager.ts2
-rw-r--r--src/client/util/request-image-size.js6
-rw-r--r--src/client/views/EditableView.tsx9
-rw-r--r--src/client/views/Main.tsx2
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx55
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx6
-rw-r--r--src/client/views/nodes/ImageBox.tsx4
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx58
-rw-r--r--src/new_fields/Doc.ts1
14 files changed, 649 insertions, 82 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index cbcf751ee..652a9b701 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -12,7 +12,9 @@ export namespace DocServer {
const GUID: string = Utils.GenerateGuid();
export function makeReadOnly() {
- _CreateField = emptyFunction;
+ _CreateField = field => {
+ _cache[field[Id]] = field;
+ };
_UpdateField = emptyFunction;
_respondToUpdate = emptyFunction;
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2bddf053a..07812432c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -36,6 +36,7 @@ import { UndoManager } from "../util/UndoManager";
import { RouteStore } from "../../server/RouteStore";
import { LinkManager } from "../util/LinkManager";
import { DocumentManager } from "../util/DocumentManager";
+import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox";
import { Scripting } from "../util/Scripting";
var requestImageSize = require('../util/request-image-size');
var path = require('path');
@@ -52,7 +53,8 @@ export enum DocTypes {
KVP = "kvp",
VID = "video",
AUDIO = "audio",
- LINK = "link"
+ LINK = "link",
+ IMPORT = "import"
}
export interface DocumentOptions {
@@ -128,6 +130,7 @@ export namespace Docs {
let audioProto: Doc;
let pdfProto: Doc;
let iconProto: Doc;
+ let importProto: Doc;
// let linkProto: Doc;
const textProtoId = "textProto";
const histoProtoId = "histoProto";
@@ -139,6 +142,7 @@ export namespace Docs {
const videoProtoId = "videoProto";
const audioProtoId = "audioProto";
const iconProtoId = "iconProto";
+ const importProtoId = "importProto";
// const linkProtoId = "linkProto";
export function initProtos(): Promise<void> {
@@ -153,6 +157,7 @@ export namespace Docs {
audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype();
pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype();
iconProto = fields[iconProtoId] as Doc || CreateIconPrototype();
+ importProto = fields[importProtoId] as Doc || CreateImportPrototype();
});
}
@@ -175,6 +180,11 @@ export namespace Docs {
return imageProto;
}
+ function CreateImportPrototype(): Doc {
+ let importProto = setupPrototypeOptions(importProtoId, "IMPORT_PROTO", DirectoryImportBox.LayoutString(), { x: 0, y: 0, width: 600, height: 600, type: DocTypes.IMPORT });
+ return importProto;
+ }
+
function CreateHistogramPrototype(): Doc {
let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"),
{ x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString(), type: DocTypes.HIST });
@@ -262,6 +272,10 @@ export namespace Docs {
return CreateInstance(audioProto, new AudioField(new URL(url)), options);
}
+ export function DirectoryImportDocument(options: DocumentOptions = {}) {
+ return CreateInstance(importProto, new List<Doc>(), options);
+ }
+
export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) {
return CreateInstance(histoProto, new HistogramField(histoOp), options);
}
@@ -334,6 +348,47 @@ export namespace Docs {
return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
}
+ export async function getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
+ let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
+ if (type.indexOf("image") !== -1) {
+ ctor = Docs.ImageDocument;
+ }
+ if (type.indexOf("video") !== -1) {
+ ctor = Docs.VideoDocument;
+ }
+ if (type.indexOf("audio") !== -1) {
+ ctor = Docs.AudioDocument;
+ }
+ if (type.indexOf("pdf") !== -1) {
+ ctor = Docs.PdfDocument;
+ options.nativeWidth = 1200;
+ }
+ if (type.indexOf("excel") !== -1) {
+ ctor = Docs.DBDocument;
+ options.dropAction = "copy";
+ }
+ if (type.indexOf("html") !== -1) {
+ if (path.includes(window.location.hostname)) {
+ let s = path.split('/');
+ let id = s[s.length - 1];
+ return DocServer.GetRefField(id).then(field => {
+ if (field instanceof Doc) {
+ let alias = Doc.MakeAlias(field);
+ alias.x = options.x || 0;
+ alias.y = options.y || 0;
+ alias.width = options.width || 300;
+ alias.height = options.height || options.width || 300;
+ return alias;
+ }
+ return undefined;
+ });
+ }
+ ctor = Docs.WebDocument;
+ options = { height: options.width, ...options, title: path, nativeWidth: undefined };
+ }
+ return ctor ? ctor(path, options) : undefined;
+ }
+
export function CaptionDocument(doc: Doc) {
const captionDoc = Doc.MakeAlias(doc);
captionDoc.overlayLayout = FixedCaption();
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
new file mode 100644
index 000000000..ce95ba90e
--- /dev/null
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -0,0 +1,376 @@
+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 { faArrowUp, faTag, faPlus } 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<FieldViewProps> {
+ private selector = React.createRef<HTMLInputElement>();
+ @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(faArrowUp, 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<Doc>());
+ }
+
+ @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<HTMLInputElement>) => {
+ runInAction(() => this.uploading = true);
+
+ let promises: Promise<void>[] = [];
+ 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(DocServer.prepend(RouteStore.upload), {
+ method: 'POST',
+ body: formData
+ }).then(async (res: Response) => {
+ (await res.json()).map(action((file: any) => {
+ let docPromise = Docs.getDocumentFromType(type, DocServer.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.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 (
+ <Measure offset onResize={this.preserveCentering}>
+ {({ measureRef }) =>
+ <div ref={measureRef} style={{ width: "100%", height: "100%", pointerEvents: "all" }} >
+ <input
+ id={"selector"}
+ ref={this.selector}
+ onChange={this.handleSelection}
+ type="file"
+ style={{
+ position: "absolute",
+ display: "none"
+ }} />
+ <label
+ 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 + 12.6,
+ top: this.top + 11,
+ opacity: uploading ? 0 : 1,
+ transition: "0.4s opacity ease"
+ }}>
+ <FontAwesomeIcon icon={faArrowUp} color="#FFFFFF" size={"2x"} />
+ </div>
+ <img
+ style={{
+ width: 80,
+ height: 80,
+ transition: "0.4s opacity ease",
+ opacity: uploading ? 0.7 : 0,
+ position: "absolute",
+ top: this.top - 15,
+ left: this.left - 15
+ }}
+ src={"/assets/loading.gif"}></img>
+ </label>
+ <input
+ type={"checkbox"}
+ onChange={e => runInAction(() => this.persistent = e.target.checked)}
+ style={{
+ margin: 0,
+ position: "absolute",
+ left: 10,
+ bottom: 10,
+ opacity: isEditing || uploading ? 0 : 1,
+ transition: "0.4s opacity ease",
+ pointerEvents: isEditing || uploading ? "none" : "all"
+ }}
+ checked={this.persistent}
+ onPointerEnter={action(() => this.removeHover = true)}
+ onPointerLeave={action(() => this.removeHover = false)}
+ />
+ <p
+ style={{
+ 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>
+ <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>
+ <div
+ style={{
+ position: "absolute",
+ top: 10,
+ right: 10,
+ borderRadius: "50%",
+ width: 25,
+ height: 25,
+ background: "black",
+ pointerEvents: uploading ? "none" : "all",
+ opacity: uploading ? 0 : 1,
+ transition: "0.4s opacity ease"
+ }}
+ title={isEditing ? "Back to Upload" : "Add Metadata"}
+ onClick={action(() => this.editingMetadata = !this.editingMetadata)}
+ />
+ <FontAwesomeIcon
+ style={{
+ pointerEvents: "none",
+ position: "absolute",
+ right: isEditing ? 16.3 : 14.5,
+ top: isEditing ? 15.4 : 16,
+ opacity: uploading ? 0 : 1,
+ transition: "0.4s opacity ease"
+ }}
+ icon={isEditing ? faArrowUp : faTag}
+ color="#FFFFFF"
+ size={"1x"}
+ />
+ <div
+ style={{
+ transition: "0.4s ease opacity",
+ width: "100%",
+ height: "100%",
+ pointerEvents: isEditing ? "all" : "none",
+ opacity: isEditing ? 1 : 0,
+ overflowY: "scroll"
+ }}
+ >
+ <div
+ style={{
+ borderRadius: "50%",
+ width: 25,
+ height: 25,
+ marginLeft: 10,
+ position: "absolute",
+ right: 41,
+ top: 10
+ }}
+ title={"Add Metadata Entry"}
+ onClick={this.addMetadataEntry}
+ >
+ <FontAwesomeIcon
+ style={{
+ pointerEvents: "none",
+ marginLeft: 6.4,
+ marginTop: 5.2
+ }}
+ icon={faPlus}
+ 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 =>
+ <ImportMetadataEntry
+ Document={doc}
+ key={doc[Id]}
+ remove={this.remove}
+ ref={(el) => { if (el) this.entries.push(el); }}
+ next={this.addMetadataEntry}
+ />
+ )}
+ </div>
+ </div>
+ }
+ </Measure>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
new file mode 100644
index 000000000..f5198c39b
--- /dev/null
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx
@@ -0,0 +1,149 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { EditableView } from "../../views/EditableView";
+import { observable, action, computed } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faPlus } from "@fortawesome/free-solid-svg-icons";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { Opt, Doc } from "../../../new_fields/Doc";
+import { StrCast, BoolCast } from "../../../new_fields/Types";
+
+interface KeyValueProps {
+ Document: Doc;
+ remove: (self: ImportMetadataEntry) => void;
+ next: () => void;
+}
+
+export const keyPlaceholder = "Key";
+export const valuePlaceholder = "Value";
+
+@observer
+export default class ImportMetadataEntry extends React.Component<KeyValueProps> {
+
+ private keyRef = React.createRef<EditableView>();
+ private valueRef = React.createRef<EditableView>();
+ private checkRef = React.createRef<HTMLInputElement>();
+
+ constructor(props: KeyValueProps) {
+ super(props);
+ library.add(faPlus);
+ }
+
+ @computed
+ public get valid() {
+ return (this.key.length > 0 && this.key !== keyPlaceholder) && (this.value.length > 0 && this.value !== valuePlaceholder);
+ }
+
+ @computed
+ private get backing() {
+ return this.props.Document;
+ }
+
+ @computed
+ public get onDataDoc() {
+ return BoolCast(this.backing.checked);
+ }
+
+ public set onDataDoc(value: boolean) {
+ this.backing.checked = value;
+ }
+
+ @computed
+ public get key() {
+ return StrCast(this.backing.key);
+ }
+
+ public set key(value: string) {
+ this.backing.key = value;
+ }
+
+ @computed
+ public get value() {
+ return StrCast(this.backing.value);
+ }
+
+ public set value(value: string) {
+ this.backing.value = value;
+ }
+
+ @action
+ updateKey = (newKey: string) => {
+ this.key = newKey;
+ this.keyRef.current && this.keyRef.current.setIsFocused(false);
+ this.valueRef.current && this.valueRef.current.setIsFocused(true);
+ this.key.length === 0 && (this.key = keyPlaceholder);
+ return true;
+ }
+
+ @action
+ updateValue = (newValue: string, shiftDown: boolean) => {
+ this.value = newValue;
+ this.valueRef.current && this.valueRef.current.setIsFocused(false);
+ this.value.length > 0 && shiftDown && this.props.next();
+ this.value.length === 0 && (this.value = valuePlaceholder);
+ return true;
+ }
+
+ render() {
+ let keyValueStyle: React.CSSProperties = {
+ paddingLeft: 10,
+ width: "50%",
+ opacity: this.valid ? 1 : 0.5,
+ };
+ return (
+ <div
+ style={{
+ display: "flex",
+ flexDirection: "row",
+ paddingBottom: 5,
+ paddingRight: 5,
+ justifyContent: "center",
+ alignItems: "center",
+ alignContent: "center"
+ }}
+ >
+ <input
+ onChange={e => this.onDataDoc = e.target.checked}
+ ref={this.checkRef}
+ style={{ margin: "0 10px 0 15px" }}
+ type="checkbox"
+ title={"Add to Data Document?"}
+ checked={this.onDataDoc}
+ />
+ <div className={"key_container"} style={keyValueStyle}>
+ <EditableView
+ ref={this.keyRef}
+ contents={this.key}
+ SetValue={this.updateKey}
+ GetValue={() => ""}
+ oneLine={true}
+ />
+ </div>
+ <div
+ className={"value_container"}
+ style={keyValueStyle}>
+ <EditableView
+ ref={this.valueRef}
+ contents={this.value}
+ SetValue={this.updateValue}
+ GetValue={() => ""}
+ oneLine={true}
+ />
+ </div>
+ <div onClick={() => this.props.remove(this)} title={"Delete Entry"}>
+ <FontAwesomeIcon
+ icon={faPlus}
+ color={"red"}
+ size={"1x"}
+ style={{
+ marginLeft: 15,
+ marginRight: 15,
+ transform: "rotate(45deg)"
+ }}
+ />
+ </div>
+ </div>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 3c396362e..9efef888d 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -53,7 +53,7 @@ export namespace SelectionManager {
stored.length > 0 && (targetColor = stored);
}
InkingControl.Instance.updateSelectedColor(targetColor);
- });
+ }, { fireImmediately: true });
export function DeselectDoc(docView: DocumentView): void {
manager.DeselectDoc(docView);
diff --git a/src/client/util/request-image-size.js b/src/client/util/request-image-size.js
index 0f9328872..27605d167 100644
--- a/src/client/util/request-image-size.js
+++ b/src/client/util/request-image-size.js
@@ -21,7 +21,9 @@ module.exports = function requestImageSize(options) {
if (options && typeof options === 'object') {
opts = Object.assign(options, opts);
} else if (options && typeof options === 'string') {
- opts = Object.assign({ uri: options }, opts);
+ opts = Object.assign({
+ uri: options
+ }, opts);
} else {
return Promise.reject(new Error('You should provide an URI string or a "request" options object.'));
}
@@ -70,4 +72,4 @@ module.exports = function requestImageSize(options) {
req.on('error', err => reject(err));
});
-};
+}; \ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index f7aa6cc94..989fb1be9 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -14,7 +14,7 @@ export interface EditableProps {
* @param value - The string entered by the user to set the value to
* @returns `true` if setting the value was successful, `false` otherwise
* */
- SetValue(value: string): boolean;
+ SetValue(value: string, shiftDown?: boolean): boolean;
OnFillDown?(value: string): void;
@@ -53,7 +53,7 @@ export class EditableView extends React.Component<EditableProps> {
this.props.OnTab && this.props.OnTab();
} else if (e.key === "Enter") {
if (!e.ctrlKey) {
- if (this.props.SetValue(e.currentTarget.value)) {
+ if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) {
this._editing = false;
}
} else if (this.props.OnFillDown) {
@@ -77,6 +77,11 @@ export class EditableView extends React.Component<EditableProps> {
e.stopPropagation();
}
+ @action
+ setIsFocused = (value: boolean) => {
+ this._editing = value;
+ }
+
render() {
if (this._editing) {
return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 3d9750a85..281d9159b 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -8,4 +8,4 @@ import * as React from 'react';
await Docs.initProtos();
await CurrentUserUtils.loadCurrentUser();
ReactDOM.render(<MainView />, document.getElementById('root'));
-})();
+})(); \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index da126fb74..b8fc3f47b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -358,11 +358,13 @@ export class MainView extends React.Component {
let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
let addTreeNode = action(() => CurrentUserUtils.UserDocument);
let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
+ let addImportCollectionNode = action(() => Docs.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 }));
let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
[React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
[React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
+ [React.createRef<HTMLDivElement>(), "arrow-up", "Import Directory", addImportCollectionNode],
];
return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 5 }} >
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5287d3c13..a8810f336 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -19,6 +19,7 @@ import { CollectionPDFView } from "./CollectionPDFView";
import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
import React = require("react");
+import { MainView } from "../MainView";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -67,10 +68,16 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
let email = CurrentUserUtils.email;
let pos = { x: position[0], y: position[1] };
if (id && email) {
- const proto = await doc.proto;
+ const proto = Doc.GetProto(doc);
if (!proto) {
return;
}
+ if (proto[Id] === "collectionProto") {
+ alert("COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...");
+ console.log(doc);
+ console.log(proto);
+ throw new Error(`AHA! You were trying to set a cursor on a collection's proto, which is the original collection proto! Look at the two previously printed lines for document values!`);
+ }
let cursors = Cast(proto.cursors, listSpec(CursorField));
if (!cursors) {
proto.cursors = cursors = new List<CursorField>();
@@ -108,47 +115,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
return false;
}
- protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
- let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
- if (type.indexOf("image") !== -1) {
- ctor = Docs.ImageDocument;
- }
- if (type.indexOf("video") !== -1) {
- ctor = Docs.VideoDocument;
- }
- if (type.indexOf("audio") !== -1) {
- ctor = Docs.AudioDocument;
- }
- if (type.indexOf("pdf") !== -1) {
- ctor = Docs.PdfDocument;
- options.nativeWidth = 1200;
- }
- if (type.indexOf("excel") !== -1) {
- ctor = Docs.DBDocument;
- options.dropAction = "copy";
- }
- if (type.indexOf("html") !== -1) {
- if (path.includes(window.location.hostname)) {
- let s = path.split('/');
- let id = s[s.length - 1];
- DocServer.GetRefField(id).then(field => {
- if (field instanceof Doc) {
- let alias = Doc.MakeAlias(field);
- alias.x = options.x || 0;
- alias.y = options.y || 0;
- alias.width = options.width || 300;
- alias.height = options.height || options.width || 300;
- this.props.addDocument(alias, false);
- }
- });
- return undefined;
- }
- ctor = Docs.WebDocument;
- options = { height: options.width, ...options, title: path, nativeWidth: undefined };
- }
- return ctor ? ctor(path, options) : undefined;
- }
-
@undoBatch
@action
protected onDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
@@ -229,7 +195,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
.then(result => {
let type = result["content-type"];
if (type) {
- this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
+ Docs.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
.then(doc => doc && this.props.addDocument(doc, false));
}
});
@@ -251,8 +217,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}).then(async (res: Response) => {
(await res.json()).map(action((file: any) => {
let path = window.location.origin + file;
- let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName });
-
+ let docPromise = Docs.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName });
docPromise.then(doc => doc && this.props.addDocument(doc));
}));
});
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 745520bc4..b4c0e844f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -24,6 +24,7 @@ import { Without, OmitKeys } from "../../../Utils";
import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
import { List } from "../../../new_fields/List";
import { Doc } from "../../../new_fields/Doc";
+import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
import { CollectionViewType } from "../collections/CollectionBaseView";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -123,8 +124,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
if (this.props.renderDepth > 7) return (null);
if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
return <ObserverJsxParser
+<<<<<<< HEAD
+ components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+ bindings={this.CreateBindings(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document)}
+=======
components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
bindings={this.CreateBindings()}
+>>>>>>> b49fdb1c42b9758e006521e0f404634ba396a2ac
jsx={this.finalLayout}
showWarnings={true}
onError={(test: any) => { console.log(test); }}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b9cecdd24..f0363d0b8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -216,7 +216,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
}), 0);
}
})
- .catch((err: any) => console.log(err));
+ .catch((err: any) => {
+ console.log(err);
+ });
}
render() {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 9407d742c..e27ab1589 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -8,7 +8,7 @@ import "./KeyValueBox.scss";
import { KeyValuePair } from "./KeyValuePair";
import React = require("react");
import { NumCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
-import { Doc, Field, FieldResult } from "../../../new_fields/Doc";
+import { Doc, Field, FieldResult, DocListCastAsync } from "../../../new_fields/Doc";
import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
import { SetupDrag } from "../../util/DragManager";
import { Docs } from "../../documents/Documents";
@@ -18,6 +18,8 @@ import { List } from "../../../new_fields/List";
import { TextField } from "../../util/ProsemirrorCopy/prompt";
import { RichTextField } from "../../../new_fields/RichTextField";
import { ImageField } from "../../../new_fields/URLField";
+import { SelectionManager } from "../../util/SelectionManager";
+import { listSpec } from "../../../new_fields/Schema";
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
@@ -167,44 +169,44 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
return parent;
}
- createTemplateField = async (parent: Doc, row: KeyValuePair) => {
- let collectionKeyProp = `fieldKey={"data"}`;
+ createTemplateField = async (parentStackingDoc: Doc, row: KeyValuePair) => {
let metaKey = row.props.keyName;
- let metaKeyProp = `fieldKey={"${metaKey}"}`;
-
let sourceDoc = await Cast(this.props.Document.data, Doc);
if (!sourceDoc) {
return;
}
- let target = this.inferType(sourceDoc[metaKey], metaKey);
-
- let template = Doc.MakeAlias(target);
- template.proto = parent;
- template.title = metaKey;
- template.nativeWidth = 0;
- template.nativeHeight = 0;
- template.embed = true;
- template.isTemplate = true;
- template.templates = new List<string>([Templates.TitleBar(metaKey)]);
- if (target.backgroundLayout) {
- let metaAnoKeyProp = `fieldKey={"${metaKey}"} fieldExt={"annotations"}`;
- let collectionAnoKeyProp = `fieldKey={"annotations"}`;
- template.layout = StrCast(target.layout).replace(collectionAnoKeyProp, metaAnoKeyProp);
- template.backgroundLayout = StrCast(target.backgroundLayout).replace(collectionKeyProp, metaKeyProp);
- } else {
- template.layout = StrCast(target.layout).replace(collectionKeyProp, metaKeyProp);
+ let fieldTemplate = this.inferType(sourceDoc[metaKey], metaKey);
+
+ // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
+ let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
+ let layout = StrCast(fieldTemplate.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
+ if (backgroundLayout) {
+ layout = StrCast(fieldTemplate.layout).replace(/fieldKey={"annotations"}/, `fieldKey={"${metaKey}"} fieldExt={"annotations"}`);
+ backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
}
- Doc.AddDocToList(parent, "data", template);
- row.uncheck();
+ let nw = NumCast(fieldTemplate.nativeWidth);
+ let nh = NumCast(fieldTemplate.nativeHeight);
+
+ fieldTemplate.title = metaKey;
+ fieldTemplate.layout = layout;
+ fieldTemplate.backgroundLayout = backgroundLayout;
+ fieldTemplate.nativeWidth = nw;
+ fieldTemplate.nativeHeight = nh;
+ fieldTemplate.embed = true;
+ fieldTemplate.isTemplate = true;
+ fieldTemplate.templates = new List<string>([Templates.TitleBar(metaKey)]);
+ fieldTemplate.proto = Doc.GetProto(parentStackingDoc);
+
+ Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
}
- inferType = (field: FieldResult, metaKey: string) => {
+ inferType = (data: FieldResult, metaKey: string) => {
let options = { width: 300, height: 300, title: metaKey };
- if (field instanceof RichTextField || typeof field === "string" || typeof field === "number") {
+ if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") {
return Docs.TextDocument(options);
- } else if (field instanceof List) {
+ } else if (data instanceof List) {
return Docs.StackingDocument([], options);
- } else if (field instanceof ImageField) {
+ } else if (data instanceof ImageField) {
return Docs.ImageDocument("https://www.freepik.com/free-icon/picture-frame-with-mountain-image_748687.htm", options);
}
return new Doc;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index aa13b1d7a..cc6af51b1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -251,6 +251,7 @@ export namespace Doc {
if (allowDuplicates !== true) {
let pind = list.reduce((l, d, i) => d instanceof Doc && Doc.AreProtosEqual(d, doc) ? i : l, -1);
if (pind !== -1) {
+ console.log("SPLICING DUPLICATE");
list.splice(pind, 1);
}
}