From 2e745fca1bf2cea9fe3518bf85590ad2d871afe0 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 17 Jul 2019 13:03:10 -0400
Subject: cursor proto id check tweak

---
 src/client/views/collections/CollectionSubView.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/client/views/collections/CollectionSubView.tsx')

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 4fd11add4..5d5189336 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -9,7 +9,7 @@ import { BoolCast, Cast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { RouteStore } from "../../../server/RouteStore";
 import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions } from "../../documents/Documents";
+import { Docs, DocumentOptions, DocumentType } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocComponent } from "../DocComponent";
@@ -74,7 +74,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     return;
                 }
                 // The following conditional detects a recurring bug we've seen on the server
-                if (proto[Id] === "collectionProto") {
+                if (proto[Id] === Docs.Prototypes.get(DocumentType.COL)[Id]) {
                     alert("COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...");
                     console.log(doc);
                     console.log(proto);
-- 
cgit v1.2.3-70-g09d2


From 5aa6bcdd4e23bb9e9d05181d0dc6b638e45e397b Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 17 Jul 2019 20:48:14 -0400
Subject: Moved DocServer.prepend and changed how corsProxy works

---
 src/Utils.ts                                       | 13 +++++
 src/client/DocServer.ts                            |  8 ----
 src/client/util/History.ts                         |  2 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |  4 +-
 src/client/util/SearchUtil.ts                      |  3 +-
 src/client/util/TooltipTextMenu.tsx                |  8 ++--
 src/client/views/MainView.tsx                      |  4 +-
 src/client/views/SearchBox.tsx                     |  3 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  7 +--
 src/client/views/nodes/DocumentView.tsx            |  4 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  5 +-
 src/client/views/nodes/ImageBox.tsx                |  6 +--
 src/client/views/nodes/VideoBox.tsx                |  4 +-
 src/client/views/pdf/PDFViewer.tsx                 | 55 +++++-----------------
 src/client/views/search/SearchBox.tsx              |  6 +--
 src/mobile/ImageUpload.tsx                         |  5 +-
 .../authentication/models/current_user_utils.ts    |  5 +-
 src/server/index.ts                                |  2 +-
 19 files changed, 65 insertions(+), 81 deletions(-)

(limited to 'src/client/views/collections/CollectionSubView.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index e8a80bdc3..ac6d127cc 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -2,6 +2,7 @@ import v4 = require('uuid/v4');
 import v5 = require("uuid/v5");
 import { Socket } from 'socket.io';
 import { Message } from './server/Message';
+import { RouteStore } from './server/RouteStore';
 
 export class Utils {
 
@@ -27,6 +28,18 @@ export class Utils {
         return { scale, translateX, translateY };
     }
 
+    /**
+     * A convenience method. Prepends the full path (i.e. http://localhost:1050) to the
+     * requested extension
+     * @param extension the specified sub-path to append to the window origin
+     */
+    public static prepend(extension: string): string {
+        return window.location.origin + extension;
+    }
+    public static CorsProxy(url: string): string {
+        return this.prepend(RouteStore.corsProxy + "/") + encodeURIComponent(url);
+    }
+
     public static CopyText(text: string) {
         var textArea = document.createElement("textarea");
         textArea.value = text;
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 6737657c8..8c64d2b2f 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -47,14 +47,6 @@ export namespace DocServer {
         Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
         Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
     }
-    /**
-     * A convenience method. Prepends the full path (i.e. http://localhost:1050) to the
-     * requested extension
-     * @param extension the specified sub-path to append to the window origin
-     */
-    export function prepend(extension: string): string {
-        return window.location.origin + extension;
-    }
 
     function errorFunc(): never {
         throw new Error("Can't use DocServer without calling init first");
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index cbf5b3fc8..e9ff21b22 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -129,7 +129,7 @@ export namespace HistoryUtil {
 
     function addStringifier(type: string, keys: string[], customStringifier?: (state: ParsedUrl, current: string) => string) {
         stringifiers[type] = state => {
-            let path = DocServer.prepend(`/${type}`);
+            let path = Utils.prepend(`/${type}`);
             if (customStringifier) {
                 path = customStringifier(state, path);
             }
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index c096e9ceb..75b0b52a7 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -98,12 +98,12 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
 
             runInAction(() => this.remaining++);
 
-            let prom = fetch(DocServer.prepend(RouteStore.upload), {
+            let prom = fetch(Utils.prepend(RouteStore.upload), {
                 method: 'POST',
                 body: formData
             }).then(async (res: Response) => {
                 (await res.json()).map(action((file: any) => {
-                    let docPromise = Docs.Get.DocumentFromType(type, DocServer.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName });
+                    let docPromise = Docs.Get.DocumentFromType(type, Utils.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName });
                     docPromise.then(doc => {
                         doc && docs.push(doc) && runInAction(() => this.remaining--);
                     });
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index abf1a7c32..ee5a83710 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -2,6 +2,7 @@ import * as rp from 'request-promise';
 import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
+import { Utils } from '../../Utils';
 
 export namespace SearchUtil {
     export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
@@ -29,7 +30,7 @@ export namespace SearchUtil {
     export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>;
     export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) {
         query = query || "*"; //If we just have a filter query, search for * as the query
-        const result: IdSearchResult = JSON.parse(await rp.get(DocServer.prepend("/search"), {
+        const result: IdSearchResult = JSON.parse(await rp.get(Utils.prepend("/search"), {
             qs: { ...options, q: query },
         }));
         if (!returnDocs) {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 309d19016..a4c053de2 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -19,7 +19,7 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie
 import { DocumentManager } from "./DocumentManager";
 import { Id } from "../../new_fields/FieldSymbols";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { SelectionManager } from "./SelectionManager";
+import { Utils } from "../../Utils";
 
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
@@ -213,8 +213,8 @@ export class TooltipTextMenu {
                 let link = node && node.marks.find(m => m.type.name === "link");
                 if (link) {
                     let href: string = link.attrs.href;
-                    if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                        let docid = href.replace(DocServer.prepend("/doc/"), "");
+                    if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                        let docid = href.replace(Utils.prepend("/doc/"), "");
                         DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
                             if (f instanceof Doc) {
                                 if (DocumentManager.Instance.getDocumentView(f)) {
@@ -254,7 +254,7 @@ export class TooltipTextMenu {
                                 if (docView && docView.props.ContainingCollectionView) {
                                     proto.sourceContext = docView.props.ContainingCollectionView.props.Document;
                                 }
-                                linkDoc instanceof Doc && this.makeLink(DocServer.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab");
+                                linkDoc instanceof Doc && this.makeLink(Utils.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab");
                             }),
                         },
                         hideSource: false
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ce7369220..94a4835a1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -16,7 +16,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { Cast, FieldValue, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnOne, returnTrue } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { SetupDrag } from '../util/DragManager';
@@ -433,7 +433,7 @@ export class MainView extends React.Component {
         return [
             this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <FilterBox /> </div> : null,
             <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
-                <button onClick={() => window.location.assign(DocServer.prepend(RouteStore.logout))}>Log Out</button></div>
+                <button onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>Log Out</button></div>
         ];
 
     }
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 1b9be841f..33cb63df5 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -13,6 +13,7 @@ import { Docs } from '../documents/Documents';
 import { SetupDrag } from '../util/DragManager';
 import { SearchItem } from './search/SearchItem';
 import "./SearchBox.scss";
+import { Utils } from '../../Utils';
 
 library.add(faSearch);
 library.add(faObjectGroup);
@@ -47,7 +48,7 @@ export class SearchBox extends React.Component {
 
     @action
     getResults = async (query: string) => {
-        let response = await rp.get(DocServer.prepend('/search'), {
+        let response = await rp.get(Utils.prepend('/search'), {
             qs: {
                 query
             }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a193ff677..ba7903419 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -465,7 +465,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             .off('click') //unbind the current click handler
             .click(action(function () {
                 stack.config.fixed = !stack.config.fixed;
-                // var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
+                // var url = Utils.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
                 // let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
             }));
     }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5d5189336..2ddefb3c0 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -20,6 +20,7 @@ import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import React = require("react");
 import { MainView } from "../MainView";
+import { Utils } from "../../../Utils";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -164,7 +165,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 } else {
                     let path = window.location.origin + "/doc/";
                     if (text.startsWith(path)) {
-                        let docid = text.replace(DocServer.prepend("/doc/"), "").split("?")[0];
+                        let docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0];
                         DocServer.GetRefField(docid).then(f => {
                             if (f instanceof Doc) {
                                 if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
@@ -193,7 +194,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
                     let str: string;
                     let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
-                        .then(action((s: string) => rp.head(DocServer.prepend(RouteStore.corsProxy + "/" + (str = s)))))
+                        .then(action((s: string) => rp.head(Utils.CorsProxy(str = s))))
                         .then(result => {
                             let type = result["content-type"];
                             if (type) {
@@ -219,7 +220,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     }).then(async (res: Response) => {
                         (await res.json()).map(action((file: any) => {
                             let full = { ...options, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300, width: 300, title: dropFileName };
-                            let path = DocServer.prepend(file);
+                            let path = Utils.prepend(file);
                             Docs.Get.DocumentFromType(type, path, full).then(doc => doc && this.props.addDocument(doc));
                         }));
                     });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 245dd319d..af1cc079b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -557,13 +557,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
         cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
-        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
+        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
         cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         type User = { email: string, userDocumentId: string };
         let usersMenu: ContextMenuProps[] = [];
         try {
-            let stuff = await rp.get(DocServer.prepend(RouteStore.getUsers));
+            let stuff = await rp.get(Utils.prepend(RouteStore.getUsers));
             const users: User[] = JSON.parse(stuff);
             usersMenu = users.filter(({ email }) => email !== CurrentUserUtils.email).map(({ email, userDocumentId }) => ({
                 description: email, event: async () => {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 066cc40e2..99801ecff 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -35,6 +35,7 @@ import "./FormattedTextBox.scss";
 import React = require("react");
 import { DateField } from '../../../new_fields/DateField';
 import { thisExpression } from 'babel-types';
+import { Utils } from '../../../Utils';
 
 library.add(faEdit);
 library.add(faSmile);
@@ -311,8 +312,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href;
             }
             if (href) {
-                if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                    this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
+                if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                     if (this._linkClicked) {
                         DocServer.GetRefField(this._linkClicked).then(async linkDoc => {
                             if (linkDoc instanceof Doc) {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ebe3732e6..4ffd265d7 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -165,12 +165,12 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             recorder.ondataavailable = async function (e: any) {
                 const formData = new FormData();
                 formData.append("file", e.data);
-                const res = await fetch(DocServer.prepend(RouteStore.upload), {
+                const res = await fetch(Utils.prepend(RouteStore.upload), {
                     method: 'POST',
                     body: formData
                 });
                 const files = await res.json();
-                const url = DocServer.prepend(files[0]);
+                const url = Utils.prepend(files[0]);
                 // upload to server with known URL 
                 let audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", x: NumCast(self.props.Document.x), y: NumCast(self.props.Document.y), width: 200, height: 32 });
                 audioDoc.embed = true;
@@ -327,7 +327,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx
         let nativeWidth = FieldValue(this.Document.nativeWidth, pw);
         let nativeHeight = FieldValue(this.Document.nativeHeight, 0);
-        let paths: string[] = [window.origin + RouteStore.corsProxy + "/" + "http://www.cs.brown.edu/~bcz/noImage.png"];
+        let paths: string[] = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
         // this._curSuffix = "";
         // if (w > 20) {
         Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 8b8e500c4..0f2d18f6b 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -122,7 +122,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
     public static async convertDataUri(imageUri: string, returnedFilename: string) {
         try {
-            let posting = DocServer.prepend(RouteStore.dataUriToImage);
+            let posting = Utils.prepend(RouteStore.dataUriToImage);
             const returnedUri = await rp.post(posting, {
                 body: {
                     uri: imageUri,
@@ -164,7 +164,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                     let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
                     VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
                         if (returnedFilename) {
-                            let url = DocServer.prepend(returnedFilename);
+                            let url = Utils.prepend(returnedFilename);
                             let imageSummary = Docs.Create.ImageDocument(url, {
                                 x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
                                 width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 699a1ffd7..b7ded7e06 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -4,23 +4,18 @@ import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import * as rp from "request-promise";
 import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
-import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../new_fields/Types";
-import { emptyFunction } from "../../../Utils";
-import { DocServer } from "../../DocServer";
-import { Docs, DocUtils, DocumentOptions } from "../../documents/Documents";
-import { DocumentManager } from "../../util/DocumentManager";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, Utils } from "../../../Utils";
+import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
-import { DocumentView } from "../nodes/DocumentView";
-import { PDFBox, handleBackspace } from "../nodes/PDFBox";
+import { PDFBox } from "../nodes/PDFBox";
 import Page from "./Page";
 import "./PDFViewer.scss";
 import React = require("react");
-import PDFMenu from "./PDFMenu";
-import { UndoManager } from "../../util/UndoManager";
-import { CompileScript, CompiledScript, CompileResult } from "../../util/Scripting";
+import { CompileScript, CompileResult } from "../../util/Scripting";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import Annotation from "./Annotation";
@@ -90,16 +85,12 @@ export class Viewer extends React.Component<IViewerProps> {
     private _annotationReactionDisposer?: IReactionDisposer;
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _filterReactionDisposer?: IReactionDisposer;
-    private _activeReactionDisposer?: IReactionDisposer;
     private _viewer: React.RefObject<HTMLDivElement>;
     private _mainCont: React.RefObject<HTMLDivElement>;
     private _pdfViewer: any;
     // private _textContent: Pdfjs.TextContent[] = [];
     private _pdfFindController: any;
     private _searchString: string = "";
-    private _rendered: boolean = false;
-    private _pageIndex: number = -1;
-    private _matchIndex: number = 0;
 
     constructor(props: IViewerProps) {
         super(props);
@@ -135,23 +126,6 @@ export class Viewer extends React.Component<IViewerProps> {
             },
             { fireImmediately: true });
 
-        this._activeReactionDisposer = reaction(
-            () => this.props.parent.props.active(),
-            () => {
-                runInAction(() => {
-                    if (!this.props.parent.props.active()) {
-                        this._searching = false;
-                        this._pdfFindController = null;
-                        if (this._viewer.current) {
-                            let cns = this._viewer.current.childNodes;
-                            for (let i = cns.length - 1; i >= 0; i--) {
-                                cns.item(i).remove();
-                            }
-                        }
-                    }
-                });
-            }
-        );
 
         if (this.props.parent.props.ContainingCollectionView) {
             this._filterReactionDisposer = reaction(
@@ -346,7 +320,7 @@ export class Viewer extends React.Component<IViewerProps> {
             this._isPage[page] = "image";
             const address = this.props.url;
             try {
-                let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
+                let res = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
                 runInAction(() => this._visibleElements[page] =
                     <img key={res.path} src={res.path} onError={handleError}
                         style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />);
@@ -476,7 +450,6 @@ export class Viewer extends React.Component<IViewerProps> {
                             phraseSearch: true,
                             query: searchString
                         });
-                    this._rendered = true;
                 });
                 container.addEventListener("pagerendered", () => {
                     console.log("rendered");
@@ -488,7 +461,6 @@ export class Viewer extends React.Component<IViewerProps> {
                             phraseSearch: true,
                             query: searchString
                         });
-                    this._rendered = true;
                 });
             }
         }
@@ -563,7 +535,6 @@ export class Viewer extends React.Component<IViewerProps> {
                     });
                     container.addEventListener("pagerendered", () => {
                         console.log("rendered");
-                        this._rendered = true;
                     });
                     this._pdfViewer.setDocument(this.props.pdf);
                     this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer);
@@ -703,17 +674,17 @@ class SimpleLinkService {
     externalLinkRel: any = null;
     pdf: any = null;
 
-    navigateTo(dest: any) { }
+    navigateTo() { }
 
-    getDestinationHash(dest: any) { return "#"; }
+    getDestinationHash() { return "#"; }
 
-    getAnchorUrl(hash: any) { return "#"; }
+    getAnchorUrl() { return "#"; }
 
-    setHash(hash: any) { }
+    setHash() { }
 
-    executeNamedAction(action: any) { }
+    executeNamedAction() { }
 
-    cachePageRef(pageNum: any, pageRef: any) { }
+    cachePageRef() { }
 
     get pagesCount() {
         return this.pdf ? this.pdf.numPages : 0;
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 16c44225a..2214ac8af 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -9,12 +9,12 @@ import { Docs } from '../../documents/Documents';
 import { NumCast, Cast } from '../../../new_fields/Types';
 import { Doc } from '../../../new_fields/Doc';
 import { SearchItem } from './SearchItem';
-import { DocServer } from '../../DocServer';
 import * as rp from 'request-promise';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { SearchUtil } from '../../util/SearchUtil';
 import { RouteStore } from '../../../server/RouteStore';
 import { FilterBox } from './FilterBox';
+import { Utils } from '../../../Utils';
 
 
 @observer
@@ -74,7 +74,7 @@ export class SearchBox extends React.Component {
 
     public static async convertDataUri(imageUri: string, returnedFilename: string) {
         try {
-            let posting = DocServer.prepend(RouteStore.dataUriToImage);
+            let posting = Utils.prepend(RouteStore.dataUriToImage);
             const returnedUri = await rp.post(posting, {
                 body: {
                     uri: imageUri,
@@ -154,7 +154,7 @@ export class SearchBox extends React.Component {
                         filteredDocs.forEach(doc => {
                             const index = this._resultsSet.get(doc);
                             const highlight = highlights[doc[Id]];
-                            const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []
+                            const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : [];
                             if (index === undefined) {
                                 this._resultsSet.set(doc, this._results.length);
                                 this._results.push([doc, hlights]);
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index a8f94b746..33a615cbf 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -11,6 +11,7 @@ import { listSpec } from '../new_fields/Schema';
 import { List } from '../new_fields/List';
 import { observer } from 'mobx-react';
 import { observable } from 'mobx';
+import { Utils } from '../Utils';
 
 
 
@@ -57,7 +58,7 @@ class Uploader extends React.Component {
 
                         this.status = "getting user document";
 
-                        const res = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId));
+                        const res = await rp.get(Utils.prepend(RouteStore.getUserDocumentId));
                         if (!res) {
                             throw new Error("No user id returned");
                         }
@@ -104,6 +105,8 @@ class Uploader extends React.Component {
 }
 
 
+DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload");
+
 ReactDOM.render((
     <Uploader />
 ),
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index e796ccb43..1c52a3f11 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -12,6 +12,7 @@ import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { RouteStore } from "../../RouteStore";
+import { Utils } from "../../../Utils";
 
 export class CurrentUserUtils {
     private static curr_email: string;
@@ -74,7 +75,7 @@ export class CurrentUserUtils {
     }
 
     public static loadCurrentUser() {
-        return rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => {
+        return rp.get(Utils.prepend(RouteStore.getCurrUser)).then(response => {
             if (response) {
                 const result: { id: string, email: string } = JSON.parse(response);
                 return result;
@@ -87,7 +88,7 @@ export class CurrentUserUtils {
     public static async loadUserDocument({ id, email }: { id: string, email: string }) {
         this.curr_id = id;
         this.curr_email = email;
-        await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => {
+        await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => {
             if (id) {
                 return DocServer.GetRefField(id).then(async field => {
                     if (field instanceof Doc) {
diff --git a/src/server/index.ts b/src/server/index.ts
index b3859aa4f..6b4e59bfc 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -417,7 +417,7 @@ app.get(RouteStore.reset, getReset);
 app.post(RouteStore.reset, postReset);
 
 app.use(RouteStore.corsProxy, (req, res) =>
-    req.pipe(request(req.url.substring(1))).pipe(res));
+    req.pipe(request(decodeURIComponent(req.url.substring(1)))).pipe(res));
 
 app.get(RouteStore.delete, (req, res) => {
     if (release) {
-- 
cgit v1.2.3-70-g09d2


From 828fdd80ab8af26f6e2ef521e05a71e7e500bf8c Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Wed, 24 Jul 2019 17:18:15 -0400
Subject: view specsss

---
 package.json                                       |   1 +
 src/client/views/EditableView.tsx                  |   2 +-
 src/client/views/MainView.tsx                      |   4 +-
 .../views/collections/CollectionStackingView.tsx   |   9 +-
 .../CollectionStackingViewFieldColumn.tsx          |  44 +++-
 src/client/views/collections/CollectionSubView.tsx |  15 +-
 .../views/collections/CollectionViewChromes.scss   | 113 ++++++++-
 .../views/collections/CollectionViewChromes.tsx    | 261 ++++++++++++++++-----
 src/client/views/collections/KeyRestrictionRow.tsx |  45 ++++
 9 files changed, 428 insertions(+), 66 deletions(-)
 create mode 100644 src/client/views/collections/KeyRestrictionRow.tsx

(limited to 'src/client/views/collections/CollectionSubView.tsx')

diff --git a/package.json b/package.json
index 4a15cbb2f..37052fde3 100644
--- a/package.json
+++ b/package.json
@@ -136,6 +136,7 @@
         "image-size": "^0.7.4",
         "imagesloaded": "^4.1.4",
         "jquery-awesome-cursor": "^0.3.1",
+        "js-datepicker": "^4.6.6",
         "jsonwebtoken": "^8.5.0",
         "jsx-to-string": "^1.4.0",
         "lodash": "^4.17.11",
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index a5bb40243..bd00d47b9 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -116,7 +116,7 @@ export class EditableView extends React.Component<EditableProps> {
             return (
                 <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
                     style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
-                    onClick={this.onClick} >
+                    onClick={this.onClick}>
                     <span style={{ fontStyle: this.props.fontStyle }}>{this.props.contents}</span>
                 </div>
             );
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 94a4835a1..69602deed 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
 import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat } from '@fortawesome/free-solid-svg-icons';
+import { faArrowDown, faCaretUp, faLongArrowAltRight, faCloudUploadAlt, faArrowUp, faClone, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx';
 import { observer } from 'mobx-react';
@@ -127,7 +127,9 @@ export class MainView extends React.Component {
         library.add(faCut);
         library.add(faCommentAlt);
         library.add(faThumbtack);
+        library.add(faLongArrowAltRight);
         library.add(faCheck);
+        library.add(faCaretUp);
         library.add(faArrowDown);
         library.add(faArrowUp);
         library.add(faCloudUploadAlt);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e4e6f92e3..a78a47ffb 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -249,6 +249,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return false;
     }
 
+    sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => {
+        let descending = BoolCast(this.props.Document.stackingHeadersSortDescending);
+        let firstEntry = descending ? b : a;
+        let secondEntry = descending ? a : b;
+        return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1;
+    }
+
     render() {
         let headings = Array.from(this.Sections.keys());
         let editableViewProps = {
@@ -264,7 +271,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())],
                     ["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)],
                     ["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */}
-                {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort((a, b) => a[0].toString() > b[0].toString() ? 1 : -1).
+                {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc).
                     map(section => this.section(section[0], section[1] as Doc[])) :
                     this.section(undefined, this.filteredChildren)}
                 {this.props.Document.sectionFilter ?
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index fe24c63c7..582adc418 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -16,6 +16,8 @@ import "./CollectionStackingView.scss";
 import { Docs } from "../../documents/Documents";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { CompileScript } from "../../util/Scripting";
 
 
 interface CSVFieldColumnProps {
@@ -35,6 +37,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     private _dropRef: HTMLDivElement | null = null;
     private dropDisposer?: DragManager.DragDropDisposer;
+    private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
 
     @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
 
@@ -182,6 +185,43 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         }
     }
 
+    startDrag = (e: PointerEvent) => {
+        let alias = Doc.MakeAlias(this.props.parent.props.Document);
+        let key = StrCast(this.props.parent.props.Document.sectionFilter);
+        let value = this.getValue(this._heading);
+        value = typeof value === "string" ? `"${value}"` : value;
+        let script = `return doc.${key} === ${value}`;
+        let compiled = CompileScript(script, { params: { doc: Doc.name } });
+        if (compiled.compiled) {
+            let scriptField = new ScriptField(compiled);
+            alias.viewSpecScript = scriptField;
+            let dragData = new DragManager.DocumentDragData([alias], [alias.proto]);
+            DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY);
+        }
+
+        e.stopPropagation();
+        document.removeEventListener("pointermove", this.startDrag);
+        document.removeEventListener("pointerup", this.pointerUp);
+    }
+
+    pointerUp = (e: PointerEvent) => {
+        e.stopPropagation();
+        e.preventDefault();
+
+        document.removeEventListener("pointermove", this.startDrag);
+        document.removeEventListener("pointerup", this.pointerUp);
+    }
+
+    headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+        e.stopPropagation();
+        e.preventDefault();
+
+        document.removeEventListener("pointermove", this.startDrag);
+        document.addEventListener("pointermove", this.startDrag);
+        document.removeEventListener("pointerup", this.pointerUp);
+        document.addEventListener("pointerup", this.pointerUp);
+    }
+
     render() {
         let cols = this.props.cols();
         let key = StrCast(this.props.parent.props.Document.sectionFilter);
@@ -199,11 +239,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             oneLine: true
         }
         let headingView = this.props.headingObject ?
-            <div key={heading} className="collectionStackingView-sectionHeader"
+            <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef}
                 style={{ width: (style.columnWidth) / (uniqueHeadings.length + 1) }}>
                 {/* the default bucket (no key value) has a tooltip that describes what it is.
                     Further, it does not have a color and cannot be deleted. */}
-                <div className="collectionStackingView-sectionHeader-subCont"
+                <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown}
                     title={evContents === `No ${key} value` ?
                         `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
                     style={{
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2ddefb3c0..24c8adb35 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -21,6 +21,8 @@ import { CollectionView } from "./CollectionView";
 import React = require("react");
 import { MainView } from "../MainView";
 import { Utils } from "../../../Utils";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { CompileScript } from "../../util/Scripting";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -54,7 +56,18 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             let self = this;
             //TODO tfs: This might not be what we want?
             //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
-            return DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]);
+            let docs = DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]);
+            let viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
+            if (viewSpecScript) {
+                let script = viewSpecScript.script;
+                docs = docs.filter(d => {
+                    let res = script.run({ doc: d });
+                    if (res.success) {
+                        return res.result;
+                    }
+                });
+            }
+            return docs;
         }
         get childDocList() {
             //TODO tfs: This might not be what we want?
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index e5cb1b546..3103cd309 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -1,12 +1,120 @@
 @import "../globalCssVariables";
+@import '~js-datepicker/dist/datepicker.min.css';
+
+.collectionViewBaseChrome {
+    display: flex;
+
+    .collectionViewBaseChrome-viewPicker {
+        font-size: 75%;
+        text-transform: uppercase;
+        letter-spacing: 2px;
+        background: rgb(238, 238, 238);
+        color: grey;
+        outline-color: black;
+        border: none;
+        padding: 12px 10px 11px 10px;
+    }
+
+    .collectionViewBaseChrome-viewPicker:active {
+        outline-color: black;
+    }
+
+    .collectionViewBaseChrome-collapse {
+        margin-right: 10px;
+        transition: all .5s;
+    }
+
+    .collectionViewBaseChrome-viewSpecs {
+        margin-left: 10px;
+        display: grid;
+
+        .collectionViewBaseChrome-viewSpecsInput {
+            padding: 12px 10px 11px 10px;
+            border: 0px;
+            color: grey;
+            text-align: center;
+            text-transform: uppercase;
+            letter-spacing: 2px;
+            outline-color: black;
+            font-size: 75%;
+            background: rgb(238, 238, 238);
+            height: 100%;
+            width: 150px;
+        }
+
+        .collectionViewBaseChrome-viewSpecsMenu {
+            overflow: hidden;
+            transition: height .5s, display .5s;
+            position: absolute;
+            top: 60px;
+            z-index: 100;
+            display: flex;
+            flex-direction: column;
+            background: rgb(238, 238, 238);
+            box-shadow: grey 2px 2px 4px;
+
+            .qs-datepicker {
+                left: unset;
+                right: 0;
+            }
+
+            .collectionViewBaseChrome-viewSpecsMenu-row {
+                display: grid;
+                grid-template-columns: 150px 200px 150px;
+                margin-top: 10px;
+                margin-right: 10px;
+
+                .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
+                .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
+                .collectionViewBaseChrome-viewSpecsMenu-rowRight {
+                    font-size: 75%;
+                    letter-spacing: 2px;
+                    color: grey;
+                    margin-left: 10px;
+                    padding: 5px;
+                    border: none;
+                    outline-color: black;
+                }
+            }
+
+            .collectionViewBaseChrome-viewSpecsMenu-lastRow {
+                display: grid;
+                grid-template-columns: 1fr 1fr;
+                grid-gap: 10px;
+                margin: 10px;
+            }
+        }
+    }
+}
 
 .collectionStackingViewChrome {
     display: grid;
-    grid-template-columns: 1fr 1fr;
+    grid-template-columns: 1fr auto;
     padding-bottom: 10px;
     border-bottom: .5px solid lightgrey;
     margin: 10px;
 
+    .collectionStackingViewChrome-cont {
+        display: flex;
+        justify-content: space-between;
+    }
+
+    .collectionStackingViewChrome-sort {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        .collectionStackingViewChrome-sortIcon {
+            transition: transform .5s;
+            margin-left: 10px;
+        }
+    }
+
+    button:hover {
+        transform: scale(1);
+    }
+
+
     .collectionStackingViewChrome-sectionFilter-cont {
         justify-self: right;
         display: flex;
@@ -21,10 +129,9 @@
 
         .collectionStackingViewChrome-sectionFilter {
             color: white;
-            width: fit-content;
+            width: 100px;
             text-align: center;
             background: rgb(238, 238, 238);
-            height: 37px;
 
             .editable-view-input,
             input,
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 52fee26bf..2edac384d 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -3,55 +3,189 @@ import { CollectionView } from "./CollectionView";
 import "./CollectionViewChromes.scss";
 import { CollectionViewType } from "./CollectionBaseView";
 import { undoBatch } from "../../util/UndoManager";
-import { action, observable, runInAction, computed } from "mobx";
+import { action, observable, runInAction, computed, IObservable, IObservableValue } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { DocLike } from "../MetadataEntryMenu";
-const higflyout = require("@hig/flyout");
-export const Flyout = higflyout.default;
 import * as Autosuggest from 'react-autosuggest';
 import { EditableView } from "../EditableView";
-import { StrCast } from "../../../new_fields/Types";
+import { StrCast, NumCast, BoolCast, Cast } from "../../../new_fields/Types";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Utils } from "../../../Utils";
+import KeyRestrictionRow from "./KeyRestrictionRow";
+import { CompileScript } from "../../util/Scripting";
+import { ScriptField } from "../../../new_fields/ScriptField";
+const datepicker = require('js-datepicker');
 
 interface CollectionViewChromeProps {
     CollectionView: CollectionView;
 }
 
+let stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
+
+@observer
 class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+    @observable private _viewSpecsOpen: boolean = false;
+    @observable private _dateWithinValue: string = "";
+    @observable private _dateValue: Date = new Date();
+    @observable private _keyRestrictions: [JSX.Element, string][] = [];
+    @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); }
+
+    private _picker: any;
+    private _datePickerElGuid = Utils.GenerateGuid();
+
+    componentDidMount = () => {
+        this._picker = datepicker("#" + this._datePickerElGuid, {
+            disabler: (date: Date) => date > new Date(),
+            onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
+            dateSelected: new Date()
+        });
+
+        runInAction(() => {
+            this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[0][1] = value)} />, ""]);
+            this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={false} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]);
+        });
+    }
+
     @undoBatch
     viewChanged = (e: React.ChangeEvent) => {
         //@ts-ignore
-        switch (e.target.selectedOptions[0].value) {
-            case "freeform":
-                this.props.CollectionView.props.Document.viewType = CollectionViewType.Freeform;
-                break;
-            case "schema":
-                this.props.CollectionView.props.Document.viewType = CollectionViewType.Schema;
-                break;
-            case "treeview":
-                this.props.CollectionView.props.Document.viewType = CollectionViewType.Tree;
-                break;
-            case "stacking":
-                this.props.CollectionView.props.Document.viewType = CollectionViewType.Stacking;
-                break;
-            case "masonry":
-                this.props.CollectionView.props.Document.viewType = CollectionViewType.Masonry;
-                break;
-            default:
-                break;
+        this.props.CollectionView.props.Document.viewType = parseInt(e.target.selectedOptions[0].value);
+    }
+
+    @action
+    openViewSpecs = (e: React.SyntheticEvent) => {
+        this._viewSpecsOpen = true;
+
+        //@ts-ignore
+        if (!e.target.classList[0].startsWith("qs")) {
+            this.closeDatePicker();
+        }
+
+        e.stopPropagation();
+        document.removeEventListener("pointerdown", this.closeViewSpecs);
+        document.addEventListener("pointerdown", this.closeViewSpecs);
+    }
+
+    @action closeViewSpecs = () => { this._viewSpecsOpen = false; document.removeEventListener("pointerdown", this.closeViewSpecs); };
+
+    @action
+    openDatePicker = (e: React.PointerEvent) => {
+        this.openViewSpecs(e);
+        if (this._picker) {
+            this._picker.alwaysShow = true;
+            this._picker.show();
+            // TODO: calendar is offset when zoomed in/out
+            // this._picker.calendar.style.position = "absolute";
+            // let transform = this.props.CollectionView.props.ScreenToLocalTransform();
+            // let x = parseInt(this._picker.calendar.style.left) / transform.Scale;
+            // let y = parseInt(this._picker.calendar.style.top) / transform.Scale;
+            // this._picker.calendar.style.left = x;
+            // this._picker.calendar.style.top = y;
+
+            e.stopPropagation();
         }
     }
 
+    @action
+    addKeyRestriction = (e: React.MouseEvent) => {
+        let index = this._keyRestrictions.length;
+        this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[index][1] = value)} />, ""]);
+
+        this.openViewSpecs(e);
+    }
+
+    @action
+    applyFilter = (e: React.MouseEvent) => {
+        this.openViewSpecs(e);
+
+        let keyRestrictionScript = `${this._keyRestrictions.map(i => i[1])
+            .reduce((acc: string, value: string, i: number) => value ? `${acc} && ${value}` : acc)}`;
+        let yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
+        let monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
+        let weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0;
+        let dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7;
+        let lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset);
+        let upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1);
+        let dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
+        let fullScript = `return ${dateRestrictionScript} && ${keyRestrictionScript}`;
+        let compiled = CompileScript(fullScript, { params: { doc: Doc.name } });
+        if (compiled.compiled) {
+            this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled);
+        }
+    }
+
+    @action
+    closeDatePicker = () => {
+        if (this._picker) {
+            this._picker.alwaysShow = false;
+            this._picker.hide();
+        }
+        document.removeEventListener("pointerdown", this.closeDatePicker);
+    }
+
     render() {
         return (
             <div className="collectionViewBaseChrome">
-                <select onChange={this.viewChanged}>
-                    <option value="freeform" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Freeform}>Freeform View</option>
-                    <option value="schema" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Schema}>Schema View</option>
-                    <option value="treeview" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Tree}>Tree View</option>
-                    <option value="stacking" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Stacking}>Stacking View</option>
-                    <option value="masonry" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Masonry}>Masonry View</option>
+                <button className="collectionViewBaseChrome-collapse" title="Collapse collection chrome">
+                    <FontAwesomeIcon icon="caret-up" size="2x" />
+                </button>
+                <select
+                    className="collectionViewBaseChrome-viewPicker"
+                    onPointerDown={stopPropagation}
+                    onChange={this.viewChanged}
+                    value={NumCast(this.props.CollectionView.props.Document.viewType)}>
+                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
+                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
+                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
+                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
+                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View></option>
                 </select>
+                <div className="collectionViewBaseChrome-viewSpecs">
+                    <input className="collectionViewBaseChrome-viewSpecsInput"
+                        placeholder="Filter Documents"
+                        value={this.filterValue ? this.filterValue.script.originalScript : ""}
+                        onPointerDown={this.openViewSpecs} />
+                    <div className="collectionViewBaseChrome-viewSpecsMenu"
+                        onPointerDown={this.openViewSpecs}
+                        style={{
+                            height: this._viewSpecsOpen ? "fit-content" : "0px",
+                            overflow: this._viewSpecsOpen ? "initial" : "hidden"
+                        }}>
+                        {this._keyRestrictions.map(i => i[0])}
+                        <div className="collectionViewBaseChrome-viewSpecsMenu-row">
+                            <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
+                                CREATED WITHIN:
+                            </div>
+                            <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+                                style={{ textTransform: "uppercase", textAlign: "center" }}
+                                value={this._dateWithinValue}
+                                onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}>
+                                <option value="1d">1 day of</option>
+                                <option value="3d">3 days of</option>
+                                <option value="1w">1 week of</option>
+                                <option value="2w">2 weeks of</option>
+                                <option value="1m">1 month of</option>
+                                <option value="2m">2 months of</option>
+                                <option value="6m">6 months of</option>
+                                <option value="1y">1 year of</option>
+                            </select>
+                            <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+                                id={this._datePickerElGuid}
+                                value={this._dateValue.toLocaleDateString()}
+                                onPointerDown={this.openDatePicker}
+                                placeholder="Value" />
+                        </div>
+                        <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
+                            <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}>
+                                ADD KEY RESTRICTION
+                            </button>
+                            <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}>
+                                APPLY FILTER
+                            </button>
+                        </div>
+                    </div>
+                </div>
             </div>
         )
     }
@@ -62,6 +196,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
     @observable private _currentKey: string = "";
     @observable private suggestions: string[] = [];
 
+    @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); }
     @computed get sectionFilter() { return StrCast(this.props.CollectionView.props.Document.sectionFilter); }
 
     getKeySuggestions = async (value: string): Promise<string[]> => {
@@ -109,41 +244,53 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
         return true;
     }
 
+    @action toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; }
     @action resetValue = () => { this._currentKey = this.sectionFilter; };
 
     render() {
         return (
             <div className="collectionStackingViewChrome">
                 <CollectionViewBaseChrome CollectionView={this.props.CollectionView} />
-                <div className="collectionStackingViewChrome-sectionFilter-cont">
-                    <div className="collectionStackingViewChrome-sectionFilter-label">
-                        Group items by:
-                    </div>
-                    <div className="collectionStackingViewChrome-sectionFilter">
-                        <EditableView
-                            GetValue={() => this.sectionFilter}
-                            autosuggestProps={
-                                {
-                                    resetValue: this.resetValue,
-                                    value: this._currentKey,
-                                    onChange: this.onKeyChange,
-                                    autosuggestProps: {
-                                        inputProps:
-                                        {
-                                            value: this._currentKey,
-                                            onChange: this.onKeyChange
-                                        },
-                                        getSuggestionValue: this.getSuggestionValue,
-                                        suggestions: this.suggestions,
-                                        alwaysRenderSuggestions: true,
-                                        renderSuggestion: this.renderSuggestion,
-                                        onSuggestionsFetchRequested: this.onSuggestionFetch,
-                                        onSuggestionsClearRequested: this.onSuggestionClear
-                                    }
-                                }}
-                            SetValue={this.setValue}
-                            contents={this.sectionFilter ? this.sectionFilter : "N/A"}
-                        />
+                <div className="collectionStackingViewChrome-cont">
+                    <button className="collectionStackingViewChrome-sort" onClick={this.toggleSort}>
+                        <div className="collectionStackingViewChrome-sortLabel">
+                            Sort
+                        </div>
+                        <div className="collectionStackingViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+                            <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+                        </div>
+                    </button>
+                    <div className="collectionStackingViewChrome-sectionFilter-cont">
+                        <div className="collectionStackingViewChrome-sectionFilter-label">
+                            Group items by:
+                        </div>
+                        <div className="collectionStackingViewChrome-sectionFilter">
+                            <EditableView
+                                GetValue={() => this.sectionFilter}
+                                autosuggestProps={
+                                    {
+                                        resetValue: this.resetValue,
+                                        value: this._currentKey,
+                                        onChange: this.onKeyChange,
+                                        autosuggestProps: {
+                                            inputProps:
+                                            {
+                                                value: this._currentKey,
+                                                onChange: this.onKeyChange
+                                            },
+                                            getSuggestionValue: this.getSuggestionValue,
+                                            suggestions: this.suggestions,
+                                            alwaysRenderSuggestions: true,
+                                            renderSuggestion: this.renderSuggestion,
+                                            onSuggestionsFetchRequested: this.onSuggestionFetch,
+                                            onSuggestionsClearRequested: this.onSuggestionClear
+                                        }
+                                    }}
+                                oneLine
+                                SetValue={this.setValue}
+                                contents={this.sectionFilter ? this.sectionFilter : "N/A"}
+                            />
+                        </div>
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
new file mode 100644
index 000000000..8051a8359
--- /dev/null
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -0,0 +1,45 @@
+import * as React from "react";
+import { observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
+
+interface IKeyRestrictionProps {
+    contains: boolean;
+    script: (value: string) => void;
+}
+
+@observer
+export default class KeyRestrictionRow extends React.Component<IKeyRestrictionProps> {
+    @observable private _key = "";
+    @observable private _value = "";
+    @observable private _contains = this.props.contains;
+
+    render() {
+        if (this._key && this._value) {
+            let parsedValue: string | number = `"${this._value}"`;
+            let parsed = parseInt(this._value);
+            if (!isNaN(parsed)) {
+                parsedValue = parsed;
+            }
+            let scriptText = `(doc.${this._key} ${this._contains ? "===" : "!=="} ${parsedValue})`;
+            this.props.script(scriptText);
+        }
+        return (
+            <div className="collectionViewBaseChrome-viewSpecsMenu-row">
+                <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"
+                    value={this._key}
+                    onChange={(e) => runInAction(() => this._key = e.target.value)}
+                    placeholder="KEY" />
+                <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+                    style={{ background: PastelSchemaPalette.get(this._contains ? "green" : "red") }}
+                    onClick={() => runInAction(() => this._contains = !this._contains)}>
+                    {this._contains ? "CONTAINS" : "DOES NOT CONTAIN"}
+                </button>
+                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+                    value={this._value}
+                    onChange={(e) => runInAction(() => this._value = e.target.value)}
+                    placeholder="VALUE" />
+            </div>
+        )
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From ede0719d96cc2551490398caf3e1f41fe7992931 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Wed, 24 Jul 2019 22:27:37 -0400
Subject: view specs almost entirely done, refactored some chrome impl

---
 .../views/collections/CollectionStackingView.scss  |   1 +
 .../views/collections/CollectionStackingView.tsx   |   6 +-
 src/client/views/collections/CollectionSubView.tsx |   1 +
 src/client/views/collections/CollectionView.tsx    |  42 ++--
 .../views/collections/CollectionViewChromes.scss   | 156 +++++++--------
 .../views/collections/CollectionViewChromes.tsx    | 214 ++++++++++++---------
 6 files changed, 227 insertions(+), 193 deletions(-)

(limited to 'src/client/views/collections/CollectionSubView.tsx')

diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index e0ced8af4..f43340967 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -7,6 +7,7 @@
     display: flex;
     overflow-y: auto;
     flex-wrap: wrap;
+    transition: top .5s;
 
     .collectionStackingView-docView-container {
         width: 45%;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a78a47ffb..31ec80d76 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -8,7 +8,7 @@ import { BoolCast, NumCast, Cast, StrCast, FieldValue } from "../../../new_field
 import { emptyFunction, Utils } from "../../../Utils";
 import { CollectionSchemaPreview } from "./CollectionSchemaView";
 import "./CollectionStackingView.scss";
-import { CollectionSubView } from "./CollectionSubView";
+import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
 import { undoBatch } from "../../util/UndoManager";
 import { DragManager } from "../../util/DragManager";
 import { DocumentType } from "../../documents/Documents";
@@ -19,6 +19,7 @@ import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { List } from "../../../new_fields/List";
 import { EditableView } from "../EditableView";
+import { CollectionViewProps } from "./CollectionBaseView";
 
 let valuesCreated = 1;
 
@@ -32,6 +33,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _columnStart: number = 0;
     @observable private cursor: CursorProperty = "grab";
     get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
+    get chromeCollapsed() { return this.props.chromeCollapsed; }
     @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
     @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); }
     @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
@@ -265,7 +267,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         }
         // let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
         return (
-            <div className="collectionStackingView"
+            <div className="collectionStackingView" style={{ top: this.chromeCollapsed ? 0 : 100 }}
                 ref={this.createRef} onDrop={this.onDrop.bind(this)} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
                 {/* {sectionFilter as boolean ? [
                     ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())],
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 24c8adb35..98447e824 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -30,6 +30,7 @@ export interface CollectionViewProps extends FieldViewProps {
     moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
+    chromeCollapsed: boolean;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ba9780c24..e2f652908 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,7 +17,8 @@ import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import { StrCast, PromiseValue } from '../../../new_fields/Types';
 import { DocumentType } from '../../documents/Documents';
-import { CollectionStackingViewChrome } from './CollectionViewChromes';
+import { CollectionStackingViewChrome, CollectionViewBaseChrome } from './CollectionViewChromes';
+import { observable } from 'mobx';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -32,40 +33,39 @@ library.add(faImage);
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
+    @observable private _collapsed = false;
+
     public static LayoutString(fieldStr: string = "data", fieldExt: string = "") { return FieldView.LayoutString(CollectionView, fieldStr, fieldExt); }
 
     private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
         switch (this.isAnnotationOverlay ? CollectionViewType.Freeform : type) {
-            case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} CollectionView={this} />);
-            case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} CollectionView={this} />);
-            case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} CollectionView={this} />);
-            case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} CollectionView={this} />); }
-            case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} CollectionView={this} />); }
+            case CollectionViewType.Schema: return (<CollectionSchemaView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
+            case CollectionViewType.Docking: return (<CollectionDockingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
+            case CollectionViewType.Tree: return (<CollectionTreeView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
+            case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
+            case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
             case CollectionViewType.Freeform:
             default:
-                return (<CollectionFreeFormView key="collview" {...props} CollectionView={this} />);
+                return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
         }
         return (null);
     }
 
-    private Chrome = (type: CollectionViewType) => {
-        if (this.isAnnotationOverlay || this.props.Document === CurrentUserUtils.UserDocument.sidebar) {
-            return (null);
-        }
-
-        switch (type) {
-            case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this} />);
-            default:
-                return null;
-        }
+    private collapse = (value: boolean) => {
+        this._collapsed = value;
     }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        return [
-            this.Chrome(type),
-            this.SubViewHelper(type, renderProps)
-        ]
+        if (this.isAnnotationOverlay || this.props.Document === CurrentUserUtils.UserDocument.sidebar) {
+            return [(null), this.SubViewHelper(type, renderProps)];
+        }
+        else {
+            return [
+                (<CollectionViewBaseChrome CollectionView={this} type={type} collapse={this.collapse} />),
+                this.SubViewHelper(type, renderProps)
+            ];
+        }
     }
 
     get isAnnotationOverlay() { return this.props.fieldExt ? true : false; }
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 3103cd309..d37228bde 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -1,98 +1,104 @@
 @import "../globalCssVariables";
 @import '~js-datepicker/dist/datepicker.min.css';
 
-.collectionViewBaseChrome {
-    display: flex;
-
-    .collectionViewBaseChrome-viewPicker {
-        font-size: 75%;
-        text-transform: uppercase;
-        letter-spacing: 2px;
-        background: rgb(238, 238, 238);
-        color: grey;
-        outline-color: black;
-        border: none;
-        padding: 12px 10px 11px 10px;
-    }
-
-    .collectionViewBaseChrome-viewPicker:active {
-        outline-color: black;
-    }
-
-    .collectionViewBaseChrome-collapse {
-        margin-right: 10px;
-        transition: all .5s;
-    }
+.collectionViewChrome {
+    display: grid;
+    grid-template-columns: 1fr auto;
+    padding-bottom: 10px;
+    border-bottom: .5px solid lightgrey;
+    margin: 10px;
+    position: relative;
+    z-index: 9001;
+    transition: top .5s;
 
-    .collectionViewBaseChrome-viewSpecs {
-        margin-left: 10px;
-        display: grid;
+    .collectionViewBaseChrome {
+        display: flex;
 
-        .collectionViewBaseChrome-viewSpecsInput {
-            padding: 12px 10px 11px 10px;
-            border: 0px;
-            color: grey;
-            text-align: center;
+        .collectionViewBaseChrome-viewPicker {
+            font-size: 75%;
             text-transform: uppercase;
             letter-spacing: 2px;
-            outline-color: black;
-            font-size: 75%;
             background: rgb(238, 238, 238);
-            height: 100%;
-            width: 150px;
+            color: grey;
+            outline-color: black;
+            border: none;
+            padding: 12px 10px 11px 10px;
+            margin-left: 50px;
         }
 
-        .collectionViewBaseChrome-viewSpecsMenu {
-            overflow: hidden;
-            transition: height .5s, display .5s;
+        .collectionViewBaseChrome-viewPicker:active {
+            outline-color: black;
+        }
+
+        .collectionViewBaseChrome-collapse {
+            transition: all .5s;
             position: absolute;
-            top: 60px;
-            z-index: 100;
-            display: flex;
-            flex-direction: column;
-            background: rgb(238, 238, 238);
-            box-shadow: grey 2px 2px 4px;
+            width: 40px;
+        }
+
+        .collectionViewBaseChrome-viewSpecs {
+            margin-left: 10px;
+            display: grid;
 
-            .qs-datepicker {
-                left: unset;
-                right: 0;
+            .collectionViewBaseChrome-viewSpecsInput {
+                padding: 12px 10px 11px 10px;
+                border: 0px;
+                color: grey;
+                text-align: center;
+                text-transform: uppercase;
+                letter-spacing: 2px;
+                outline-color: black;
+                font-size: 75%;
+                background: rgb(238, 238, 238);
+                height: 100%;
+                width: 150px;
             }
 
-            .collectionViewBaseChrome-viewSpecsMenu-row {
-                display: grid;
-                grid-template-columns: 150px 200px 150px;
-                margin-top: 10px;
-                margin-right: 10px;
-
-                .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
-                .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
-                .collectionViewBaseChrome-viewSpecsMenu-rowRight {
-                    font-size: 75%;
-                    letter-spacing: 2px;
-                    color: grey;
-                    margin-left: 10px;
-                    padding: 5px;
-                    border: none;
-                    outline-color: black;
+            .collectionViewBaseChrome-viewSpecsMenu {
+                overflow: hidden;
+                transition: height .5s, display .5s;
+                position: absolute;
+                top: 60px;
+                z-index: 100;
+                display: flex;
+                flex-direction: column;
+                background: rgb(238, 238, 238);
+                box-shadow: grey 2px 2px 4px;
+
+                .qs-datepicker {
+                    left: unset;
+                    right: 0;
+                }
+
+                .collectionViewBaseChrome-viewSpecsMenu-row {
+                    display: grid;
+                    grid-template-columns: 150px 200px 150px;
+                    margin-top: 10px;
+                    margin-right: 10px;
+
+                    .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
+                    .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
+                    .collectionViewBaseChrome-viewSpecsMenu-rowRight {
+                        font-size: 75%;
+                        letter-spacing: 2px;
+                        color: grey;
+                        margin-left: 10px;
+                        padding: 5px;
+                        border: none;
+                        outline-color: black;
+                    }
                 }
-            }
 
-            .collectionViewBaseChrome-viewSpecsMenu-lastRow {
-                display: grid;
-                grid-template-columns: 1fr 1fr;
-                grid-gap: 10px;
-                margin: 10px;
+                .collectionViewBaseChrome-viewSpecsMenu-lastRow {
+                    display: grid;
+                    grid-template-columns: 1fr 1fr;
+                    grid-gap: 10px;
+                    margin: 10px;
+                }
             }
         }
     }
-}
 
-.collectionStackingViewChrome {
-    display: grid;
-    grid-template-columns: 1fr auto;
-    padding-bottom: 10px;
-    border-bottom: .5px solid lightgrey;
-    margin: 10px;
 
     .collectionStackingViewChrome-cont {
         display: flex;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 2edac384d..24d13d5cb 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -19,16 +19,19 @@ const datepicker = require('js-datepicker');
 
 interface CollectionViewChromeProps {
     CollectionView: CollectionView;
+    type: CollectionViewType;
+    collapse?: (value: boolean) => any;
 }
 
 let stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
 
 @observer
-class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
     @observable private _viewSpecsOpen: boolean = false;
     @observable private _dateWithinValue: string = "";
     @observable private _dateValue: Date = new Date();
     @observable private _keyRestrictions: [JSX.Element, string][] = [];
+    @observable private _collapsed: boolean = false;
     @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); }
 
     private _picker: any;
@@ -124,68 +127,92 @@ class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps
         document.removeEventListener("pointerdown", this.closeDatePicker);
     }
 
+    @action
+    toggleCollapse = () => {
+        this._collapsed = !this._collapsed;
+        this.props.collapse(this._collapsed);
+    }
+
+    subChrome = () => {
+
+        switch (this.props.type) {
+            case CollectionViewType.Stacking: return (
+                <CollectionStackingViewChrome
+                    key="collchrome"
+                    CollectionView={this.props.CollectionView}
+                    type={this.props.type} />);
+            default:
+                return null;
+        }
+    }
+
     render() {
         return (
-            <div className="collectionViewBaseChrome">
-                <button className="collectionViewBaseChrome-collapse" title="Collapse collection chrome">
-                    <FontAwesomeIcon icon="caret-up" size="2x" />
-                </button>
-                <select
-                    className="collectionViewBaseChrome-viewPicker"
-                    onPointerDown={stopPropagation}
-                    onChange={this.viewChanged}
-                    value={NumCast(this.props.CollectionView.props.Document.viewType)}>
-                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
-                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
-                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
-                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
-                    <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View></option>
-                </select>
-                <div className="collectionViewBaseChrome-viewSpecs">
-                    <input className="collectionViewBaseChrome-viewSpecsInput"
-                        placeholder="Filter Documents"
-                        value={this.filterValue ? this.filterValue.script.originalScript : ""}
-                        onPointerDown={this.openViewSpecs} />
-                    <div className="collectionViewBaseChrome-viewSpecsMenu"
-                        onPointerDown={this.openViewSpecs}
-                        style={{
-                            height: this._viewSpecsOpen ? "fit-content" : "0px",
-                            overflow: this._viewSpecsOpen ? "initial" : "hidden"
-                        }}>
-                        {this._keyRestrictions.map(i => i[0])}
-                        <div className="collectionViewBaseChrome-viewSpecsMenu-row">
-                            <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
-                                CREATED WITHIN:
+            <div className="collectionViewChrome" style={{ top: this._collapsed ? -100 : 0 }}>
+                <div className="collectionViewBaseChrome">
+                    <button className="collectionViewBaseChrome-collapse"
+                        style={{ top: this._collapsed ? 80 : 0, transform: `rotate(${this._collapsed ? 180 : 0}deg)` }}
+                        title="Collapse collection chrome" onClick={this.toggleCollapse}>
+                        <FontAwesomeIcon icon="caret-up" size="2x" />
+                    </button>
+                    <select
+                        className="collectionViewBaseChrome-viewPicker"
+                        onPointerDown={stopPropagation}
+                        onChange={this.viewChanged}
+                        value={NumCast(this.props.CollectionView.props.Document.viewType)}>
+                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option>
+                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option>
+                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
+                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
+                        <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View></option>
+                    </select>
+                    <div className="collectionViewBaseChrome-viewSpecs">
+                        <input className="collectionViewBaseChrome-viewSpecsInput"
+                            placeholder="Filter Documents"
+                            value={this.filterValue ? this.filterValue.script.originalScript : ""}
+                            onPointerDown={this.openViewSpecs} />
+                        <div className="collectionViewBaseChrome-viewSpecsMenu"
+                            onPointerDown={this.openViewSpecs}
+                            style={{
+                                height: this._viewSpecsOpen ? "fit-content" : "0px",
+                                overflow: this._viewSpecsOpen ? "initial" : "hidden"
+                            }}>
+                            {this._keyRestrictions.map(i => i[0])}
+                            <div className="collectionViewBaseChrome-viewSpecsMenu-row">
+                                <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
+                                    CREATED WITHIN:
                             </div>
-                            <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
-                                style={{ textTransform: "uppercase", textAlign: "center" }}
-                                value={this._dateWithinValue}
-                                onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}>
-                                <option value="1d">1 day of</option>
-                                <option value="3d">3 days of</option>
-                                <option value="1w">1 week of</option>
-                                <option value="2w">2 weeks of</option>
-                                <option value="1m">1 month of</option>
-                                <option value="2m">2 months of</option>
-                                <option value="6m">6 months of</option>
-                                <option value="1y">1 year of</option>
-                            </select>
-                            <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
-                                id={this._datePickerElGuid}
-                                value={this._dateValue.toLocaleDateString()}
-                                onPointerDown={this.openDatePicker}
-                                placeholder="Value" />
-                        </div>
-                        <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
-                            <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}>
-                                ADD KEY RESTRICTION
+                                <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+                                    style={{ textTransform: "uppercase", textAlign: "center" }}
+                                    value={this._dateWithinValue}
+                                    onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}>
+                                    <option value="1d">1 day of</option>
+                                    <option value="3d">3 days of</option>
+                                    <option value="1w">1 week of</option>
+                                    <option value="2w">2 weeks of</option>
+                                    <option value="1m">1 month of</option>
+                                    <option value="2m">2 months of</option>
+                                    <option value="6m">6 months of</option>
+                                    <option value="1y">1 year of</option>
+                                </select>
+                                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+                                    id={this._datePickerElGuid}
+                                    value={this._dateValue.toLocaleDateString()}
+                                    onPointerDown={this.openDatePicker}
+                                    placeholder="Value" />
+                            </div>
+                            <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
+                                <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}>
+                                    ADD KEY RESTRICTION
                             </button>
-                            <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}>
-                                APPLY FILTER
+                                <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}>
+                                    APPLY FILTER
                             </button>
+                            </div>
                         </div>
                     </div>
                 </div>
+                {this.subChrome()}
             </div>
         )
     }
@@ -249,48 +276,45 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
 
     render() {
         return (
-            <div className="collectionStackingViewChrome">
-                <CollectionViewBaseChrome CollectionView={this.props.CollectionView} />
-                <div className="collectionStackingViewChrome-cont">
-                    <button className="collectionStackingViewChrome-sort" onClick={this.toggleSort}>
-                        <div className="collectionStackingViewChrome-sortLabel">
-                            Sort
-                        </div>
-                        <div className="collectionStackingViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
-                            <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+            <div className="collectionStackingViewChrome-cont">
+                <button className="collectionStackingViewChrome-sort" onClick={this.toggleSort}>
+                    <div className="collectionStackingViewChrome-sortLabel">
+                        Sort
                         </div>
-                    </button>
-                    <div className="collectionStackingViewChrome-sectionFilter-cont">
-                        <div className="collectionStackingViewChrome-sectionFilter-label">
-                            Group items by:
-                        </div>
-                        <div className="collectionStackingViewChrome-sectionFilter">
-                            <EditableView
-                                GetValue={() => this.sectionFilter}
-                                autosuggestProps={
-                                    {
-                                        resetValue: this.resetValue,
-                                        value: this._currentKey,
-                                        onChange: this.onKeyChange,
-                                        autosuggestProps: {
-                                            inputProps:
-                                            {
-                                                value: this._currentKey,
-                                                onChange: this.onKeyChange
-                                            },
-                                            getSuggestionValue: this.getSuggestionValue,
-                                            suggestions: this.suggestions,
-                                            alwaysRenderSuggestions: true,
-                                            renderSuggestion: this.renderSuggestion,
-                                            onSuggestionsFetchRequested: this.onSuggestionFetch,
-                                            onSuggestionsClearRequested: this.onSuggestionClear
-                                        }
-                                    }}
-                                oneLine
-                                SetValue={this.setValue}
-                                contents={this.sectionFilter ? this.sectionFilter : "N/A"}
-                            />
+                    <div className="collectionStackingViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+                        <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+                    </div>
+                </button>
+                <div className="collectionStackingViewChrome-sectionFilter-cont">
+                    <div className="collectionStackingViewChrome-sectionFilter-label">
+                        Group items by:
                         </div>
+                    <div className="collectionStackingViewChrome-sectionFilter">
+                        <EditableView
+                            GetValue={() => this.sectionFilter}
+                            autosuggestProps={
+                                {
+                                    resetValue: this.resetValue,
+                                    value: this._currentKey,
+                                    onChange: this.onKeyChange,
+                                    autosuggestProps: {
+                                        inputProps:
+                                        {
+                                            value: this._currentKey,
+                                            onChange: this.onKeyChange
+                                        },
+                                        getSuggestionValue: this.getSuggestionValue,
+                                        suggestions: this.suggestions,
+                                        alwaysRenderSuggestions: true,
+                                        renderSuggestion: this.renderSuggestion,
+                                        onSuggestionsFetchRequested: this.onSuggestionFetch,
+                                        onSuggestionsClearRequested: this.onSuggestionClear
+                                    }
+                                }}
+                            oneLine
+                            SetValue={this.setValue}
+                            contents={this.sectionFilter ? this.sectionFilter : "N/A"}
+                        />
                     </div>
                 </div>
             </div>
-- 
cgit v1.2.3-70-g09d2


From 7d468c19c28a48314ea993d84e5995364e88fa86 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 26 Jul 2019 02:21:09 -0400
Subject: added a MakeBackground and FitToBox menu items.   fixed problems with
 fitToBox and frozen dimensions.  fixed issue with drop() on free forms.

---
 .../views/collections/CollectionBaseView.scss      |  2 +-
 .../views/collections/CollectionBaseView.tsx       |  6 ++--
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 32 +++++++++++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/DocumentView.scss           |  1 -
 src/client/views/nodes/DocumentView.tsx            |  7 +++++
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 8 files changed, 37 insertions(+), 17 deletions(-)

(limited to 'src/client/views/collections/CollectionSubView.tsx')

diff --git a/src/client/views/collections/CollectionBaseView.scss b/src/client/views/collections/CollectionBaseView.scss
index 34bcb705e..583e6f6ca 100644
--- a/src/client/views/collections/CollectionBaseView.scss
+++ b/src/client/views/collections/CollectionBaseView.scss
@@ -6,7 +6,7 @@
     border-radius: 0 0 $border-radius $border-radius;
     box-sizing: border-box;
     border-radius: inherit;
-    pointer-events: all;
     width:100%;
     height:100%;
+    overflow: auto;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index c39caec24..67112ae7c 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -126,7 +126,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         let self = this;
         let targetDataDoc = this.props.Document;
         if (Doc.AreProtosEqual(targetDataDoc, targetCollection)) {
-            //if (Doc.AreProtosEqual(this.extensionDoc, targetCollection)) {
             return true;
         }
         if (this.removeDocument(doc)) {
@@ -146,7 +145,10 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         const viewtype = this.collectionViewType;
         return (
             <div id="collectionBaseView"
-                style={{ overflow: "auto", boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}` }}
+                style={{
+                    pointerEvents: this.props.Document.isBackground ? "none" : "all",
+                    boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
+                }}
                 className={this.props.className || "collectionView-cont"}
                 onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2ddefb3c0..cbc9e61eb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -104,7 +104,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 } else if (de.data.moveDocument) {
                     let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments;
                     added = movedDocs.reduce((added: boolean, d) =>
-                        de.data.moveDocument(d, /*this.props.DataDoc ? this.props.DataDoc :*/ this.props.Document, this.props.addDocument) || added, false);
+                        de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false);
                 } else {
                     added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6500b3273..9c4275388 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,9 +34,11 @@ import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faTable, faPaintBrush, faAsterisk } from "@fortawesome/free-solid-svg-icons";
+import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt } from "@fortawesome/free-solid-svg-icons";
+import { undo } from "prosemirror-history";
+import { number } from "prop-types";
 
-library.add(faEye, faTable, faPaintBrush);
+library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -58,24 +60,29 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private get _pheight() { return this.props.PanelHeight(); }
     private inkKey = "ink";
 
+    get prevScaling() {
+        return (this.props as any).ContentScaling && this.Document.nativeWidth && this.fitToBox ? (this.props as any).ContentScaling() : 1;
+    }
+
     @computed get contentBounds() {
-        let bounds = this.props.fitToBox && !NumCast(this.nativeWidth) ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
+        let bounds = (this.fitToBox) && !this.nativeWidth ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
         return {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
-            scale: bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1
+            scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.prevScaling
         };
     }
 
-    @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
-    @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+    @computed get fitToBox() { return this.props.fitToBox || this.props.Document.fitToBox; }
+    @computed get nativeWidth() { return this.fitToBox ? 0 : this.Document.nativeWidth || 0; }
+    @computed get nativeHeight() { return this.fitToBox ? 0 : this.Document.nativeHeight || 0; }
     public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt')
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
     private panX = () => this.contentBounds.panX;
     private panY = () => this.contentBounds.panY;
     private zoomScaling = () => this.contentBounds.scale;
-    private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0;  // shift so pan position is at center of window for non-overlay collections
-    private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections
+    private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 / this.prevScaling : 0;  // shift so pan position is at center of window for non-overlay collections
+    private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 / this.prevScaling : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
@@ -109,11 +116,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
+        let xf = this.getTransform();
         if (super.drop(e, de)) {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.droppedDocuments.length) {
-                    let dragDoc = de.data.droppedDocuments[0];
-                    let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+                    let [xp, yp] = xf.transformPoint(de.x, de.y);
                     let x = xp - de.data.xOffset;
                     let y = yp - de.data.yOffset;
                     let dropX = NumCast(de.data.droppedDocuments[0].x);
@@ -491,6 +498,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     onContextMenu = () => {
+        ContextMenu.Instance.addItem({
+            description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`,
+            event: undoBatch(async () => this.props.Document.fitToBox = !this.fitToBox),
+            icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt"
+        });
         ContextMenu.Instance.addItem({
             description: "Arrange contents in grid",
             icon: "table",
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b765517a2..d96e93aeb 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -365,7 +365,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     marqueeSelect() {
         let selRect = this.Bounds;
         let selection: Doc[] = [];
-        this.props.activeDocuments().map(doc => {
+        this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => {
             var z = NumCast(doc.zoomBasis, 1);
             var x = NumCast(doc.x);
             var y = NumCast(doc.y);
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 3a4b46b7e..7c72fb6e6 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -4,7 +4,6 @@
   position: inherit;
   top: 0;
   left:0;
-  pointer-events: all;
  // background: $light-color; //overflow: hidden;
   transform-origin: left top;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4a5e20533..9d2dd3121 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -514,6 +514,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         proto.ignoreAspect = !BoolCast(proto.ignoreAspect, false);
     }
+    @undoBatch
+    @action
+    makeBackground = (): void => {
+        this.props.Document.isBackground = true;
+    }
 
     @undoBatch
     @action
@@ -544,6 +549,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "edit" });
         cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
         cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Pos" : "Lock Pos", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
+        cm.addItem({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
         cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
         cm.addItem({
             description: "Make Portal", event: () => {
@@ -648,6 +654,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
+                    pointerEvents: this.props.Document.isBackground ? "none" : "all",
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ea167286b..a36885616 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -350,7 +350,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         if (field instanceof ImageField) paths = [this.choosePath(field.url)];
         paths.push(...altpaths);
         // }
-        let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
+        let interactive = InkingControl.Instance.selectedTool || this.props.Document.isBackground ? "" : "-interactive";
         let rotation = NumCast(this.dataDoc.rotation, 0);
         let aspect = (rotation % 180) ? this.dataDoc[HeightSym]() / this.dataDoc[WidthSym]() : 1;
         let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
-- 
cgit v1.2.3-70-g09d2


From 7c3b56a2e4552308014aa011493b33e3b054be94 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Tue, 30 Jul 2019 16:33:27 -0400
Subject: fixed some scripting stuff on view specs

---
 src/client/views/collections/CollectionSubView.tsx      | 3 +++
 src/client/views/collections/CollectionViewChromes.scss | 3 ++-
 src/client/views/collections/CollectionViewChromes.tsx  | 9 +++++++--
 src/client/views/collections/KeyRestrictionRow.tsx      | 6 +++++-
 4 files changed, 17 insertions(+), 4 deletions(-)

(limited to 'src/client/views/collections/CollectionSubView.tsx')

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a15ed8f94..7386eaba6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -66,6 +66,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     if (res.success) {
                         return res.result;
                     }
+                    else {
+                        console.log(res.error);
+                    }
                 });
             }
             return docs;
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 989315194..c062c3261 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -37,9 +37,10 @@
             }
 
             .collectionViewBaseChrome-collapse {
-                transition: all .5s;
+                transition: all .5s opacity 0.2s;
                 position: absolute;
                 width: 40px;
+                transform-origin: top left;
             }
 
             .collectionViewBaseChrome-viewSpecs {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 2bffe3cc0..0827cfef9 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -142,7 +142,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
             `return ${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} ${keyRestrictionScript}` :
             `return ${keyRestrictionScript} ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
             "return true";
-        let compiled = CompileScript(fullScript, { params: { doc: Doc.name } });
+        let compiled = CompileScript(fullScript, { params: { doc: Doc.name }, typecheck: false });
         if (compiled.compiled) {
             this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled);
         }
@@ -189,7 +189,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                 <div className="collectionViewChrome">
                     <div className="collectionViewBaseChrome">
                         <button className="collectionViewBaseChrome-collapse"
-                            style={{ marginTop: this._collapsed ? 60 : 0, transform: `rotate(${this._collapsed ? 180 : 0}deg)` }}
+                            style={{
+                                marginTop: this._collapsed ? 60 : 0,
+                                transform: `rotate(${this._collapsed ? 180 : 0}deg) scale(${this._collapsed ? 0.5 : 1}) translate(${this._collapsed ? "-100%, -100%" : "0, 0"})`,
+                                opacity: (this._collapsed && !this.props.CollectionView.props.isSelected()) ? 0 : 0.9,
+                                left: (this._collapsed ? 0 : "unset"),
+                            }}
                             title="Collapse collection chrome" onClick={this.toggleCollapse}>
                             <FontAwesomeIcon icon="caret-up" size="2x" />
                         </button>
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
index 9baa250a6..1b59547d8 100644
--- a/src/client/views/collections/KeyRestrictionRow.tsx
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -2,6 +2,7 @@ import * as React from "react";
 import { observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
+import { Doc } from "../../../new_fields/Doc";
 
 interface IKeyRestrictionProps {
     contains: boolean;
@@ -23,7 +24,10 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr
                 parsedValue = parsed;
                 type = "number";
             }
-            let scriptText = `${this._contains ? "" : "!"}((doc.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))`;
+            let scriptText = `${this._contains ? "" : "!"}(((doc.${this._key} && (doc.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))) ||
+                ((doc.data_ext && doc.data_ext.${this._key}) && (doc.data_ext.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))))`;
+            // let doc = new Doc();
+            // ((doc.data_ext && doc.data_ext!.text) && (doc.data_ext!.text as string).includes("hello"));
             this.props.script(scriptText);
         }
         else {
-- 
cgit v1.2.3-70-g09d2


From a268800c9d743e94c9395dc1d34809bb55c3ceab Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 31 Jul 2019 00:05:39 -0400
Subject: added alt-drag-drop for applying template to items in a collection

---
 src/client/views/collections/CollectionSubView.tsx | 15 ++++++++++-----
 src/client/views/nodes/ImageBox.tsx                | 12 +-----------
 src/new_fields/Doc.ts                              | 13 +++++++++++++
 3 files changed, 24 insertions(+), 16 deletions(-)

(limited to 'src/client/views/collections/CollectionSubView.tsx')

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 7386eaba6..077f3f941 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,13 +1,15 @@
 import { action, computed } from "mobx";
 import * as rp from 'request-promise';
 import CursorField from "../../../new_fields/CursorField";
-import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
 import { BoolCast, Cast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { RouteStore } from "../../../server/RouteStore";
+import { Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs, DocumentOptions, DocumentType } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
@@ -19,10 +21,6 @@ import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import React = require("react");
-import { MainView } from "../MainView";
-import { Utils } from "../../../Utils";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { CompileScript } from "../../util/Scripting";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -115,6 +113,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         @action
         protected drop(e: Event, de: DragManager.DropEvent): boolean {
             if (de.data instanceof DragManager.DocumentDragData) {
+                if (de.mods === "AltKey" && de.data.draggedDocuments.length) {
+                    this.childDocs.map(doc =>
+                        Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, undefined)
+                    );
+                    e.stopPropagation();
+                    return true;
+                }
                 let added = false;
                 if (de.data.dropAction || de.data.userDropAction) {
                     added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index dbe545048..b60ef41fd 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -93,17 +93,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         if (de.data instanceof DragManager.DocumentDragData) {
             de.data.droppedDocuments.forEach(action((drop: Doc) => {
                 if (de.mods === "CtrlKey") {
-                    let temp = Doc.MakeDelegate(drop);
-                    this.props.Document.nativeWidth = Doc.GetProto(this.props.Document).nativeWidth = undefined;
-                    this.props.Document.nativeHeight = Doc.GetProto(this.props.Document).nativeHeight = undefined;
-                    this.props.Document.width = drop.width;
-                    this.props.Document.height = drop.height;
-                    Doc.GetProto(this.props.Document).type = DocumentType.TEMPLATE;
-                    if (this.props.DataDoc && this.props.DataDoc.layout === this.props.Document) {
-                        this.props.DataDoc.layout = temp;
-                    } else {
-                        this.props.Document.layout = temp;
-                    }
+                    Doc.ApplyTemplateTo(drop, this.props.Document, this.props.DataDoc);
                     e.stopPropagation();
                 } else if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
                     Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 59314783b..dab0f9070 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -462,6 +462,19 @@ export namespace Doc {
         otherdoc.type = DocumentType.TEMPLATE;
         return otherdoc;
     }
+    export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc) {
+        let temp = Doc.MakeDelegate(templateDoc);
+        target.nativeWidth = Doc.GetProto(target).nativeWidth = undefined;
+        target.nativeHeight = Doc.GetProto(target).nativeHeight = undefined;
+        target.width = templateDoc.width;
+        target.height = templateDoc.height;
+        Doc.GetProto(target).type = DocumentType.TEMPLATE;
+        if (targetData && targetData.layout === target) {
+            targetData.layout = temp;
+        } else {
+            target.layout = temp;
+        }
+    }
 
     export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
-- 
cgit v1.2.3-70-g09d2

