aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/DragManager.ts1
-rw-r--r--src/client/util/LinkManager.ts38
-rw-r--r--src/client/util/SearchUtil.ts33
-rw-r--r--src/client/util/TooltipTextMenu.scss2
-rw-r--r--src/client/util/TooltipTextMenu.tsx32
-rw-r--r--src/client/views/Main.scss18
-rw-r--r--src/client/views/MainView.tsx35
-rw-r--r--src/client/views/Templates.tsx3
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx2
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx4
-rw-r--r--src/client/views/collections/CollectionStackingView.scss14
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx64
-rw-r--r--src/client/views/collections/CollectionSubView.tsx1
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx22
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx16
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.tsx3
-rw-r--r--src/client/views/nodes/LinkEditor.tsx5
-rw-r--r--src/client/views/nodes/LinkMenuItem.tsx12
-rw-r--r--src/client/views/pdf/PDFMenu.tsx19
-rw-r--r--src/client/views/pdf/Page.tsx4
-rw-r--r--src/client/views/search/SearchItem.tsx18
-rw-r--r--src/new_fields/Doc.ts21
-rw-r--r--src/new_fields/Types.ts3
-rw-r--r--src/new_fields/util.ts2
-rw-r--r--src/server/GarbageColletor.ts51
-rw-r--r--src/server/authentication/models/current_user_utils.ts39
-rw-r--r--src/server/database.ts14
-rw-r--r--src/server/index.ts7
30 files changed, 346 insertions, 143 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index f9492f452..6c2340d04 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -321,6 +321,7 @@ export namespace DragManager {
dragElement.style.top = "0";
dragElement.style.bottom = "";
dragElement.style.left = "0";
+ dragElement.style.transition = "none";
dragElement.style.color = "black";
dragElement.style.transformOrigin = "0 0";
dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index f2f3e51dd..944bc532f 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -68,8 +68,8 @@ export class LinkManager {
// finds all links that contain the given anchor
public getAllRelatedLinks(anchor: Doc): Doc[] {//List<Doc> {
let related = LinkManager.Instance.getAllLinks().filter(link => {
- let protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc));
- let protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, new Doc));
+ let protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, null));
+ let protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, null));
return protomatch1 || protomatch2;
});
return related;
@@ -100,9 +100,11 @@ export class LinkManager {
if (index > -1) groupTypes.splice(index, 1);
LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
LinkManager.Instance.LinkManagerDoc[groupType] = undefined;
- LinkManager.Instance.getAllLinks().forEach(linkDoc => {
- LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc), groupType);
- LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc), groupType);
+ LinkManager.Instance.getAllLinks().forEach(async linkDoc => {
+ const anchor1 = await Cast(linkDoc.anchor1, Doc);
+ const anchor2 = await Cast(linkDoc.anchor2, Doc);
+ anchor1 && LinkManager.Instance.removeGroupFromAnchor(linkDoc, anchor1, groupType);
+ anchor2 && LinkManager.Instance.removeGroupFromAnchor(linkDoc, anchor2, groupType);
});
}
return true;
@@ -122,8 +124,8 @@ export class LinkManager {
}
// gets the groups associates with an anchor in a link
- public getAnchorGroups(linkDoc: Doc, anchor: Doc): Array<Doc> {
- if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
+ public getAnchorGroups(linkDoc: Doc, anchor?: Doc): Array<Doc> {
+ if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
return DocListCast(linkDoc.anchor1Groups);
} else {
return DocListCast(linkDoc.anchor2Groups);
@@ -132,7 +134,7 @@ export class LinkManager {
// sets the groups of the given anchor in the given link
public setAnchorGroups(linkDoc: Doc, anchor: Doc, groups: Doc[]) {
- if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
+ if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
linkDoc.anchor1Groups = new List<Doc>(groups);
} else {
linkDoc.anchor2Groups = new List<Doc>(groups);
@@ -209,10 +211,10 @@ export class LinkManager {
let md: Doc[] = [];
let allLinks = LinkManager.Instance.getAllLinks();
allLinks.forEach(linkDoc => {
- let anchor1Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc));
- let anchor2Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc));
- anchor1Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) md.push(Cast(groupDoc.metadata, Doc, new Doc)); });
- anchor2Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) md.push(Cast(groupDoc.metadata, Doc, new Doc)); });
+ let anchor1Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor1, Doc, null));
+ let anchor2Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor2, Doc, null));
+ anchor1Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) { const meta = Cast(groupDoc.metadata, Doc, null); meta && md.push(meta); } });
+ anchor2Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) { const meta = Cast(groupDoc.metadata, Doc, null); meta && md.push(meta); } });
});
return md;
}
@@ -221,18 +223,20 @@ export class LinkManager {
public doesLinkExist(anchor1: Doc, anchor2: Doc): boolean {
let allLinks = LinkManager.Instance.getAllLinks();
let index = allLinks.findIndex(linkDoc => {
- return (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, new Doc), anchor1) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, new Doc), anchor2)) ||
- (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, new Doc), anchor2) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, new Doc), anchor1));
+ return (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor1) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor2)) ||
+ (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor2) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor1));
});
return index !== -1;
}
// finds the opposite anchor of a given anchor in a link
+ //TODO This should probably return undefined if there isn't an opposite anchor
+ //TODO This should also await the return value of the anchor so we don't filter out promises
public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc {
- if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
- return Cast(linkDoc.anchor2, Doc, new Doc);
+ if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
+ return Cast(linkDoc.anchor2, Doc, null)!;
} else {
- return Cast(linkDoc.anchor1, Doc, new Doc);
+ return Cast(linkDoc.anchor1, Doc, null)!;
}
}
} \ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index ac2eb72e7..338628960 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -29,11 +29,16 @@ export namespace SearchUtil {
return { docs, numFound };
}
- export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]> {
- const proto = await Doc.GetT(doc, "proto", Doc, true);
- const protoId = (proto || doc)[Id];
- const result = await Search(`proto_i:"${protoId}"`, true);
- return result.docs;
+ export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]>;
+ export async function GetAliasesOfDocument(doc: Doc, returnDocs: false): Promise<string[]>;
+ export async function GetAliasesOfDocument(doc: Doc, returnDocs = true): Promise<Doc[] | string[]> {
+ const proto = Doc.GetProto(doc);
+ const protoId = proto[Id];
+ if (returnDocs) {
+ return (await Search(`proto_i:"${protoId}"`, returnDocs)).docs;
+ } else {
+ return (await Search(`proto_i:"${protoId}"`, returnDocs)).ids;
+ }
// return Search(`{!join from=id to=proto_i}id:${protoId}`, true);
}
@@ -41,4 +46,22 @@ export namespace SearchUtil {
const results = await Search(`proto_i:"${doc[Id]}"`, true);
return results.docs;
}
+
+ export async function GetContextsOfDocument(doc: Doc): Promise<{ contexts: Doc[], aliasContexts: Doc[] }> {
+ const docContexts = (await Search(`data_l:"${doc[Id]}"`, true)).docs;
+ const aliases = await GetAliasesOfDocument(doc, false);
+ const aliasContexts = (await Promise.all(aliases.map(doc => Search(`data_l:"${doc}"`, true))));
+ const contexts = { contexts: docContexts, aliasContexts: [] as Doc[] };
+ aliasContexts.forEach(result => contexts.aliasContexts.push(...result.docs));
+ return contexts;
+ }
+
+ export async function GetContextIdsOfDocument(doc: Doc): Promise<{ contexts: string[], aliasContexts: string[] }> {
+ const docContexts = (await Search(`data_l:"${doc[Id]}"`, false)).ids;
+ const aliases = await GetAliasesOfDocument(doc, false);
+ const aliasContexts = (await Promise.all(aliases.map(doc => Search(`data_l:"${doc}"`, false))));
+ const contexts = { contexts: docContexts, aliasContexts: [] as string[] };
+ aliasContexts.forEach(result => contexts.aliasContexts.push(...result.ids));
+ return contexts;
+ }
} \ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index b10573b3e..40ac3abb9 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -248,7 +248,7 @@
transform: translateY(-85px);
pointer-events: all;
height: 30px;
- width:500px;
+ width:550px;
.ProseMirror-example-setup-style hr {
padding: 2px 10px;
border: none;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index e3e26d1f4..cb7ed976a 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -32,7 +32,6 @@ export class TooltipTextMenu {
private fontSizeToNum: Map<MarkType, number>;
private fontStylesToName: Map<MarkType, string>;
private listTypeToIcon: Map<NodeType, string>;
- private link: HTMLAnchorElement;
private linkEditor?: HTMLDivElement;
private linkText?: HTMLDivElement;
@@ -89,6 +88,7 @@ export class TooltipTextMenu {
});
});
+ this.updateLinkMenu();
//list of font styles
this.fontStylesToName = new Map();
@@ -121,11 +121,6 @@ export class TooltipTextMenu {
this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
this.listTypes = Array.from(this.listTypeToIcon.keys());
- this.link = document.createElement("a");
- this.link.target = "_blank";
- this.link.style.color = "white";
- //this.tooltip.appendChild(this.link);
-
this.tooltip.appendChild(this.createLink().render(this.view).dom);
this.tooltip.appendChild(this.createStar().render(this.view).dom);
@@ -191,6 +186,7 @@ export class TooltipTextMenu {
updateLinkMenu() {
if (!this.linkEditor || !this.linkText) {
this.linkEditor = document.createElement("div");
+ this.linkEditor.className = "ProseMirror-icon menuicon";
this.linkEditor.style.color = "black";
this.linkText = document.createElement("div");
this.linkText.style.cssFloat = "left";
@@ -231,8 +227,9 @@ export class TooltipTextMenu {
};
this.linkDrag = document.createElement("img");
this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
- this.linkDrag.style.width = "20px";
- this.linkDrag.style.height = "20px";
+ this.linkDrag.style.width = "15px";
+ this.linkDrag.style.height = "15px";
+ this.linkDrag.title = "Drag to create link";
this.linkDrag.style.color = "black";
this.linkDrag.style.background = "black";
this.linkDrag.style.cssFloat = "left";
@@ -250,10 +247,10 @@ export class TooltipTextMenu {
hideSource: false
});
};
- // this.linkEditor.appendChild(this.linkDrag);
+ this.linkEditor.appendChild(this.linkDrag);
// this.linkEditor.appendChild(this.linkText);
// this.linkEditor.appendChild(linkBtn);
- //this.tooltip.appendChild(this.linkEditor);
+ this.tooltip.appendChild(this.linkEditor);
}
let node = this.view.state.selection.$from.nodeAfter;
@@ -444,16 +441,24 @@ export class TooltipTextMenu {
enable(state) { return !state.selection.empty; },
run: (state, dispatch, view) => {
// to remove link
+ let curLink = "";
if (this.markActive(state, markType)) {
- toggleMark(markType)(state, dispatch);
- return true;
+
+ let { from, $from, to, empty } = state.selection;
+ let node = state.doc.nodeAt(from);
+ node && node.marks.map(m => {
+ m.type === markType && (curLink = m.attrs.href);
+ })
+ //toggleMark(markType)(state, dispatch);
+ //return true;
}
// to create link
openPrompt({
title: "Create a link",
fields: {
href: new TextField({
- label: "Link target",
+ value: curLink,
+ label: "Link Target",
required: true
}),
title: new TextField({ label: "Title" })
@@ -603,7 +608,6 @@ export class TooltipTextMenu {
}
}
this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks));
- this.updateLinkMenu();
}
//finds all active marks on selection in given group
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index b85a8040a..f52e3b658 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -230,6 +230,8 @@ ul#add-options-list {
.mainView-libraryFlyout {
height: 100%;
position: absolute;
+ display: flex;
+ flex-direction:column;
}
.mainView-libraryHandle {
@@ -241,4 +243,20 @@ ul#add-options-list {
position: absolute;
z-index: 1;
background: gray;
+}
+
+.mainView-workspace {
+ height:200px;
+ position:relative;
+ display:flex;
+}
+.mainView-library {
+ height:75%;
+ position:relative;
+ display:flex;
+}
+.mainView-recentlyClosed {
+ height:25%;
+ position:relative;
+ display:flex;
} \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 61ccf4c1d..fe59f52c8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,19 +1,19 @@
import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faArrowUp, faBell, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { faArrowDown, faArrowUp, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, runInAction } from 'mobx';
+import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import { SketchPicker } from 'react-color';
import Measure from 'react-measure';
import * as request from 'request';
-import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
+import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc';
import { Id } from '../../new_fields/FieldSymbols';
import { InkTool } from '../../new_fields/InkField';
import { List } from '../../new_fields/List';
import { listSpec } from '../../new_fields/Schema';
-import { Cast, FieldValue } from '../../new_fields/Types';
+import { Cast, FieldValue, NumCast } from '../../new_fields/Types';
import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
import { RouteStore } from '../../server/RouteStore';
import { emptyFunction, returnOne, returnTrue } from '../../Utils';
@@ -72,6 +72,22 @@ export class MainView extends React.Component {
window.removeEventListener("pointerup", this.pointerUp);
window.addEventListener("pointerup", this.pointerUp);
+
+ reaction(() => {
+ let workspaces = CurrentUserUtils.UserDocument.workspaces;
+ let recent = CurrentUserUtils.UserDocument.recentlyClosed;
+ if (!(recent instanceof Doc)) return 0;
+ if (!(workspaces instanceof Doc)) return 0;
+ let workspacesDoc = workspaces;
+ let recentDoc = recent;
+ let libraryHeight = this.getPHeight() - workspacesDoc[HeightSym]() - recentDoc[HeightSym]() - 20 + CurrentUserUtils.UserDocument[HeightSym]() * 0.00001;
+ return libraryHeight;
+ }, (libraryHeight: number) => {
+ if (libraryHeight && Math.abs(CurrentUserUtils.UserDocument[HeightSym]() - libraryHeight) > 5) {
+ CurrentUserUtils.UserDocument.height = libraryHeight;
+ }
+ (Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc) as Doc)!.allowClear = true;
+ }, { fireImmediately: true });
}
pointerDown = (e: PointerEvent) => this.isPointerDown = true;
@@ -163,7 +179,9 @@ export class MainView extends React.Component {
@action
createNewWorkspace = async (id?: string) => {
- const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
+ let workspaces = Cast(CurrentUserUtils.UserDocument.workspaces, Doc);
+ if (!(workspaces instanceof Doc)) return;
+ const list = Cast((CurrentUserUtils.UserDocument.workspaces as Doc).data, listSpec(Doc));
if (list) {
let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` });
var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] };
@@ -285,8 +303,11 @@ export class MainView extends React.Component {
};
@computed
get flyout() {
+ let sidebar = CurrentUserUtils.UserDocument.sidebar;
+ if (!(sidebar instanceof Doc)) return (null);
+ let sidebarDoc = sidebar;
return <DocumentView
- Document={CurrentUserUtils.UserDocument}
+ Document={sidebarDoc}
DataDoc={undefined}
addDocument={undefined}
addDocTab={this.addDocTabFunc}
@@ -304,7 +325,7 @@ export class MainView extends React.Component {
ContainingCollectionView={undefined}
zoomToScale={emptyFunction}
getScale={returnOne}>
- </DocumentView>;
+ </DocumentView>
}
@computed
get mainContent() {
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 4843a70a6..236704fa2 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -69,8 +69,7 @@ export namespace Templates {
`< div >
<div style="height:100%; width:100%;position:absolute;">{layout}</div>
<div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
- <img id="isExpander" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg=="
- width="15px" height="15px" />
+ <img id="isExpander" src="/assets/downarrow.png" width="15px" height="15px" />
</div>
</div > `
);
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 879898018..e4f9b5058 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -64,7 +64,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
active = (): boolean => {
var isSelected = this.props.isSelected();
- return isSelected || this._isChildActive || this.props.renderDepth === 0;
+ return isSelected || this._isChildActive || this.props.renderDepth === 0 || BoolCast(this.props.Document.excludeFromLibrary);
}
//TODO should this be observable?
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 19d07ecdc..d477f96f0 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -27,6 +27,7 @@ import { MainView } from '../MainView';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
import { faFile } from '@fortawesome/free-solid-svg-icons';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
library.add(faFile);
@observer
@@ -405,6 +406,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (doc instanceof Doc) {
let theDoc = doc;
CollectionDockingView.Instance._removedDocs.push(theDoc);
+ if (CurrentUserUtils.UserDocument.recentlyClosed instanceof Doc) {
+ Doc.AddDocToList(CurrentUserUtils.UserDocument.recentlyClosed, "data", doc, undefined, true, true);
+ }
SelectionManager.DeselectAll();
}
tab.contentItem.remove();
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 034a09eaa..bc733f152 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -16,7 +16,7 @@
align-items: center;
}
- .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid{
+ .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid {
width:100%;
height:100%;
position: absolute;
@@ -25,7 +25,17 @@
left: 0;
width: 100%;
position: absolute;
-
+ }
+ .collectionStackingView-masonrySingle {
+ width:100%;
+ height:100%;
+ position: absolute;
+ display:flex;
+ flex-direction: column;
+ top: 0;
+ left: 0;
+ width: 100%;
+ position: absolute;
}
.collectionStackingView-description {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6b4eddec9..aea74321e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,6 +1,6 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, reaction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, untracked } from "mobx";
import { observer } from "mobx-react";
import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
@@ -10,7 +10,6 @@ import { ContextMenu } from "../ContextMenu";
import { CollectionSchemaPreview } from "./CollectionSchemaView";
import "./CollectionStackingView.scss";
import { CollectionSubView } from "./CollectionSubView";
-import { resolve } from "bluebird";
import { undoBatch } from "../../util/UndoManager";
import { DragManager } from "../../util/DragManager";
@@ -25,6 +24,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
@computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); }
@computed get columnWidth() { return this.singleColumn ? (this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin) : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); }
+ @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
singleColDocHeight(d: Doc) {
let nw = NumCast(d.nativeWidth);
@@ -35,14 +35,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
componentDidMount() {
this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
- () => {
- if (this.singleColumn) {
- let children = this.childDocs.filter(d => !d.isMinimized);
- this.props.Document.height = children.reduce((height, d, i) =>
- height + this.singleColDocHeight(d) + (i === children.length - 1 ? this.yMargin : this.gridGap)
- , this.yMargin);
- }
- }, { fireImmediately: true });
+ () => this.singleColumn &&
+ (this.props.Document.height = this.filteredChildren.reduce((height, d, i) =>
+ height + this.singleColDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin))
+ , { fireImmediately: true });
}
componentWillUnmount() {
if (this._heightDisposer) this._heightDisposer();
@@ -50,14 +46,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@action
moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
- this.props.removeDocument(doc);
- addDocument(doc);
- return true;
+ return this.props.removeDocument(doc) && addDocument(doc);
}
- getDocTransform(doc: Doc, dref: HTMLDivElement) {
- let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
+ getSingleDocTransform(doc: Doc, ind: number, width: number) {
+ let localY = this.filteredChildren.reduce((height, d, i) =>
+ height + (i < ind ? this.singleColDocHeight(d) + this.gridGap : 0), this.yMargin);
+ let translate = this.props.ScreenToLocalTransform().inverse().transformPoint((this.props.PanelWidth() - width) / 2, localY);
let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
- let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+ let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translate[0], outerXf.translateY - translate[1]);
return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.columnWidth);
}
createRef = (ele: HTMLDivElement | null) => {
@@ -67,17 +63,15 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@computed
get singleColumnChildren() {
- let children = this.childDocs.filter(d => !d.isMinimized);
- return children.map((d, i) => {
+ return this.filteredChildren.map((d, i) => {
let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
- let dref = React.createRef<HTMLDivElement>();
- let dxf = () => this.getDocTransform(layoutDoc, dref.current!).scale(this.columnWidth / d[WidthSym]());
let width = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
let height = () => this.singleColDocHeight(layoutDoc);
+ let dxf = () => this.getSingleDocTransform(layoutDoc, i, width()).scale(this.columnWidth / d[WidthSym]());
+ let gap = i === 0 ? 0 : this.gridGap;
return <div className="collectionStackingView-columnDoc"
key={d[Id]}
- ref={dref}
- style={{ width: width(), height: height() }} >
+ style={{ width: width(), display: "inline-block", marginTop: gap, height: `${height() / (this.props.Document[HeightSym]() - 2 * this.yMargin) * 100}%` }} >
<CollectionSchemaPreview
Document={layoutDoc}
DataDocument={d !== this.props.DataDoc ? this.props.DataDoc : undefined}
@@ -98,11 +92,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
</div>;
});
}
+ getDocTransform(doc: Doc, dref: HTMLDivElement) {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
+ let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
+ let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+ return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.columnWidth);
+ }
docXfs: any[] = []
@computed
get children() {
this.docXfs.length = 0;
- return this.childDocs.filter(d => !d.isMinimized).map((d, i) => {
+ return this.filteredChildren.map((d, i) => {
let aspect = d.nativeHeight ? NumCast(d.nativeWidth) / NumCast(d.nativeHeight) : undefined;
let dref = React.createRef<HTMLDivElement>();
let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]());
@@ -187,14 +187,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
})
}
if (super.drop(e, de)) {
- if (targInd !== -1) {
- let newDoc = de.data.droppedDocuments[0];
- let docs = this.childDocList;
- if (docs) {
- let srcInd = docs.indexOf(newDoc);
- docs.splice(srcInd, 1);
- docs.splice(targInd > srcInd ? targInd - 1 : targInd, 0, newDoc);
- }
+ let newDoc = de.data.droppedDocuments[0];
+ let docs = this.childDocList;
+ if (docs) {
+ if (targInd === -1) targInd = docs.length;
+ else targInd = docs.indexOf(this.filteredChildren[targInd]);
+ let srcInd = docs.indexOf(newDoc);
+ docs.splice(srcInd, 1);
+ docs.splice(targInd > srcInd ? targInd - 1 : targInd, 0, newDoc);
}
}
return false;
@@ -223,7 +223,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
});
}
render() {
- let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.childDocs.filter(d => !d.isMinimized).length,
+ let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
let templatecols = "";
for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 79c23d71a..5287d3c13 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -102,6 +102,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
return added;
}
else if (de.data instanceof DragManager.AnnotationDragData) {
+ e.stopPropagation();
return this.props.addDocument(de.data.dropDocument);
}
return false;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 93a1ec1eb..d7725f444 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,5 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight, faCamera, faExpand, faBell, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
+import { faAngleRight, faCamera, faExpand, faTrash, faBell, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, trace } from "mobx";
import { observer } from "mobx-react";
@@ -51,6 +51,7 @@ export interface TreeViewProps {
library.add(faTrashAlt);
library.add(faAngleRight);
library.add(faBell);
+library.add(faTrash);
library.add(faCamera);
library.add(faExpand);
library.add(faCaretDown);
@@ -471,9 +472,12 @@ export class CollectionTreeView extends CollectionSubView(Document) {
}
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!e.isPropagationStopped() && this.props.Document.excludeFromLibrary) { // excludeFromLibrary means this is the user document
+ if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { // excludeFromLibrary means this is the user document
ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()) });
ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) });
+ e.stopPropagation();
+ e.preventDefault();
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
}
@@ -506,6 +510,17 @@ export class CollectionTreeView extends CollectionSubView(Document) {
</div>
</div >;
}
+ @computed get clearButton() {
+ return <div id="toolbar" key="toolbar">
+ <div >
+ <button className="toolbar-button round-button" title="Notifs"
+ onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}>
+ <FontAwesomeIcon icon={faTrash} size="sm" />
+ </button>
+ </div>
+ </div >;
+ }
+
render() {
let dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
@@ -531,7 +546,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
TreeView.loadId = doc[Id];
Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
}} />
- {this.props.Document.excludeFromLibrary ? this.notifsButton : (null)}
+ {this.props.Document.workspaceLibrary ? this.notifsButton : (null)}
+ {this.props.Document.allowClear ? this.clearButton : (null)}
<ul className="no-indent" style={{ width: "max-content" }} >
{
TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4b7d462e5..7a22b7ec3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -249,16 +249,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
var scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
- // this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
- // this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
- this.props.Document.panX = panX;
+ this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
+ this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
+ // this.props.Document.panX = panX;
+ // this.props.Document.panY = panY;
if (this.props.Document.scrollY) {
this.props.Document.scrollY = panY;
- this.props.Document.panY = panY;
- }
- else {
-
- this.props.Document.panY = panY;
}
}
@@ -468,8 +464,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
description: "Add freeform arrangement",
event: () => {
let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
- const docs = DocListCast(this.Document[this.props.fieldKey]);
- docs.map(d => d.transition = "transform 1s");
let overlayDisposer: () => void;
const script = this.Document[key];
let originalText: string | undefined = undefined;
@@ -484,6 +478,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
onError(script.errors.map(error => error.messageText).join("\n"));
return;
}
+ const docs = DocListCast(this.Document[this.props.fieldKey]);
+ docs.map(d => d.transition = "transform 1s");
this.Document[key] = new ScriptField(script);
overlayDisposer();
setTimeout(() => docs.map(d => d.transition = undefined), 1200);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 0da4888a1..56a14e26e 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 { CollectionViewType } from "../collections/CollectionBaseView";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -82,7 +83,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
// by checking the layoutKey. This should probably be moved into
// a prop so that the overlay can explicitly turn off templates.
if ((this.props.layoutKey === "overlayLayout" && StrCast(this.props.Document.layout).indexOf("CollectionView") !== -1) ||
- (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1)) {
+ (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1) ||
+ (this.props.layoutKey === "layout" && NumCast(this.props.Document.viewType)) !== CollectionViewType.Freeform) {
this.templates.forEach(template => {
let self = this;
// this scales constants in the markup by the scaling applied to the document, but caps the constants to be smaller
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2610d0e6d..c75ff51e2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -375,7 +375,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble && this.active) {
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ if (!this.props.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3)) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5da52f4c2..b9cecdd24 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -182,7 +182,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
return url.href;
}
let ext = path.extname(url.href);
- return url.href.replace(ext, this._curSuffix + ext);
+ const suffix = this.props.renderDepth <= 1 ? "_o" : this._curSuffix;
+ return url.href.replace(ext, suffix + ext);
}
@observable _smallRetryCount = 1;
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index e6cc50620..a97ec8831 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -177,9 +177,10 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
LinkManager.Instance.deleteGroupType(groupType);
}
- copyGroup = (groupType: string): void => {
+ copyGroup = async (groupType: string): Promise<void> => {
let sourceGroupDoc = this.props.groupDoc;
- let sourceMdDoc = Cast(sourceGroupDoc.metadata, Doc, new Doc);
+ const sourceMdDoc = await Cast(sourceGroupDoc.metadata, Doc);
+ if (!sourceMdDoc) return;
let destDoc = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
// let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc);
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index 4dee6741f..9728671c0 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -56,11 +56,13 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
let mdRows: Array<JSX.Element> = [];
if (groupDoc) {
- let mdDoc = Cast(groupDoc.metadata, Doc, new Doc);
- let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
- mdRows = keys.map(key => {
- return (<div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
- });
+ let mdDoc = Cast(groupDoc.metadata, Doc, null);
+ if (mdDoc) {
+ let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
+ mdRows = keys.map(key => {
+ return (<div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
+ });
+ }
}
return (<div className="link-metadata">{mdRows}</div>);
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index f93b2e59f..d6970e7f4 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -3,11 +3,8 @@ import "./PDFMenu.scss";
import { observable, action, runInAction } from "mobx";
import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { emptyFunction, returnZero, returnTrue, returnFalse } from "../../../Utils";
+import { emptyFunction, returnFalse } from "../../../Utils";
import { Doc } from "../../../new_fields/Doc";
-import { DragManager } from "../../util/DragManager";
-import { DocUtils } from "../../documents/Documents";
-import { PresentationView } from "../presentationview/PresentationView";
@observer
export default class PDFMenu extends React.Component {
@@ -20,7 +17,7 @@ export default class PDFMenu extends React.Component {
@observable private _transitionDelay: string = "";
- StartDrag: (e: PointerEvent) => void = emptyFunction;
+ StartDrag: (e: PointerEvent, ele: HTMLDivElement) => void = emptyFunction;
Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction;
Delete: () => void = emptyFunction;
Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction;
@@ -35,9 +32,10 @@ export default class PDFMenu extends React.Component {
private _offsetY: number = 0;
private _offsetX: number = 0;
- private _mainCont: React.RefObject<HTMLDivElement>;
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _commentCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef();
private _dragging: boolean = false;
- private _snippetButton: React.RefObject<HTMLButtonElement>;
@observable private _keyValue: string = "";
@observable private _valueValue: string = "";
@observable private _added: boolean = false;
@@ -46,9 +44,6 @@ export default class PDFMenu extends React.Component {
super(props);
PDFMenu.Instance = this;
-
- this._mainCont = React.createRef();
- this._snippetButton = React.createRef();
}
pointerDown = (e: React.PointerEvent) => {
@@ -69,7 +64,7 @@ export default class PDFMenu extends React.Component {
return;
}
- this.StartDrag(e);
+ this.StartDrag(e, this._commentCont.current!);
this._dragging = true;
}
@@ -246,7 +241,7 @@ export default class PDFMenu extends React.Component {
style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
</button>,
- <button className="pdfMenu-button" title="Drag to Annotate" onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" key="2" /></button>,
+ <button className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" key="2" /></button>,
this.Status === "snippet" ? <button className="pdfMenu-button" title="Drag to Snippetize Selection" onPointerDown={this.snippetStart} ref={this._snippetButton}><FontAwesomeIcon icon="cut" size="lg" /></button> : undefined,
<button className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} key="3"
style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 92f5390ae..49eac71c4 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -152,7 +152,7 @@ export default class Page extends React.Component<IPageProps> {
* start a drag event and create or put the necessary info into the drag event.
*/
@action
- startDrag = (e: PointerEvent): void => {
+ startDrag = (e: PointerEvent, ele: HTMLDivElement): void => {
e.preventDefault();
e.stopPropagation();
let thisDoc = this.props.parent.Document;
@@ -163,7 +163,7 @@ export default class Page extends React.Component<IPageProps> {
// create dragData and star tdrag
let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc);
if (this._textLayer.current) {
- DragManager.StartAnnotationDrag([this._textLayer.current], dragData, e.pageX, e.pageY, {
+ DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, {
handlers: {
dragComplete: emptyFunction,
},
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 51f0ed607..804ffa7f5 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -187,10 +187,10 @@ export class SearchItem extends React.Component<SearchItemProps> {
if (this.props.doc.type === DocTypes.LINK) {
if (this.props.doc.anchor1 && this.props.doc.anchor2) {
- let doc1 = Cast(this.props.doc.anchor1, Doc, new Doc());
- let doc2 = Cast(this.props.doc.anchor2, Doc, new Doc());
- doc1.libraryBrush = true;
- doc2.libraryBrush = true;
+ let doc1 = Cast(this.props.doc.anchor1, Doc, null);
+ let doc2 = Cast(this.props.doc.anchor2, Doc, null);
+ doc1 && (doc1.libraryBrush = true);
+ doc2 && (doc2.libraryBrush = true);
}
} else {
let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
@@ -204,10 +204,10 @@ export class SearchItem extends React.Component<SearchItemProps> {
if (this.props.doc.type === DocTypes.LINK) {
if (this.props.doc.anchor1 && this.props.doc.anchor2) {
- let doc1 = Cast(this.props.doc.anchor1, Doc, new Doc());
- let doc2 = Cast(this.props.doc.anchor2, Doc, new Doc());
- doc1.libraryBrush = false;
- doc2.libraryBrush = false;
+ let doc1 = Cast(this.props.doc.anchor1, Doc, null);
+ let doc2 = Cast(this.props.doc.anchor2, Doc, null);
+ doc1 && (doc1.libraryBrush = false);
+ doc2 && (doc2.libraryBrush = false);
}
} else {
let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
@@ -241,7 +241,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
render() {
return (
<div className="search-overview" onPointerDown={this.pointerDown} onContextMenu={this.onContextMenu}>
- <div className="search-item" onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc} ref={this.collectionRef} id="result"
+ <div className="search-item" onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc} id="result"
onClick={this.onClick} onPointerDown={this.pointerDown} >
<div className="main-search-info">
<div title="Drag as document" onPointerDown={this.onPointerDown}> <FontAwesomeIcon icon="file" size="lg" /> </div>
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index c361e3032..aa13b1d7a 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -9,6 +9,7 @@ import { ObjectField } from "./ObjectField";
import { RefField, FieldId } from "./RefField";
import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols";
import { scriptingGlobal } from "../client/util/Scripting";
+import { List } from "./List";
export namespace Field {
export function toScriptString(field: Field): string {
@@ -241,12 +242,24 @@ export namespace Doc {
return Array.from(results);
}
- export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean) {
+ export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean) {
+ if (target[key] === undefined) {
+ Doc.GetProto(target)[key] = new List<Doc>();
+ }
let list = Cast(target[key], listSpec(Doc));
if (list) {
- let ind = relativeTo ? list.indexOf(relativeTo) : -1;
- if (ind === -1) list.push(doc);
- else list.splice(before ? ind : ind + 1, 0, doc);
+ if (allowDuplicates !== true) {
+ let pind = list.reduce((l, d, i) => d instanceof Doc && Doc.AreProtosEqual(d, doc) ? i : l, -1);
+ if (pind !== -1) {
+ list.splice(pind, 1);
+ }
+ }
+ if (first) list.splice(0, 0, doc);
+ else {
+ let ind = relativeTo ? list.indexOf(relativeTo) : -1;
+ if (ind === -1) list.push(doc);
+ else list.splice(before ? ind : ind + 1, 0, doc);
+ }
}
return true;
}
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index 8dd893aa4..39d384d64 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -45,9 +45,10 @@ export interface Interface {
[key: string]: InterfaceValue;
// [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
}
+export type WithoutRefField<T extends Field> = T extends RefField ? never : T;
export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
-export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal: WithoutList<ToType<T>> | null): WithoutList<ToType<T>>;
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
if (field instanceof Promise) {
return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal === null ? undefined : defaultVal;
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index abb777adf..b5e50d501 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -2,7 +2,6 @@ import { UndoManager } from "../client/util/UndoManager";
import { Doc, Field } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField } from "./Proxy";
-import { FieldValue } from "./Types";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action } from "mobx";
@@ -13,6 +12,7 @@ function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
}
const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+ //console.log("-set " + target[SelfProxy].title + "(" + target[SelfProxy][prop] + ")." + prop.toString() + " = " + value);
if (SerializationHelper.IsSerializing()) {
target[prop] = value;
return true;
diff --git a/src/server/GarbageColletor.ts b/src/server/GarbageColletor.ts
new file mode 100644
index 000000000..f26b0cec6
--- /dev/null
+++ b/src/server/GarbageColletor.ts
@@ -0,0 +1,51 @@
+import { Database } from './database';
+
+function addDoc(doc: any, ids: string[]) {
+ for (const key in doc) {
+ if (!doc.hasOwnProperty(key)) {
+ continue;
+ }
+ const field = doc[key];
+ if (!(field instanceof Object)) {
+ continue;
+ }
+ if (field.__type === "proxy") {
+ ids.push(field.fieldId);
+ } else if (field.__type === "list") {
+ addDoc(field.fields, ids);
+ }
+ }
+}
+
+async function GarbageCollect() {
+ // await new Promise(res => setTimeout(res, 3000));
+ const cursor = await Database.Instance.query({}, 'users');
+ const users = await cursor.toArray();
+ const ids: string[] = users.map(user => user.userDocumentId);
+ const visited = new Set<string>();
+
+ while (ids.length) {
+ const id = ids.pop()!;
+ if (visited.has(id)) continue;
+ const doc = await new Promise<{ [key: string]: any }>(res => Database.Instance.getDocument(id, res, "newDocuments"));
+ if (doc === undefined) {
+ console.log(`Couldn't find field with Id ${id}`);
+ continue;
+ }
+ visited.add(id);
+ addDoc(doc.fields, ids);
+ console.log(`To Go: ${ids.length}, visited: ${visited.size}`);
+ }
+
+ console.log(`Done: ${visited.size}`);
+
+ cursor.close();
+
+ const toDeleteCursor = await Database.Instance.query({ _id: { $nin: Array.from(visited) } });
+ const toDelete = (await toDeleteCursor.toArray()).map(doc => doc._id);
+ toDeleteCursor.close();
+ const result = await Database.Instance.delete({ _id: { $in: toDelete } }, "newDocuments");
+ console.log(`${result.deletedCount} documents deleted`);
+}
+
+GarbageCollect();
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 816c5f269..e328d6e5c 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -10,7 +10,7 @@ import { CollectionView } from "../../../client/views/collections/CollectionView
import { Doc } from "../../../new_fields/Doc";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
-import { Cast } from "../../../new_fields/Types";
+import { Cast, FieldValue } from "../../../new_fields/Types";
import { RouteStore } from "../../RouteStore";
export class CurrentUserUtils {
@@ -32,7 +32,11 @@ export class CurrentUserUtils {
doc.dropAction = "alias";
doc.layout = CollectionView.LayoutString();
doc.title = this.email;
+ this.updateUserDocument(doc);
doc.data = new List<Doc>();
+ doc.gridGap = 5;
+ doc.xMargin = 5;
+ doc.yMargin = 5;
doc.excludeFromLibrary = true;
doc.optionalRightCollection = Docs.StackingDocument([], { title: "New mobile uploads" });
// doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
@@ -40,6 +44,29 @@ export class CurrentUserUtils {
return doc;
}
+ static updateUserDocument(doc: Doc) {
+ if (doc.workspaces === undefined) {
+ const workspaces = Docs.TreeDocument([], { title: "Workspaces", height: 100 });
+ workspaces.excludeFromLibrary = true;
+ workspaces.workspaceLibrary = true;
+ doc.workspaces = workspaces;
+ }
+ if (doc.recentlyClosed === undefined) {
+ const recentlyClosed = Docs.TreeDocument([], { title: "Recently Closed", height: 75 });
+ recentlyClosed.excludeFromLibrary = true;
+ doc.recentlyClosed = recentlyClosed;
+ }
+ if (doc.sidebar === undefined) {
+ const sidebar = Docs.StackingDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], { title: "Sidebar" });
+ sidebar.excludeFromLibrary = true;
+ sidebar.gridGap = 5;
+ sidebar.xMargin = 5;
+ sidebar.yMargin = 5;
+ doc.sidebar = sidebar;
+ }
+
+ }
+
public static async loadCurrentUser(): Promise<any> {
let userPromise = rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => {
if (response) {
@@ -52,8 +79,14 @@ export class CurrentUserUtils {
});
let userDocPromise = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => {
if (id) {
- return DocServer.GetRefField(id).then(field =>
- runInAction(() => this.user_document = field instanceof Doc ? field : this.createUserDocument(id)));
+ return DocServer.GetRefField(id).then(async field => {
+ if (field instanceof Doc) {
+ await this.updateUserDocument(field);
+ runInAction(() => this.user_document = field);
+ } else {
+ runInAction(() => this.user_document = this.createUserDocument(id));
+ }
+ });
} else {
throw new Error("There should be a user id! Why does Dash think there isn't one?");
}
diff --git a/src/server/database.ts b/src/server/database.ts
index d240bd909..a17447629 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -41,11 +41,17 @@ export class Database {
}
}
- public delete(id: string, collectionName = Database.DocumentsCollection) {
+ public delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
+ public delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
+ public delete(id: any, collectionName = Database.DocumentsCollection) {
+ if (typeof id === "string") {
+ id = { _id: id };
+ }
if (this.db) {
- this.db.collection(collectionName).remove({ id: id });
+ const db = this.db;
+ return new Promise(res => db.collection(collectionName).deleteMany(id, (err, result) => res(result)));
} else {
- this.onConnect.push(() => this.delete(id, collectionName));
+ return new Promise(res => this.onConnect.push(() => res(this.delete(id, collectionName))));
}
}
@@ -125,7 +131,7 @@ export class Database {
return Promise.resolve<mongodb.Cursor>(this.db.collection(collectionName).find(query));
} else {
return new Promise<mongodb.Cursor>(res => {
- this.onConnect.push(() => res(this.query(query)));
+ this.onConnect.push(() => res(this.query(query, collectionName)));
});
}
}
diff --git a/src/server/index.ts b/src/server/index.ts
index a93940907..bf946fc9f 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -295,9 +295,10 @@ app.post(
const file = path.basename(files[name].path);
const ext = path.extname(file);
let resizers = [
- { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: "_s" },
- { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" },
- { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: "_l" },
+ { resizer: sharp().rotate(), suffix: "_o" },
+ { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }).rotate(), suffix: "_s" },
+ { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }).rotate(), suffix: "_m" },
+ { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }).rotate(), suffix: "_l" },
];
let isImage = false;
if (pngTypes.includes(ext)) {