aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts3
-rw-r--r--src/client/util/ClientUtils.ts.temp3
-rw-r--r--src/client/util/SelectionManager.ts2
-rw-r--r--src/client/util/request-image-size.js73
-rw-r--r--src/client/views/ContextMenu.scss27
-rw-r--r--src/client/views/ContextMenu.tsx186
-rw-r--r--src/client/views/ContextMenuItem.tsx17
-rw-r--r--src/client/views/DocumentDecorations.tsx6
-rw-r--r--src/client/views/EditableView.tsx11
-rw-r--r--src/client/views/InkingControl.tsx11
-rw-r--r--src/client/views/MainView.tsx8
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx12
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.scss13
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx62
-rw-r--r--src/client/views/collections/CollectionSubView.tsx7
-rw-r--r--src/client/views/collections/CollectionTreeView.scss25
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx376
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx34
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx43
-rw-r--r--src/client/views/nodes/DocumentView.tsx47
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx1
-rw-r--r--src/client/views/nodes/IconBox.tsx6
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx3
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/pdf/PDFMenu.tsx75
-rw-r--r--src/client/views/pdf/PDFViewer.tsx369
-rw-r--r--src/client/views/pdf/Page.tsx81
-rw-r--r--src/new_fields/Doc.ts12
-rw-r--r--src/server/Search.ts1
-rw-r--r--src/server/index.ts34
32 files changed, 947 insertions, 607 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fcd1010c6..b04fc401a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -18,7 +18,6 @@ import { action } from "mobx";
import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel";
import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel";
import { AggregateFunction } from "../northstar/model/idea/idea";
-import { Template } from "../views/Templates";
import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
import { IconBox } from "../views/nodes/IconBox";
import { Field, Doc, Opt } from "../../new_fields/Doc";
@@ -35,7 +34,7 @@ import { dropActionType } from "../util/DragManager";
import { DateField } from "../../new_fields/DateField";
import { UndoManager } from "../util/UndoManager";
import { RouteStore } from "../../server/RouteStore";
-var requestImageSize = require('request-image-size');
+var requestImageSize = require('../util/request-image-size');
var path = require('path');
export interface DocumentOptions {
diff --git a/src/client/util/ClientUtils.ts.temp b/src/client/util/ClientUtils.ts.temp
new file mode 100644
index 000000000..f9fad5ed9
--- /dev/null
+++ b/src/client/util/ClientUtils.ts.temp
@@ -0,0 +1,3 @@
+export namespace ClientUtils {
+ export const RELEASE = "mode";
+} \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 09bccb1a0..7dbb81e76 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -66,7 +66,7 @@ export namespace SelectionManager {
export function GetIsDragging() { return manager.IsDragging; }
export function SelectedDocuments(): Array<DocumentView> {
- return manager.SelectedDocuments;
+ return manager.SelectedDocuments.slice();
}
export function ViewsSortedHorizontally(): DocumentView[] {
let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
diff --git a/src/client/util/request-image-size.js b/src/client/util/request-image-size.js
new file mode 100644
index 000000000..0f9328872
--- /dev/null
+++ b/src/client/util/request-image-size.js
@@ -0,0 +1,73 @@
+/**
+ * request-image-size: Detect image dimensions via request.
+ * Licensed under the MIT license.
+ *
+ * https://github.com/FdezRomero/request-image-size
+ * © 2017 Rodrigo Fernández Romero
+ *
+ * Based on the work of Johannes J. Schmidt
+ * https://github.com/jo/http-image-size
+ */
+
+const request = require('request');
+const imageSize = require('image-size');
+const HttpError = require('standard-http-error');
+
+module.exports = function requestImageSize(options) {
+ let opts = {
+ encoding: null
+ };
+
+ if (options && typeof options === 'object') {
+ opts = Object.assign(options, opts);
+ } else if (options && typeof options === 'string') {
+ opts = Object.assign({ uri: options }, opts);
+ } else {
+ return Promise.reject(new Error('You should provide an URI string or a "request" options object.'));
+ }
+
+ opts.encoding = null;
+
+ return new Promise((resolve, reject) => {
+ const req = request(opts);
+
+ req.on('response', res => {
+ if (res.statusCode >= 400) {
+ return reject(new HttpError(res.statusCode, res.statusMessage));
+ }
+
+ let buffer = new Buffer([]);
+ let size;
+ let imageSizeError;
+
+ res.on('data', chunk => {
+ buffer = Buffer.concat([buffer, chunk]);
+
+ try {
+ size = imageSize(buffer);
+ } catch (err) {
+ imageSizeError = err;
+ return;
+ }
+
+ if (size) {
+ resolve(size);
+ return req.abort();
+ }
+ });
+
+ res.on('error', err => reject(err));
+
+ res.on('end', () => {
+ if (!size) {
+ return reject(imageSizeError);
+ }
+
+ size.downloaded = buffer.length;
+ return resolve(size);
+ });
+ });
+
+ req.on('error', err => reject(err));
+ });
+};
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index e363c5158..254163b53 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -53,6 +53,33 @@
font-size: 20px;
}
+.contextMenu-itemSelected {
+ background: rgb(136, 136, 136)
+}
+
+.contextMenu-group {
+ // width: 11vw; //10vw
+ height: 30px; //2vh
+ background: rgb(200, 200, 200);
+ display: flex; //comment out to allow search icon to be inline with search text
+ justify-content: left;
+ align-items: center;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ transition: all .1s;
+ border-width: .11px;
+ border-style: none;
+ border-color: $intermediate-color; // rgb(187, 186, 186);
+ border-bottom-style: solid;
+ // padding: 10px 0px 10px 0px;
+ white-space: nowrap;
+ font-size: 20px;
+}
+
.contextMenu-item:hover {
transition: all 0.1s;
background: $lighter-alt-accent;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index eb1937683..69692dbb8 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,11 +1,12 @@
import React = require("react");
-import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
-import { observable, action } from "mobx";
+import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem";
+import { observable, action, computed } from "mobx";
import { observer } from "mobx-react";
import "./ContextMenu.scss";
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch, faCircle } from '@fortawesome/free-solid-svg-icons';
+import Measure from "react-measure";
library.add(faSearch);
library.add(faCircle);
@@ -14,29 +15,27 @@ library.add(faCircle);
export class ContextMenu extends React.Component {
static Instance: ContextMenu;
- @observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault(), icon: "smile" }];
+ @observable private _items: Array<ContextMenuProps> = [];
@observable private _pageX: number = 0;
@observable private _pageY: number = 0;
- @observable private _display: string = "none";
+ @observable private _display: boolean = false;
@observable private _searchString: string = "";
// afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
@observable private _yRelativeToTop: boolean = true;
+ @observable selectedIndex = -1;
-
- private ref: React.RefObject<HTMLDivElement>;
+ @observable private _width: number = 0;
+ @observable private _height: number = 0;
constructor(props: Readonly<{}>) {
super(props);
- this.ref = React.createRef();
-
ContextMenu.Instance = this;
}
@action
clearItems() {
this._items = [];
- this._display = "none";
}
@action
@@ -50,63 +49,178 @@ export class ContextMenu extends React.Component {
return this._items;
}
+ static readonly buffer = 20;
+ get pageX() {
+ const x = this._pageX;
+ if (x < 0) {
+ return 0;
+ }
+ const width = this._width;
+ if (x + width > window.innerWidth - ContextMenu.buffer) {
+ return window.innerWidth - ContextMenu.buffer - width;
+ }
+ return x;
+ }
+
+ get pageY() {
+ const y = this._pageY;
+ if (y < 0) {
+ return 0;
+ }
+ const height = this._height;
+ if (y + height > window.innerHeight - ContextMenu.buffer) {
+ return window.innerHeight - ContextMenu.buffer - height;
+ }
+ return y;
+ }
+
@action
displayMenu(x: number, y: number) {
//maxX and maxY will change if the UI/font size changes, but will work for any amount
//of items added to the menu
- let maxX = window.innerWidth - 150;
- let maxY = window.innerHeight - ((this._items.length + 1/*for search box*/) * 34 + 30);
- this._pageX = x > maxX ? maxX : x;
- this._pageY = y > maxY ? maxY : y;
+ this._pageX = x;
+ this._pageY = y;
this._searchString = "";
- this._display = "flex";
+ this._display = true;
}
- intersects = (x: number, y: number): boolean => {
- if (this.ref.current && this._display !== "none") {
- let menuSize = { width: this.ref.current.getBoundingClientRect().width, height: this.ref.current.getBoundingClientRect().height };
-
- let upperLeft = { x: this._pageX, y: this._yRelativeToTop ? this._pageY : window.innerHeight - (this._pageY + menuSize.height) };
- let bottomRight = { x: this._pageX + menuSize.width, y: this._yRelativeToTop ? this._pageY + menuSize.height : window.innerHeight - this._pageY };
+ @action
+ closeMenu = () => {
+ this.clearItems();
+ this._display = false;
+ }
- if (x >= upperLeft.x && x <= bottomRight.x) {
- if (y >= upperLeft.y && y <= bottomRight.y) {
- return true;
+ @computed get filteredItems(): (OriginalMenuProps | string[])[] {
+ const searchString = this._searchString.toLowerCase().split(" ");
+ const matches = (descriptions: string[]): boolean => {
+ return searchString.every(s => descriptions.some(desc => desc.toLowerCase().includes(s)));
+ };
+ const flattenItems = (items: ContextMenuProps[], groupFunc: (groupName: any) => string[]) => {
+ let eles: (OriginalMenuProps | string[])[] = [];
+
+ const leaves: OriginalMenuProps[] = [];
+ for (const item of items) {
+ const description = item.description;
+ const path = groupFunc(description);
+ if ("subitems" in item) {
+ const children = flattenItems(item.subitems, name => [...groupFunc(description), name]);
+ if (children.length || matches(path)) {
+ eles.push(path);
+ eles = eles.concat(children);
+ }
+ } else {
+ if (!matches(path)) {
+ continue;
+ }
+ leaves.push(item);
}
}
- }
- return false;
+
+ eles = [...leaves, ...eles];
+
+ return eles;
+ };
+ return flattenItems(this._items, name => [name]);
}
- @action
- closeMenu = () => {
- this.clearItems();
+ @computed get flatItems(): OriginalMenuProps[] {
+ return this.filteredItems.filter(item => !Array.isArray(item)) as OriginalMenuProps[];
+ }
+
+ @computed get filteredViews() {
+ const createGroupHeader = (contents: any) => {
+ return (
+ <div className="contextMenu-group">
+ <div className="contextMenu-description">{contents}</div>
+ </div>
+ );
+ };
+ const createItem = (item: ContextMenuProps, selected: boolean) => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} selected={selected} />;
+ let itemIndex = 0;
+ return this.filteredItems.map(value => {
+ if (Array.isArray(value)) {
+ return createGroupHeader(value.join(" -> "));
+ } else {
+ return createItem(value, itemIndex++ === this.selectedIndex);
+ }
+ });
+ }
+
+ @computed get menuItems() {
+ if (!this._searchString) {
+ return this._items.map(item => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} />);
+ }
+ return this.filteredViews;
}
render() {
- let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } :
- { left: this._pageX, bottom: this._pageY, display: this._display };
+ if (!this._display) {
+ return null;
+ }
+ let style = this._yRelativeToTop ? { left: this.pageX, top: this.pageY } :
+ { left: this.pageX, bottom: this.pageY };
+ console.log(this._pageX);
+ console.log(this.pageX);
+ console.log();
- return (
- <div className="contextMenu-cont" style={style} ref={this.ref}>
+ const contents = (
+ <>
<span>
<span className="icon-background">
<FontAwesomeIcon icon="search" size="lg" />
</span>
- <input className="contextMenu-item contextMenu-description" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} />
+ <input className="contextMenu-item contextMenu-description" type="text" placeholder="Search . . ." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
</span>
- {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
- map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.closeMenu} />)}
- </div>
+ {this.menuItems}
+ </>
+ );
+ return (
+ <Measure offset onResize={action((r: any) => { this._width = r.offset.width; this._height = r.offset.height; })}>
+ {({ measureRef }) => (
+ <div className="contextMenu-cont" style={style} ref={measureRef}>
+ {contents}
+ </div>
+ )
+ }
+ </Measure>
);
}
@action
+ onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "ArrowDown") {
+ if (this.selectedIndex < this.flatItems.length - 1) {
+ this.selectedIndex++;
+ }
+ e.preventDefault();
+ } else if (e.key === "ArrowUp") {
+ if (this.selectedIndex > 0) {
+ this.selectedIndex--;
+ }
+ e.preventDefault();
+ } else if (e.key === "Enter") {
+ const item = this.flatItems[this.selectedIndex];
+ item.event();
+ this.closeMenu();
+ }
+ }
+
+ @action
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this._searchString = e.target.value;
+ if (!this._searchString) {
+ this.selectedIndex = -1;
+ }
+ else {
+ if (this.selectedIndex === -1) {
+ this.selectedIndex = 0;
+ } else {
+ this.selectedIndex = Math.min(this.flatItems.length - 1, this.selectedIndex);
+ }
+ }
}
} \ No newline at end of file
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index dc0751049..9bbb97d7e 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -9,7 +9,7 @@ library.add(faAngleRight);
export interface OriginalMenuProps {
description: string;
- event: (e: React.MouseEvent<HTMLDivElement>) => void;
+ event: () => void;
icon?: IconProp; //maybe should be optional (icon?)
closeMenu?: () => void;
}
@@ -21,13 +21,10 @@ export interface SubmenuProps {
closeMenu?: () => void;
}
-export interface ContextMenuItemProps {
- type: ContextMenuProps | SubmenuProps;
-}
export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
@observer
-export class ContextMenuItem extends React.Component<ContextMenuProps> {
+export class ContextMenuItem extends React.Component<ContextMenuProps & { selected?: boolean }> {
@observable private _items: Array<ContextMenuProps> = [];
@observable private overItem = false;
@@ -40,7 +37,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
handleEvent = (e: React.MouseEvent<HTMLDivElement>) => {
if ("event" in this.props) {
- this.props.event(e);
+ this.props.event();
this.props.closeMenu && this.props.closeMenu();
}
}
@@ -67,13 +64,12 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
return;
}
this.currentTimeout = setTimeout(action(() => this.overItem = false), ContextMenuItem.timeout);
-
}
render() {
if ("event" in this.props) {
return (
- <div className="contextMenu-item" onClick={this.handleEvent}>
+ <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onClick={this.handleEvent}>
{this.props.icon ? (
<span className="icon-background">
<FontAwesomeIcon icon={this.props.icon} size="sm" />
@@ -84,14 +80,13 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
</div>
</div>
);
- }
- else {
+ } else if ("subitems" in this.props) {
let submenu = !this.overItem ? (null) :
<div className="contextMenu-subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
</div>;
return (
- <div className="contextMenu-item" onMouseEnter={this.onPointerEnter} onMouseLeave={this.onPointerLeave}>
+ <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onMouseEnter={this.onPointerEnter} onMouseLeave={this.onPointerLeave}>
{this.props.icon ? (
<span className="icon-background">
<FontAwesomeIcon icon={this.props.icon} size="sm" />
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3d802964a..4136ce7b1 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -464,16 +464,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
runInAction(() => FormattedTextBox.InputBoxOverlay = undefined);
SelectionManager.SelectedDocuments().forEach(element => {
- const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
-
- if (rect.width !== 0 && (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0)) {
+ if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
let doc = PositionDocument(element.props.Document);
let nwidth = doc.nativeWidth || 0;
let nheight = doc.nativeHeight || 0;
let zoomBasis = NumCast(doc.zoomBasis, 1);
let width = (doc.width || 0) / zoomBasis;
let height = (doc.height || (nheight / nwidth * width)) / zoomBasis;
- let scale = width / rect.width;
+ let scale = element.props.ScreenToLocalTransform().Scale;
let actualdW = Math.max(width + (dW * scale), 20);
let actualdH = Math.max(height + (dH * scale), 20);
doc.x = (doc.x || 0) + dX * (actualdW - width);
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 70d6c22bf..97a2d19dd 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -29,6 +29,7 @@ export interface EditableProps {
display?: string;
oneLine?: boolean;
editing?: boolean;
+ onClick?: (e: React.MouseEvent) => boolean;
}
/**
@@ -65,14 +66,20 @@ export class EditableView extends React.Component<EditableProps> {
@action
onClick = (e: React.MouseEvent) => {
- this._editing = true;
+ if (!this.props.onClick || !this.props.onClick(e)) {
+ this._editing = true;
+ }
+ e.stopPropagation();
+ }
+
+ stopPropagation(e: React.SyntheticEvent) {
e.stopPropagation();
}
render() {
if (this._editing) {
return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
- onBlur={action(() => this._editing = false)}
+ onBlur={action(() => this._editing = false)} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
style={{ display: this.props.display }} />;
} else {
return (
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index b98132c23..0837e07a9 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -28,11 +28,18 @@ export class InkingControl extends React.Component {
switchTool = (tool: InkTool): void => {
this._selectedTool = tool;
}
+ decimalToHexString(number: number) {
+ if (number < 0) {
+ number = 0xFFFFFFFF + number + 1;
+ }
+
+ return number.toString(16).toUpperCase();
+ }
@action
switchColor = (color: ColorResult): void => {
- this._selectedColor = color.hex;
- SelectionManager.SelectedDocuments().forEach(doc => Doc.GetProto(doc.props.Document).backgroundColor = color.hex);
+ this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
+ SelectionManager.SelectedDocuments().forEach(doc => Doc.GetProto(doc.props.Document).backgroundColor = this._selectedColor);
}
@action
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7f979cd3b..51630c29b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,7 +3,7 @@ import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
-import { CirclePicker, SliderPicker, BlockPicker, TwitterPicker } from 'react-color';
+import { CirclePicker, SliderPicker, BlockPicker, TwitterPicker, SketchPicker } from 'react-color';
import "normalize.css";
import * as React from 'react';
import Measure from 'react-measure';
@@ -118,7 +118,7 @@ export class MainView extends React.Component {
const targets = document.elementsFromPoint(e.x, e.y);
if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) {
- ContextMenu.Instance.clearItems();
+ ContextMenu.Instance.closeMenu();
}
}), true);
}
@@ -272,8 +272,8 @@ export class MainView extends React.Component {
<li key="redo"><button className="add-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
<li key="color"><button className="add-button round-button" title="Redo" onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
- <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { display: "block" } : { display: "none" }}>
- <TwitterPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
+ <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
+ <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
</div>
</div></button></li>
{btns.map(btn =>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 69b9e77eb..5f8862c43 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -135,10 +135,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
- if (this._goldenLayout.root.contentItems[0].isRow) {
+ if (this._goldenLayout.root.contentItems.length === 0) {
+ this._goldenLayout.root.addChild(newContentItem);
+ } else if (this._goldenLayout.root.contentItems[0].isRow) {
this._goldenLayout.root.contentItems[0].addChild(newContentItem);
- }
- else {
+ } else {
var collayout = this._goldenLayout.root.contentItems[0];
var newRow = collayout.layoutManager.createContentItem({ type: "row" }, this._goldenLayout);
collayout.parent.replaceChild(collayout, newRow);
@@ -259,6 +260,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@action
onPointerDown = (e: React.PointerEvent): void => {
this._isPointerDown = true;
+ let onPointerUp = action(() => {
+ window.removeEventListener("pointerup", onPointerUp)
+ this._isPointerDown = false
+ })
+ window.addEventListener("pointerup", onPointerUp);
var className = (e.target as any).className;
if (className === "messageCounter") {
e.stopPropagation();
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index faea8d44d..9cc8961e3 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -385,7 +385,7 @@ interface CollectionSchemaPreviewProps {
Document?: Doc;
width: () => number;
height: () => number;
- CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+ CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView;
getTransform: () => Transform;
addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean;
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index af194aec9..485ecf1de 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -38,4 +38,17 @@
background: $dark-color;
color: $light-color;
}
+
+
+ .collectionStackingView-columnDoc,
+ .collectionStackingView-masonryDoc {
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ .collectionStackingView-masonryDoc {
+ transform-origin: top left;
+ grid-column-end: span 1;
+ height: 100%;
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index f5ad4ee95..c855cb43a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,5 +1,5 @@
import React = require("react");
-import { action, computed, IReactionDisposer, reaction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
import { observer } from "mobx-react";
import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
@@ -10,6 +10,7 @@ import { DocumentView } from "../nodes/DocumentView";
import { CollectionSchemaPreview } from "./CollectionSchemaView";
import "./CollectionStackingView.scss";
import { CollectionSubView } from "./CollectionSubView";
+import { Transform } from "../../util/Transform";
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -66,18 +67,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
let children = this.childDocs.filter(d => !d.isMinimized);
return children.map((d, i) => {
let dref = React.createRef<HTMLDivElement>();
- let script = undefined;
- let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
- let rowHeight = () => this.singleColDocHeight(d);
let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]());
- return <div className="collectionStackingView-masonryDoc"
+ let width = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
+ let height = () => this.singleColDocHeight(d);
+ return <div className="collectionStackingView-columnDoc"
key={d[Id]}
ref={dref}
- style={{ width: colWidth(), height: rowHeight(), marginLeft: "auto", marginRight: "auto" }} >
+ style={{ width: width(), height: height() }} >
<CollectionSchemaPreview
Document={d}
- width={colWidth}
- height={rowHeight}
+ width={width}
+ height={height}
getTransform={dxf}
CollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
@@ -87,7 +87,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
setPreviewScript={emptyFunction}
- previewScript={script}>
+ previewScript={undefined}>
</CollectionSchemaPreview>
</div>;
});
@@ -95,41 +95,31 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@computed
get children() {
return this.childDocs.filter(d => !d.isMinimized).map((d, i) => {
+ let aspect = d.nativeHeight ? NumCast(d.nativeWidth) / NumCast(d.nativeHeight) : undefined;
let dref = React.createRef<HTMLDivElement>();
- let dxf = () => this.getDocTransform(d, dref.current!);
- let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this._gridSize + this.gridGap));
- let childFocus = (doc: Doc) => {
- doc.libraryBrush = true;
- this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered.
- };
+ let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]());
+ let width = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
+ let height = () => aspect ? width() / aspect : d[HeightSym]()
+ let rowSpan = Math.ceil((height() + this.gridGap) / (this._gridSize + this.gridGap));
return (<div className="collectionStackingView-masonryDoc"
key={d[Id]}
ref={dref}
- style={{
- width: NumCast(d.nativeWidth, d[WidthSym]()),
- height: NumCast(d.nativeHeight, d[HeightSym]()),
- transformOrigin: "top left",
- gridRowEnd: `span ${rowSpan}`,
- gridColumnEnd: `span 1`,
- transform: `scale(${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())}, ${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())})`
- }} >
- <DocumentView key={d[Id]} Document={d}
+ style={{ gridRowEnd: `span ${rowSpan}` }} >
+ <CollectionSchemaPreview
+ Document={d}
+ CollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
removeDocument={this.props.removeDocument}
- moveDocument={this.moveDocument}
- ContainingCollectionView={this.props.CollectionView}
- isTopMost={false}
- ScreenToLocalTransform={dxf}
- focus={childFocus}
- ContentScaling={returnOne}
- PanelWidth={d[WidthSym]}
- PanelHeight={d[HeightSym]}
- selectOnLoad={false}
- parentActive={this.props.active}
+ getTransform={dxf}
+ width={width}
+ height={height}
+ active={this.props.active}
addDocTab={this.props.addDocTab}
- bringToFront={emptyFunction}
whenActiveChanged={this.props.whenActiveChanged}
- />
+ setPreviewScript={emptyFunction}
+ previewScript={undefined}>
+ </CollectionSchemaPreview>
</div>);
});
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e55cd9e37..699bddc7c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -176,8 +176,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
return;
}
if (html && !html.startsWith("<a")) {
- if (html.indexOf("<img") === 0) {
- let split = html.split("\"")[1];
+ let tags = html.split("<");
+ if (tags[0] === "") tags.splice(0, 1);
+ let img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
+ if (img) {
+ let split = img.split("src=\"")[1].split("\"")[0];
let doc = Docs.ImageDocument(split, { ...options, width: 300 });
this.props.addDocument(doc, false);
return;
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index f6df96d92..a85604e58 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -4,15 +4,15 @@
border-width: $COLLECTION_BORDER_WIDTH;
border-color: transparent;
border-style: solid;
- border-radius: $border-radius;
+ border-radius: inherit;
box-sizing: border-box;
height: 100%;
- padding: 20px;
+ padding-top: 20px;
padding-left: 10px;
padding-right: 0px;
background: $light-color-secondary;
font-size: 13px;
- overflow: scroll;
+ overflow: auto;
ul {
list-style: none;
@@ -50,10 +50,13 @@
font-size: 24px;
}
- .collectionTreeView-keyHeader {
- font-style: italic;
- font-size: 8pt;
- }
+}
+.collectionTreeView-keyHeader {
+ font-style: italic;
+ font-size: 8pt;
+ margin-left: 3px;
+ display:none;
+ background: lightgray;
}
.docContainer {
@@ -68,7 +71,15 @@
display: none;
}
+.treeViewItem-border {
+ display:inherit;
+ border-left: dashed 1px #00000042;
+}
+
.treeViewItem-header:hover {
+ .collectionTreeView-keyHeader {
+ display:inherit;
+ }
.treeViewItem-openRight {
display: inline-block;
height:13px;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 5e690361c..eaa3add40 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,55 +1,55 @@
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight, faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faAngleRight, faCaretDown, faCaretRight, faTrashAlt, faCaretSquareRight, faCaretSquareDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, trace } from "mobx";
+import { action, observable, computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast } from '../../../new_fields/Doc';
+import { Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
+import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
-import { BoolCast, Cast, NumCast, StrCast, PromiseValue } from '../../../new_fields/Types';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
+import { emptyFunction, Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
+import { SelectionManager } from '../../util/SelectionManager';
+import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { EditableView } from "../EditableView";
import { MainView } from '../MainView';
+import { Templates } from '../Templates';
import { CollectionViewType } from './CollectionBaseView';
import { CollectionDockingView } from './CollectionDockingView';
+import { CollectionSchemaPreview } from './CollectionSchemaView';
import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
-import { Transform } from '../../util/Transform';
-import { SelectionManager } from '../../util/SelectionManager';
-import { emptyFunction } from '../../../Utils';
-import { List } from '../../../new_fields/List';
-import { Templates } from '../Templates';
export interface TreeViewProps {
document: Doc;
- deleteDoc: (doc: Doc) => void;
+ deleteDoc: (doc: Doc) => boolean;
moveDocument: DragManager.MoveFunction;
dropAction: "alias" | "copy" | undefined;
addDocTab: (doc: Doc, where: string) => void;
+ panelWidth: () => number;
+ panelHeight: () => number;
addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
indentDocument?: () => void;
ScreenToLocalTransform: () => Transform;
+ outerXf: () => { translateX: number, translateY: number };
treeViewId: string;
parentKey: string;
active: () => boolean;
}
-export enum BulletType {
- Collapsed,
- Collapsible,
- List
-}
-
library.add(faTrashAlt);
library.add(faAngleRight);
library.add(faCaretDown);
library.add(faCaretRight);
+library.add(faCaretSquareDown);
+library.add(faCaretSquareRight);
@observer
/**
@@ -57,26 +57,25 @@ library.add(faCaretRight);
*/
class TreeView extends React.Component<TreeViewProps> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
- private treedropDisposer?: DragManager.DragDropDisposer;
+ private _treedropDisposer?: DragManager.DragDropDisposer;
+ private _dref = React.createRef<HTMLDivElement>();
+ @observable _chosenKey: string = "data";
+ @observable _collapsed: boolean = true;
+
protected createTreeDropTarget = (ele: HTMLDivElement) => {
- this.treedropDisposer && this.treedropDisposer();
+ this._treedropDisposer && this._treedropDisposer();
if (ele) {
- this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } });
+ this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } });
}
}
- @observable _isOver: boolean = false;
- @observable _collapsed: boolean = true;
-
@undoBatch delete = () => this.props.deleteDoc(this.props.document);
- @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "openRight");
-
- @action onMouseEnter = () => { this._isOver = true; };
- @action onMouseLeave = () => { this._isOver = false; };
+ @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "onRight");
+ onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
onPointerEnter = (e: React.PointerEvent): void => {
this.props.active() && (this.props.document.libraryBrush = true);
- if (e.buttons === 1) {
+ if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
this._header!.current!.className = "treeViewItem-header";
document.addEventListener("pointermove", this.onDragMove, true);
}
@@ -92,73 +91,87 @@ class TreeView extends React.Component<TreeViewProps> {
let rect = this._header!.current!.getBoundingClientRect();
let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
let before = x[1] < bounds[1];
- let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible);
+ let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed);
this._header!.current!.className = "treeViewItem-header";
- if (inside && this._bulletType !== BulletType.List) this._header!.current!.className += " treeViewItem-header-inside";
+ if (inside) this._header!.current!.className += " treeViewItem-header-inside";
else if (before) this._header!.current!.className += " treeViewItem-header-above";
else if (!before) this._header!.current!.className += " treeViewItem-header-below";
e.stopPropagation();
}
- onPointerDown = (e: React.PointerEvent) => {
- e.stopPropagation();
- }
@action
- remove = (document: Document, key: string) => {
+ remove = (document: Document, key: string): boolean => {
let children = Cast(this.props.document[key], listSpec(Doc), []);
- children.indexOf(document) !== -1 && children.splice(children.indexOf(document), 1);
+ if (children.indexOf(document) !== -1) {
+ children.splice(children.indexOf(document), 1);
+ return true;
+ }
+ return false;
}
@action
- move: DragManager.MoveFunction = (document: Doc, target: Doc, addDoc) => {
- if (this.props.document !== target) {
- //TODO This should check if it was removed
- this.props.deleteDoc(document);
- return addDoc(document);
- }
- return true;
+ move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {
+ return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
}
@action
- indent = () => {
- this.props.addDocument(this.props.document);
- this.delete();
- }
+ indent = () => this.props.addDocument(this.props.document) && this.delete()
+ renderBullet() {
+ let docList = Cast(this.props.document.data, listSpec(Doc));
+ let doc = Cast(this.props.document.data, Doc);
+ let isDoc = doc instanceof Doc || docList;
+ return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)}>
+ {<FontAwesomeIcon icon={this._collapsed ? (isDoc ? "caret-square-right" : "caret-right") : (isDoc ? "caret-square-down" : "caret-down")} />}
+ </div>;
+ }
- renderBullet(type: BulletType) {
- let onClicked = action(() => this._collapsed = !this._collapsed);
- let bullet: IconProp | undefined = undefined;
- switch (type) {
- case BulletType.Collapsed: bullet = "caret-right"; break;
- case BulletType.Collapsible: bullet = "caret-down"; break;
+ titleClicked = (e: React.MouseEvent) => {
+ if (this._collapsed) return false;
+ else {
+ this.props.document.embed = !BoolCast(this.props.document.embed);
+ return true;
}
- return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;
}
static loadId = "";
- editableView = (key: string, style?: string) =>
- (<EditableView
- oneLine={true}
- display={"inline"}
- editing={this.props.document[Id] === TreeView.loadId}
- contents={StrCast(this.props.document[key])}
- height={36}
- fontStyle={style}
- GetValue={() => StrCast(this.props.document[key])}
- OnFillDown={(value: string) => {
- Doc.GetProto(this.props.document)[key] = value;
- let doc = Docs.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25 });
- TreeView.loadId = doc[Id];
- doc.templates = new List<string>([Templates.Title.Layout]);
- this.props.addDocument(doc);
- return true;
- }}
- OnTab={() => this.props.indentDocument && this.props.indentDocument()}
- SetValue={(value: string) => {
- Doc.GetProto(this.props.document)[key] = value;
- return true;
- }}
- />)
+ editableView = (key: string, style?: string) => (<EditableView
+ oneLine={true}
+ display={"inline"}
+ editing={this.props.document[Id] === TreeView.loadId}
+ contents={StrCast(this.props.document[key])}
+ onClick={this.titleClicked}
+ height={36}
+ fontStyle={style}
+ GetValue={() => StrCast(this.props.document[key])}
+ SetValue={(value: string) => (Doc.GetProto(this.props.document)[key] = value) ? true : true}
+ OnFillDown={(value: string) => {
+ Doc.GetProto(this.props.document)[key] = value;
+ let doc = Docs.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ TreeView.loadId = doc[Id];
+ return this.props.addDocument(doc);
+ }}
+ OnTab={() => this.props.indentDocument && this.props.indentDocument()}
+ />)
+ @computed get keyList() {
+ let keys = Array.from(Object.keys(this.props.document));
+ if (this.props.document.proto instanceof Doc) {
+ keys.push(...Array.from(Object.keys(this.props.document.proto)));
+ while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
+ }
+ let keyList: string[] = [];
+ keys.map(key => {
+ let docList = Cast(this.props.document[key], listSpec(Doc));
+ let doc = Cast(this.props.document[key], Doc);
+ if (doc instanceof Doc || docList) {
+ keyList.push(key);
+ }
+ });
+ if (keyList.indexOf("data") !== -1) {
+ keyList.splice(keyList.indexOf("data"), 1);
+ }
+ keyList.splice(0, 0, "data");
+ return keyList;
+ }
/**
* Renders the EditableView title element for placement into the tree.
*/
@@ -166,14 +179,22 @@ class TreeView extends React.Component<TreeViewProps> {
let reference = React.createRef<HTMLDivElement>();
let onItemDown = SetupDrag(reference, () => this.props.document, this.move, this.props.dropAction, this.props.treeViewId, true);
+ let headerElements = (
+ <span className="collectionTreeView-keyHeader" key={this._chosenKey}
+ onPointerDown={action(() => {
+ let ind = this.keyList.indexOf(this._chosenKey);
+ ind = (ind + 1) % this.keyList.length;
+ this._chosenKey = this.keyList[ind];
+ })} >
+ {this._chosenKey}
+ </span>);
let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : [];
let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
<div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
<FontAwesomeIcon icon="angle-right" size="lg" />
- {/* <FontAwesomeIcon icon="angle-right" size="lg" /> */}
</div>);
return <>
- <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
+ <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
style={{
background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0",
pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
@@ -182,6 +203,7 @@ class TreeView extends React.Component<TreeViewProps> {
{this.editableView("title")}
{/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
</div >
+ {headerElements}
{openRight}
</>;
}
@@ -200,7 +222,7 @@ class TreeView extends React.Component<TreeViewProps> {
} else {
ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
}
- ContextMenu.Instance.displayMenu(e.pageX - 156, e.pageY - 15);
+ ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15);
e.stopPropagation();
}
}
@@ -209,7 +231,7 @@ class TreeView extends React.Component<TreeViewProps> {
let rect = this._header!.current!.getBoundingClientRect();
let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
let before = x[1] < bounds[1];
- let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible);
+ let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed);
if (de.data instanceof DragManager.DocumentDragData) {
let addDoc = (doc: Doc) => this.props.addDocument(doc, this.props.document, before);
if (inside) {
@@ -218,91 +240,66 @@ class TreeView extends React.Component<TreeViewProps> {
addDoc = (doc: Doc) => { docList && docList.push(doc); return true; };
}
}
- let added = false;
- if (de.data.dropAction || de.data.userDropAction) {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before) || added, false);
- } else if (de.data.moveDocument) {
- let movedDocs = de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments;
- added = movedDocs.reduce((added: boolean, d) =>
- de.data.moveDocument(d, this.props.document, addDoc) || added, false);
- } else {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before), false);
- }
e.stopPropagation();
- return added;
+ let movedDocs = (de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments);
+ return (de.data.dropAction || de.data.userDropAction) ?
+ de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before) || added, false)
+ : (de.data.moveDocument) ?
+ movedDocs.reduce((added: boolean, d) => de.data.moveDocument(d, this.props.document, addDoc) || added, false)
+ : de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before), false);
}
return false;
}
- public static AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean) {
- let list = Cast(target[key], listSpec(Doc));
- if (list) {
- let ind = relativeTo ? list.indexOf(relativeTo) : -1;
- if (ind === -1) list.push(doc);
- else list.splice(before ? ind : ind + 1, 0, doc);
- }
- return true;
+ docTransform = () => {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(this._dref.current!);
+ let outerXf = this.props.outerXf();
+ let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+ let finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
+ return finalXf;
}
- @observable _chosenKey: string = "data";
- _bulletType: BulletType = BulletType.List;
render() {
- let bulletType = BulletType.List;
- let contentElement: (JSX.Element | null)[] = [];
- let keys = Array.from(Object.keys(this.props.document));
- if (this.props.document.proto instanceof Doc) {
- keys.push(...Array.from(Object.keys(this.props.document.proto)));
- while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
- }
- if (keys.indexOf("data") !== -1) {
- keys.splice(keys.indexOf("data"), 1);
- keys.splice(0, 0, "data");
- }
- let keyList: string[] = [];
- keys.map(key => {
- let docList = Cast(this.props.document[key], listSpec(Doc));
- let doc = Cast(this.props.document[key], Doc);
- if (doc instanceof Doc || (docList && (DocListCast(docList).length > 0 || key === "data"))) {
- keyList.push(key);
- }
- });
- let headerElements = <div style={{ display: "block", marginTop: "7px" }} key={this._chosenKey}>{keyList.map(key =>
- <span className="collectionTreeView-keyHeader" key={key} onPointerDown={action(() => this._chosenKey = key)}
- style={{ display: "inline", marginRight: "3px", marginTop: "7px", background: key === this._chosenKey ? "lightgray" : undefined }}>
- {key}
- </span>)}
- </div>;
- [this._chosenKey].map(key => {
- let docList = Cast(this.props.document[key], listSpec(Doc));
- let remDoc = (doc: Doc) => this.remove(doc, key);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.document, key, doc, addBefore, before);
- let doc = Cast(this.props.document[key], Doc);
- if (doc instanceof Doc || (docList && (DocListCast(docList).length > 0 || key === "data"))) {
- if (!this._collapsed) {
- bulletType = BulletType.Collapsible;
- contentElement.push(<ul key={key + "more"}>
- {headerElements}
- <div style={{ display: "block" }}>
- {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, key, addDoc, remDoc, this.move,
- this.indent,
- this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active)}
- </div>
- </ul >);
- } else {
- bulletType = BulletType.Collapsed;
- }
+ let contentElement: (JSX.Element | null) = null;
+ let docList = Cast(this.props.document[this._chosenKey], listSpec(Doc));
+ let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this._chosenKey, doc, addBefore, before);
+ let doc = Cast(this.props.document[this._chosenKey], Doc);
+ let docWidth = () => NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 5) : this.props.panelWidth() - 5;
+ if (!this._collapsed) {
+ if (!this.props.document.embed) {
+ contentElement = <ul key={this._chosenKey + "more"}>
+ {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this._chosenKey, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth)}
+ </ul >;
+ } else {
+ contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.props.panelHeight() }} key={this.props.document[Id]}>
+ <CollectionSchemaPreview
+ Document={this.props.document}
+ width={docWidth}
+ height={this.props.panelHeight}
+ getTransform={this.docTransform}
+ CollectionView={undefined}
+ addDocument={emptyFunction as any}
+ moveDocument={this.props.moveDocument}
+ removeDocument={emptyFunction as any}
+ active={this.props.active}
+ whenActiveChanged={emptyFunction as any}
+ addDocTab={this.props.addDocTab}
+ setPreviewScript={emptyFunction}>
+ </CollectionSchemaPreview>
+ </div>;
}
- });
- this._bulletType = bulletType;
- return <div className="treeViewItem-container"
- ref={this.createTreeDropTarget}
- onContextMenu={this.onWorkspaceContextMenu}>
+ }
+ return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onContextMenu={this.onWorkspaceContextMenu}>
<li className="collection-child">
<div className="treeViewItem-header" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- {this.renderBullet(bulletType)}
+ {this.renderBullet()}
{this.renderTitle()}
</div>
- {contentElement}
+ <div className="treeViewItem-border">
+ {contentElement}
+ </div>
</li>
</div>;
}
@@ -311,35 +308,47 @@ class TreeView extends React.Component<TreeViewProps> {
treeViewId: string,
key: string,
add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean,
- remove: ((doc: Doc) => void),
+ remove: ((doc: Doc) => boolean),
move: DragManager.MoveFunction,
- indent: () => void,
dropAction: dropActionType,
addDocTab: (doc: Doc, where: string) => void,
screenToLocalXf: () => Transform,
- active: () => boolean
+ outerXf: () => { translateX: number, translateY: number },
+ active: () => boolean,
+ panelWidth: () => number,
) {
let docList = docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized));
+ let rowWidth = () => panelWidth() - 20;
return docList.map((child, i) => {
let indent = i === 0 ? undefined : () => {
if (StrCast(docList[i - 1].layout).indexOf("CollectionView") !== -1) {
let fieldKeysub = StrCast(docList[i - 1].layout).split("fieldKey")[1];
let fieldKey = fieldKeysub.split("\"")[1];
- TreeView.AddDocToList(docList[i - 1], fieldKey, child);
+ Doc.AddDocToList(docList[i - 1], fieldKey, child);
remove(child);
}
- }
+ };
+ let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
+ return add(doc, relativeTo ? relativeTo : docList[i], before !== undefined ? before : false);
+ };
+ let rowHeight = () => {
+ let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0);
+ return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym]();
+ };
return <TreeView
document={child}
treeViewId={treeViewId}
key={child[Id]}
indentDocument={indent}
deleteDoc={remove}
- addDocument={add}
+ addDocument={addDocument}
+ panelWidth={rowWidth}
+ panelHeight={rowHeight}
moveDocument={move}
dropAction={dropAction}
addDocTab={addDocTab}
ScreenToLocalTransform={screenToLocalXf}
+ outerXf={outerXf}
parentKey={key}
active={active} />;
});
@@ -349,19 +358,27 @@ class TreeView extends React.Component<TreeViewProps> {
@observer
export class CollectionTreeView extends CollectionSubView(Document) {
private treedropDisposer?: DragManager.DragDropDisposer;
+ private _mainEle?: HTMLDivElement;
+
protected createTreeDropTarget = (ele: HTMLDivElement) => {
- if (this.treedropDisposer) {
- this.treedropDisposer();
- }
- if (ele) {
+ this.treedropDisposer && this.treedropDisposer();
+ if (this._mainEle = ele) {
this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
}
}
+ componentWillUnmount() {
+ this.treedropDisposer && this.treedropDisposer();
+ }
+
@action
- remove = (document: Document) => {
+ remove = (document: Document): boolean => {
let children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
- children.indexOf(document) !== -1 && children.splice(children.indexOf(document), 1);
+ if (children.indexOf(document) !== -1) {
+ children.splice(children.indexOf(document), 1);
+ return true;
+ }
+ return false;
}
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
@@ -371,22 +388,16 @@ export class CollectionTreeView extends CollectionSubView(Document) {
}
}
- onTreeDrop = (e: React.DragEvent) => {
- this.onDrop(e, {});
- }
+ outerXf = () => Utils.GetScreenTransform(this._mainEle!);
+ onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
+
render() {
let dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
- if (!this.childDocs) {
- return (null);
- }
- let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
+ let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
- let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove,
- moveDoc, emptyFunction, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active);
- return (
+ return !this.childDocs ? (null) : (
<div id="body" className="collectionTreeView-dropTarget"
- style={{ borderRadius: "inherit" }}
onContextMenu={this.onContextMenu}
onWheel={(e: React.WheelEvent) => this.props.isSelected() && e.stopPropagation()}
onDrop={this.onTreeDrop}
@@ -397,14 +408,19 @@ export class CollectionTreeView extends CollectionSubView(Document) {
display={"inline"}
height={72}
GetValue={() => StrCast(this.props.Document.title)}
- SetValue={(value: string) => {
- let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
- target.title = value;
- return true;
+ SetValue={(value: string) => (Doc.GetProto(this.props.Document).title = value) ? true : true}
+ OnFillDown={(value: string) => {
+ Doc.GetProto(this.props.Document).title = value;
+ let doc = Docs.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ TreeView.loadId = doc[Id];
+ Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
}} />
</div>
<ul className="no-indent">
- {childElements}
+ {
+ TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove,
+ moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth)
+ }
</ul>
</div >
);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 84841e469..4b4e7465a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
import { action, computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { Doc, HeightSym, WidthSym, DocListCastAsync } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
@@ -26,6 +26,7 @@ import { MarqueeView } from "./MarqueeView";
import React = require("react");
import v5 = require("uuid/v5");
import PDFMenu from "../../pdf/PDFMenu";
+import { ContextMenu } from "../../ContextMenu";
export const panZoomSchema = createSchema({
panX: "number",
@@ -216,7 +217,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
} else {
// if (modes[e.deltaMode] === 'pixels') coefficient = 50;
// else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??
- let deltaScale = (1 - (e.deltaY / coefficient));
+ let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1;
if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
deltaScale = 1 / this.zoomScaling();
}
@@ -339,6 +340,33 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
+ onContextMenu = () => {
+ ContextMenu.Instance.addItem({
+ description: "Arrange contents in grid",
+ event: async () => {
+ const docs = await DocListCastAsync(this.Document[this.props.fieldKey]);
+ if (docs) {
+ let startX = this.Document.panX || 0;
+ let x = startX;
+ let y = this.Document.panY || 0;
+ let i = 0;
+ const width = Math.max(...docs.map(doc => NumCast(doc.width)));
+ const height = Math.max(...docs.map(doc => NumCast(doc.height)));
+ for (const doc of docs) {
+ doc.x = x;
+ doc.y = y;
+ x += width + 20;
+ if (++i === 6) {
+ i = 0;
+ x = startX;
+ y += height + 20;
+ }
+ }
+ }
+ }
+ });
+ }
+
private childViews = () => [
<CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
...this.views
@@ -349,7 +377,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return (
<div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
style={{ borderRadius: "inherit" }}
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onContextMenu={this.onContextMenu}>
<MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b1e0e62ea..796ff029c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -302,35 +302,22 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this.props.addLiveTextDocument(container);
// });
} else if (e.key === "S") {
- await htmlToImage.toPng(this._mainCont.current!, { width: bounds.width * zoomBasis, height: bounds.height * zoomBasis, quality: 0.2 }).then((dataUrl) => {
- selected.map(d => {
- this.props.removeDocument(d);
- d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
- d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
- d.page = -1;
- return d;
- });
- let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
- SearchBox.convertDataUri(dataUrl, "icon" + summary[Id] + "_image").then((returnedFilename) => {
- if (returnedFilename) {
- let url = DocServer.prepend(returnedFilename);
- let imageSummary = Docs.ImageDocument(url, {
- x: bounds.left, y: bounds.top + 100 / zoomBasis,
- width: 150, height: bounds.height / bounds.width * 150, title: "-summary image-"
- });
- summary.imageSummary = imageSummary;
- this.props.addDocument(imageSummary, false);
- }
- });
- newCollection.proto!.summaryDoc = summary;
- selected = [newCollection];
- newCollection.x = bounds.left + bounds.width;
- //this.props.addDocument(newCollection, false);
- summary.proto!.summarizedDocs = new List<Doc>(selected);
- summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight"
-
- this.props.addLiveTextDocument(summary);
+ selected.map(d => {
+ this.props.removeDocument(d);
+ d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+ d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+ d.page = -1;
+ return d;
});
+ let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+ newCollection.proto!.summaryDoc = summary;
+ selected = [newCollection];
+ newCollection.x = bounds.left + bounds.width;
+ //this.props.addDocument(newCollection, false);
+ summary.proto!.summarizedDocs = new List<Doc>(selected);
+ summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight"
+
+ this.props.addLiveTextDocument(summary);
}
else {
this.props.addDocument(newCollection, false);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4992669df..522c37989 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,5 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faUnlock, faLock, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faShare, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons';
+import * as fa from '@fortawesome/free-solid-svg-icons';
import { action, computed, IReactionDisposer, reaction, trace, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
@@ -34,23 +34,24 @@ import { ContextMenuProps } from '../ContextMenuItem';
import { RouteStore } from '../../../server/RouteStore';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
-library.add(faTrash);
-library.add(faShare);
-library.add(faExpandArrowsAlt);
-library.add(faCompressArrowsAlt);
-library.add(faLayerGroup);
-library.add(faAlignCenter);
-library.add(faCaretSquareRight);
-library.add(faSquare);
-library.add(faConciergeBell);
-library.add(faFolder);
-library.add(faMapPin);
-library.add(faLink);
-library.add(faFingerprint);
-library.add(faCrosshairs);
-library.add(faDesktop);
-library.add(faUnlock);
-library.add(faLock);
+library.add(fa.faTrash);
+library.add(fa.faShare);
+library.add(fa.faExpandArrowsAlt);
+library.add(fa.faCompressArrowsAlt);
+library.add(fa.faLayerGroup);
+library.add(fa.faExternalLinkAlt);
+library.add(fa.faAlignCenter);
+library.add(fa.faCaretSquareRight);
+library.add(fa.faSquare);
+library.add(fa.faConciergeBell);
+library.add(fa.faFolder);
+library.add(fa.faMapPin);
+library.add(fa.faLink);
+library.add(fa.faFingerprint);
+library.add(fa.faCrosshairs);
+library.add(fa.faDesktop);
+library.add(fa.faUnlock);
+library.add(fa.faLock);
const linkSchema = createSchema({
title: "string",
@@ -88,6 +89,7 @@ const schema = createSchema({
nativeWidth: "number",
nativeHeight: "number",
backgroundColor: "string",
+ hidden: "boolean"
});
export const positionSchema = createSchema({
@@ -235,7 +237,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
static _undoBatch?: UndoManager.Batch = undefined;
@action
- public collapseTargetsToPoint = async (scrpt: number[], expandedDocs: Doc[] | undefined): Promise<void> => {
+ public collapseTargetsToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => {
SelectionManager.DeselectAll();
if (expandedDocs) {
if (!DocumentView._undoBatch) {
@@ -446,7 +448,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.templates = this.templates;
}
- freezeNativeDimensions = (e: React.MouseEvent): void => {
+ freezeNativeDimensions = (): void => {
let proto = Doc.GetProto(this.props.Document);
if (proto.ignoreAspect === undefined && !proto.nativeWidth) {
proto.nativeWidth = this.props.PanelWidth();
@@ -475,7 +477,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" });
subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "caret-square-right" });
subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
- cm.addItem({ description: "Open...", subitems: subitems });
+ cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
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.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
@@ -537,6 +539,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
render() {
+ if (this.Document.hidden) {
+ return null;
+ }
var scaling = this.props.ContentScaling();
var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 421267b2a..391e42b57 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -415,6 +415,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "initial",
pointerEvents: interactive ? "all" : "none",
+ fontSize: "13px"
}}
onKeyDown={this.onKeyPress}
onFocus={this.onFocused}
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 00021bc78..d6ab2a34a 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -37,14 +37,14 @@ export class IconBox extends React.Component<FieldViewProps> {
return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
}
- setLabelField = (e: React.MouseEvent): void => {
+ setLabelField = (): void => {
this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel);
}
- setUseOwnTitleField = (e: React.MouseEvent): void => {
+ setUseOwnTitleField = (): void => {
this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle);
}
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (): void => {
ContextMenu.Instance.addItem({
description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon",
event: this.setLabelField
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 917be734d..3d626eef0 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -54,7 +54,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
field = res.result;
}
if (Field.IsField(field, true)) {
- let target = !eq ? doc : Doc.GetProto(doc);
+ let target = eq ? doc : Doc.GetProto(doc);
target[key] = field;
return true;
}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index dd1bca7f6..420a1ad94 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -61,10 +61,11 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
</td>
<td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }}>
<EditableView contents={contents} height={36} GetValue={() => {
+ const onDelegate = Object.keys(props.Document).includes(props.fieldKey);
let field = FieldValue(props.Document[props.fieldKey]);
if (Field.IsField(field)) {
- return Field.toScriptString(field);
+ return (onDelegate ? "=" : "") + Field.toScriptString(field);
}
return "";
}}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 7dd2b1dc5..d2de1cb1c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -97,7 +97,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
render() {
// uses mozilla pdf as default
const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"));
- let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
+ let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
return (
<div onScroll={this.onScroll}
style={{
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 2ed891131..39b15fb11 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -15,16 +15,20 @@ export default class PDFMenu extends React.Component {
@observable private _opacity: number = 1;
@observable private _transition: string = "opacity 0.5s";
@observable private _transitionDelay: string = "";
- @observable private _pinned: boolean = false;
+
+ @observable public Pinned: boolean = false;
StartDrag: (e: PointerEvent) => void = emptyFunction;
Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction;
- @observable Highlighting: boolean = false;
+ Delete: () => void = emptyFunction;
+
+ @observable public Highlighting: boolean = false;
+ @observable public Status: "pdf" | "annotation" | "" = "";
- private _timeout: NodeJS.Timeout | undefined;
private _offsetY: number = 0;
private _offsetX: number = 0;
private _mainCont: React.RefObject<HTMLDivElement>;
+ private _dragging: boolean = false;
constructor(props: Readonly<{}>) {
super(props);
@@ -35,27 +39,38 @@ export default class PDFMenu extends React.Component {
}
pointerDown = (e: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.StartDrag);
- document.addEventListener("pointermove", this.StartDrag);
+ document.removeEventListener("pointermove", this.pointerMove);
+ document.addEventListener("pointermove", this.pointerMove);
document.removeEventListener("pointerup", this.pointerUp);
document.addEventListener("pointerup", this.pointerUp);
- console.log(this.StartDrag);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ pointerMove = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
+
+ if (this._dragging) {
+ return;
+ }
+
+ this.StartDrag(e);
+ this._dragging = true;
}
pointerUp = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.StartDrag);
+ this._dragging = false;
+ document.removeEventListener("pointermove", this.pointerMove);
document.removeEventListener("pointerup", this.pointerUp);
e.stopPropagation();
e.preventDefault();
}
@action
- jumpTo = (x: number, y: number) => {
- if (!this._pinned) {
+ jumpTo = (x: number, y: number, forceJump: boolean = false) => {
+ if (!this.Pinned || forceJump) {
this._transition = this._transitionDelay = "";
this._opacity = 1;
this._left = x;
@@ -65,7 +80,7 @@ export default class PDFMenu extends React.Component {
@action
fadeOut = (forceOut: boolean) => {
- if (!this._pinned) {
+ if (!this.Pinned) {
if (this._opacity === 0.2) {
this._transition = "opacity 0.1s";
this._transitionDelay = "";
@@ -84,7 +99,7 @@ export default class PDFMenu extends React.Component {
@action
pointerLeave = (e: React.PointerEvent) => {
- if (!this._pinned) {
+ if (!this.Pinned) {
this._transition = "opacity 0.5s";
this._transitionDelay = "1s";
this._opacity = 0.2;
@@ -101,8 +116,8 @@ export default class PDFMenu extends React.Component {
@action
togglePin = (e: React.MouseEvent) => {
- this._pinned = !this._pinned;
- if (!this._pinned) {
+ this.Pinned = !this.Pinned;
+ if (!this.Pinned) {
this.Highlighting = false;
}
}
@@ -138,7 +153,7 @@ export default class PDFMenu extends React.Component {
@action
highlightClicked = (e: React.MouseEvent) => {
- if (!this._pinned) {
+ if (!this.Pinned) {
this.Highlight(undefined, "#f4f442");
}
else {
@@ -147,11 +162,35 @@ export default class PDFMenu extends React.Component {
}
}
+ deleteClicked = (e: React.PointerEvent) => {
+ this.Delete();
+ }
+
+ handleContextMenu = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
render() {
+ let buttons = this.Status === "pdf" ? [
+ <button className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked}
+ style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
+ <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
+ </button>,
+ <button className="pdfMenu-button" title="Drag to Annotate" onPointerDown={this.pointerDown}><FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
+ <button className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin}
+ style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
+ <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
+ </button>
+ ] : [
+ <button className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" /></button>
+ ];
+
return (
- <div className="pdfMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont}
+ <div className="pdfMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay }}>
- <button className="pdfMenu-button" title="Highlight" onClick={this.highlightClicked}
+ {buttons}
+ {/* <button className="pdfMenu-button" title="Highlight" onClick={this.highlightClicked}
style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
</button>
@@ -159,8 +198,8 @@ export default class PDFMenu extends React.Component {
<button className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin}
style={this._pinned ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this._pinned ? "rotate(45deg)" : "" }} />
- </button>
- <div className="pdfMenu-dragger" onPointerDown={this.dragStart} style={{ width: this._pinned ? "20px" : "0px" }} />
+ </button> */}
+ <div className="pdfMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
</div >
);
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 8c0aaea00..6adead626 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -7,7 +7,7 @@ import { Dictionary } from "typescript-collections";
import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../new_fields/Types";
import { emptyFunction } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from "../../documents/Documents";
@@ -19,6 +19,7 @@ import Page from "./Page";
import "./PDFViewer.scss";
import React = require("react");
import PDFMenu from "./PDFMenu";
+import { UndoManager } from "../../util/UndoManager";
export const scale = 2;
interface IPDFViewerProps {
@@ -90,7 +91,8 @@ class Viewer extends React.Component<IViewerProps> {
@action
componentDidMount = () => {
this._reactionDisposer = reaction(
- () => [this.props.parent.props.active(), this.startIndex, this.endIndex],
+
+ () => [this.props.parent.props.active(), this.startIndex, this._pageSizes.length ? this.endIndex : 0],
async () => {
await this.initialLoad();
this.renderPages();
@@ -114,12 +116,16 @@ class Viewer extends React.Component<IViewerProps> {
let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
this._isPage = Array<string>(this.props.pdf.numPages);
for (let i = 0; i < this.props.pdf.numPages; i++) {
- await this.props.pdf.getPage(i + 1).then(page => runInAction(() =>
- pageSizes[i] = { width: page.view[2] * scale, height: page.view[3] * scale }));
+ await this.props.pdf.getPage(i + 1).then(page => runInAction(() => {
+ // pageSizes[i] = { width: page.view[2] * scale, height: page.view[3] * scale };
+ let x = page.getViewport(scale);
+ pageSizes[i] = { width: x.width, height: x.height };
+ }));
}
runInAction(() =>
Array.from(Array((this._pageSizes = pageSizes).length).keys()).map(this.getPlaceholderPage));
- this.props.loaded(pageSizes[0].width, pageSizes[0].height, this.props.pdf.numPages);
+ this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages);
+ // this.props.loaded(Math.max(...pageSizes.map(i => i.width)), pageSizes[0].height, this.props.pdf.numPages);
}
}
@@ -132,6 +138,7 @@ class Viewer extends React.Component<IViewerProps> {
makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string): Doc => {
let annoDocs: Doc[] = [];
+ let mainAnnoDoc = new Doc();
this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => {
for (let anno of value) {
let annoDoc = new Doc();
@@ -141,6 +148,7 @@ class Viewer extends React.Component<IViewerProps> {
if (anno.style.width) annoDoc.width = parseInt(anno.style.width) / scale;
annoDoc.page = key;
annoDoc.target = sourceDoc;
+ annoDoc.group = mainAnnoDoc;
annoDoc.color = color;
annoDoc.type = AnnotationTypes.Region;
annoDocs.push(annoDoc);
@@ -148,13 +156,12 @@ class Viewer extends React.Component<IViewerProps> {
}
});
- let annoDoc = new Doc();
- annoDoc.annotations = new List<Doc>(annoDocs);
+ mainAnnoDoc.annotations = new List<Doc>(annoDocs);
if (sourceDoc) {
- DocUtils.MakeLink(sourceDoc, annoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title));
+ DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title));
}
this._savedAnnotations.clear();
- return annoDoc;
+ return mainAnnoDoc;
}
drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -185,7 +192,7 @@ class Viewer extends React.Component<IViewerProps> {
if (this._isPage[page] !== "none") {
this._isPage[page] = "none";
this._visibleElements[page] = (
- <div key={`placeholder-${page}`} className="pdfviewer-placeholder"
+ <div key={`${this.props.url}-placeholder-${page + 1}`} className="pdfviewer-placeholder"
style={{ width: this._pageSizes[page].width, height: this._pageSizes[page].height }} />
);
}
@@ -196,14 +203,15 @@ class Viewer extends React.Component<IViewerProps> {
this._isPage[page] = "page";
this._visibleElements[page] = (
<Page
+ size={this._pageSizes[page]}
pdf={this.props.pdf}
page={page}
numPages={this.props.pdf.numPages}
- key={`rendered-${page + 1}`}
+ key={`${this.props.url}-rendered-${page + 1}`}
name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`}
pageLoaded={this.pageLoaded}
parent={this.props.parent}
- makePin={this.createPinAnnotation}
+ makePin={emptyFunction}
renderAnnotations={this.renderAnnotations}
createAnnotation={this.createAnnotation}
sendAnnotations={this.receiveAnnotations}
@@ -236,7 +244,7 @@ class Viewer extends React.Component<IViewerProps> {
// endIndex: where to end rendering pages
@computed get endIndex(): number {
- return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY) + this._pageBuffer);
+ return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.scrollY + this._pageSizes[0].height) + this._pageBuffer);
}
@action
@@ -280,27 +288,27 @@ class Viewer extends React.Component<IViewerProps> {
return this._savedAnnotations.getValue(page);
}
- createPinAnnotation = (x: number, y: number, page: number): void => {
- let targetDoc = Docs.TextDocument({ width: 100, height: 50, title: "New Pin Annotation" });
- let pinAnno = new Doc();
- pinAnno.x = x;
- pinAnno.y = y + this.getScrollFromPage(page);
- pinAnno.width = pinAnno.height = PinRadius;
- pinAnno.page = page;
- pinAnno.target = targetDoc;
- pinAnno.type = AnnotationTypes.Pin;
- // this._annotations.push(pinAnno);
- let annoDoc = new Doc();
- annoDoc.annotations = new List<Doc>([pinAnno]);
- let annotations = DocListCast(this.props.parent.Document.annotations);
- if (annotations && annotations.length) {
- annotations.push(annoDoc);
- this.props.parent.Document.annotations = new List<Doc>(annotations);
- }
- else {
- this.props.parent.Document.annotations = new List<Doc>([annoDoc]);
- }
- }
+ // createPinAnnotation = (x: number, y: number, page: number): void => {
+ // let targetDoc = Docs.TextDocument({ width: 100, height: 50, title: "New Pin Annotation" });
+ // let pinAnno = new Doc();
+ // pinAnno.x = x;
+ // pinAnno.y = y + this.getScrollFromPage(page);
+ // pinAnno.width = pinAnno.height = PinRadius;
+ // pinAnno.page = page;
+ // pinAnno.target = targetDoc;
+ // pinAnno.type = AnnotationTypes.Pin;
+ // // this._annotations.push(pinAnno);
+ // let annoDoc = new Doc();
+ // annoDoc.annotations = new List<Doc>([pinAnno]);
+ // let annotations = DocListCast(this.props.parent.Document.annotations);
+ // if (annotations && annotations.length) {
+ // annotations.push(annoDoc);
+ // this.props.parent.Document.annotations = new List<Doc>(annotations);
+ // }
+ // else {
+ // this.props.parent.Document.annotations = new List<Doc>([annoDoc]);
+ // }
+ // }
// get the page index that the vertical offset passed in is on
getPageFromScroll = (vOffset: number) => {
@@ -334,26 +342,6 @@ class Viewer extends React.Component<IViewerProps> {
else {
this._savedAnnotations.setValue(page, [div]);
}
- PDFMenu.Instance.StartDrag = this.startDrag;
- }
- }
-
- startDrag = (e: PointerEvent) => {
- e.preventDefault();
- e.stopPropagation();
- let thisDoc = this.props.parent.Document;
- // document that this annotation is linked to
- let targetDoc = Docs.TextDocument({ width: 200, height: 200, title: "New Annotation" });
- targetDoc.targetPage = Math.min(...this._savedAnnotations.keys());
- let annotationDoc = this.makeAnnotationDocument(targetDoc, 1, "red");
- let dragData = new DragManager.AnnotationDragData(thisDoc, annotationDoc, targetDoc);
- if (this._annotationLayer.current) {
- DragManager.StartAnnotationDrag([this._annotationLayer.current], dragData, e.pageX, e.pageY, {
- handlers: {
- dragComplete: action(emptyFunction),
- },
- hideSource: false
- });
}
}
@@ -362,8 +350,8 @@ class Viewer extends React.Component<IViewerProps> {
let res = annotationDocs.map(a => {
let type = NumCast(a.type);
switch (type) {
- case AnnotationTypes.Pin:
- return <PinAnnotation parent={this} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
+ // case AnnotationTypes.Pin:
+ // return <PinAnnotation parent={this} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
case AnnotationTypes.Region:
return <RegionAnnotation parent={this} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />;
default:
@@ -394,7 +382,7 @@ class Viewer extends React.Component<IViewerProps> {
}
export enum AnnotationTypes {
- Region, Pin
+ Region
}
interface IAnnotationProps {
@@ -406,132 +394,193 @@ interface IAnnotationProps {
document: Doc;
}
-@observer
-class PinAnnotation extends React.Component<IAnnotationProps> {
- @observable private _backgroundColor: string = "green";
- @observable private _display: string = "initial";
+// @observer
+// class PinAnnotation extends React.Component<IAnnotationProps> {
+// @observable private _backgroundColor: string = "green";
+// @observable private _display: string = "initial";
+
+// private _mainCont: React.RefObject<HTMLDivElement>;
+
+// constructor(props: IAnnotationProps) {
+// super(props);
+// this._mainCont = React.createRef();
+// }
+
+// componentDidMount = () => {
+// let selected = this.props.document.selected;
+// if (!BoolCast(selected)) {
+// runInAction(() => {
+// this._backgroundColor = "red";
+// this._display = "none";
+// });
+// }
+// if (selected) {
+// if (BoolCast(selected)) {
+// runInAction(() => {
+// this._backgroundColor = "green";
+// this._display = "initial";
+// });
+// }
+// else {
+// runInAction(() => {
+// this._backgroundColor = "red";
+// this._display = "none";
+// });
+// }
+// }
+// else {
+// runInAction(() => {
+// this._backgroundColor = "red";
+// this._display = "none";
+// });
+// }
+// }
+
+// @action
+// pointerDown = (e: React.PointerEvent) => {
+// let selected = this.props.document.selected;
+// if (selected && BoolCast(selected)) {
+// this._backgroundColor = "red";
+// this._display = "none";
+// this.props.document.selected = false;
+// }
+// else {
+// this._backgroundColor = "green";
+// this._display = "initial";
+// this.props.document.selected = true;
+// }
+// e.preventDefault();
+// e.stopPropagation();
+// }
+
+// @action
+// doubleClick = (e: React.MouseEvent) => {
+// if (this._mainCont.current) {
+// let annotations = DocListCast(this.props.parent.props.parent.Document.annotations);
+// if (annotations && annotations.length) {
+// let index = annotations.indexOf(this.props.document);
+// annotations.splice(index, 1);
+// this.props.parent.props.parent.Document.annotations = new List<Doc>(annotations);
+// }
+// // this._mainCont.current.childNodes.forEach(e => e.remove());
+// this._mainCont.current.style.display = "none";
+// // if (this._mainCont.current.parentElement) {
+// // this._mainCont.current.remove();
+// // }
+// }
+// e.stopPropagation();
+// }
+
+// render() {
+// let targetDoc = Cast(this.props.document.target, Doc);
+// if (targetDoc instanceof Doc) {
+// return (
+// <div className="pdfViewer-pinAnnotation" onPointerDown={this.pointerDown}
+// onDoubleClick={this.doubleClick} ref={this._mainCont}
+// style={{
+// top: this.props.y * scale - PinRadius / 2, left: this.props.x * scale - PinRadius / 2, width: PinRadius,
+// height: PinRadius, pointerEvents: "all", backgroundColor: this._backgroundColor
+// }}>
+// <div style={{
+// position: "absolute", top: "25px", left: "25px", transform: "scale(3)", transformOrigin: "top left",
+// display: this._display, width: targetDoc[WidthSym](), height: targetDoc[HeightSym]()
+// }}>
+// <DocumentView Document={targetDoc}
+// ContainingCollectionView={undefined}
+// ScreenToLocalTransform={this.props.parent.props.parent.props.ScreenToLocalTransform}
+// isTopMost={false}
+// ContentScaling={() => 1}
+// PanelWidth={() => NumCast(this.props.parent.props.parent.Document.nativeWidth)}
+// PanelHeight={() => NumCast(this.props.parent.props.parent.Document.nativeHeight)}
+// focus={emptyFunction}
+// selectOnLoad={false}
+// parentActive={this.props.parent.props.parent.props.active}
+// whenActiveChanged={this.props.parent.props.parent.props.whenActiveChanged}
+// bringToFront={emptyFunction}
+// addDocTab={this.props.parent.props.parent.props.addDocTab}
+// />
+// </div>
+// </div >
+// );
+// }
+// return null;
+// }
+// }
+
+class RegionAnnotation extends React.Component<IAnnotationProps> {
+ @observable private _backgroundColor: string = "red";
+ private _reactionDisposer?: IReactionDisposer;
private _mainCont: React.RefObject<HTMLDivElement>;
constructor(props: IAnnotationProps) {
super(props);
+
this._mainCont = React.createRef();
}
- componentDidMount = () => {
- let selected = this.props.document.selected;
- if (!BoolCast(selected)) {
- runInAction(() => {
- this._backgroundColor = "red";
- this._display = "none";
- });
- }
- if (selected) {
- if (BoolCast(selected)) {
- runInAction(() => {
- this._backgroundColor = "green";
- this._display = "initial";
- });
- }
- else {
- runInAction(() => {
- this._backgroundColor = "red";
- this._display = "none";
- });
- }
- }
- else {
- runInAction(() => {
- this._backgroundColor = "red";
- this._display = "none";
- });
- }
+ componentDidMount() {
+ this._reactionDisposer = reaction(
+ () => BoolCast(this.props.document.delete),
+ () => {
+ if (BoolCast(this.props.document.delete)) {
+ if (this._mainCont.current) {
+ this._mainCont.current.style.display = "none";
+ }
+ }
+ },
+ { fireImmediately: true }
+ );
}
- @action
- pointerDown = (e: React.PointerEvent) => {
- let selected = this.props.document.selected;
- if (selected && BoolCast(selected)) {
- this._backgroundColor = "red";
- this._display = "none";
- this.props.document.selected = false;
- }
- else {
- this._backgroundColor = "green";
- this._display = "initial";
- this.props.document.selected = true;
- }
- e.preventDefault();
- e.stopPropagation();
+ componentWillUnmount() {
+ this._reactionDisposer && this._reactionDisposer();
}
- @action
- doubleClick = (e: React.MouseEvent) => {
- if (this._mainCont.current) {
- let annotations = DocListCast(this.props.parent.props.parent.Document.annotations);
- if (annotations && annotations.length) {
- let index = annotations.indexOf(this.props.document);
- annotations.splice(index, 1);
- this.props.parent.props.parent.Document.annotations = new List<Doc>(annotations);
- }
- // this._mainCont.current.childNodes.forEach(e => e.remove());
- this._mainCont.current.style.display = "none";
- // if (this._mainCont.current.parentElement) {
- // this._mainCont.current.remove();
- // }
+ deleteAnnotation = () => {
+ let annotation = DocListCast(this.props.parent.props.parent.Document.annotations);
+ let group = FieldValue(Cast(this.props.document.group, Doc));
+ if (group && annotation.indexOf(group) !== -1) {
+ let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc)));
+ this.props.parent.props.parent.Document.annotations = new List<Doc>(newAnnotations);
}
- e.stopPropagation();
- }
- render() {
- let targetDoc = Cast(this.props.document.target, Doc);
- if (targetDoc instanceof Doc) {
- return (
- <div className="pdfViewer-pinAnnotation" onPointerDown={this.pointerDown}
- onDoubleClick={this.doubleClick} ref={this._mainCont}
- style={{
- top: this.props.y * scale - PinRadius / 2, left: this.props.x * scale - PinRadius / 2, width: PinRadius,
- height: PinRadius, pointerEvents: "all", backgroundColor: this._backgroundColor
- }}>
- <div style={{
- position: "absolute", top: "25px", left: "25px", transform: "scale(3)", transformOrigin: "top left",
- display: this._display, width: targetDoc[WidthSym](), height: targetDoc[HeightSym]()
- }}>
- <DocumentView Document={targetDoc}
- ContainingCollectionView={undefined}
- ScreenToLocalTransform={this.props.parent.props.parent.props.ScreenToLocalTransform}
- isTopMost={false}
- ContentScaling={() => 1}
- PanelWidth={() => NumCast(this.props.parent.props.parent.Document.nativeWidth)}
- PanelHeight={() => NumCast(this.props.parent.props.parent.Document.nativeHeight)}
- focus={emptyFunction}
- selectOnLoad={false}
- parentActive={this.props.parent.props.parent.props.active}
- whenActiveChanged={this.props.parent.props.parent.props.whenActiveChanged}
- bringToFront={emptyFunction}
- addDocTab={this.props.parent.props.parent.props.addDocTab}
- />
- </div>
- </div >
- );
+ if (group) {
+ let groupAnnotations = DocListCast(group.annotations);
+ groupAnnotations.forEach(anno => anno.delete = true);
}
- return null;
+
+ PDFMenu.Instance.fadeOut(true);
}
-}
-class RegionAnnotation extends React.Component<IAnnotationProps> {
- @observable private _backgroundColor: string = "red";
- onPointerDown = (e: React.MouseEvent) => {
- let targetDoc = Cast(this.props.document.target, Doc, null);
- if (targetDoc) {
- DocumentManager.Instance.jumpToDocument(targetDoc);
+ // annotateThis = (e: PointerEvent) => {
+ // e.preventDefault();
+ // e.stopPropagation();
+ // // document that this annotation is linked to
+ // let targetDoc = Docs.TextDocument({ width: 200, height: 200, title: "New Annotation" });
+ // let group = FieldValue(Cast(this.props.document.group, Doc));
+ // }
+
+ @action
+ onPointerDown = (e: React.PointerEvent) => {
+ if (e.button === 0) {
+ let targetDoc = Cast(this.props.document.target, Doc, null);
+ if (targetDoc) {
+ DocumentManager.Instance.jumpToDocument(targetDoc);
+ }
+ }
+ if (e.button === 2) {
+ PDFMenu.Instance.Status = "annotation";
+ PDFMenu.Instance.Delete = this.deleteAnnotation;
+ PDFMenu.Instance.Pinned = false;
+ PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true);
}
}
render() {
return (
- <div className="pdfViewer-annotationBox" onClick={this.onPointerDown}
+ <div className="pdfViewer-annotationBox" onPointerDown={this.onPointerDown} ref={this._mainCont}
style={{ top: this.props.y * scale, left: this.props.x * scale, width: this.props.width * scale, height: this.props.height * scale, pointerEvents: "all", backgroundColor: StrCast(this.props.document.color) }}></div>
);
}
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index a19b64eda..b6f362702 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -15,9 +15,11 @@ import { listSpec } from "../../../new_fields/Schema";
import { menuBar } from "prosemirror-menu";
import { AnnotationTypes, PDFViewer, scale } from "./PDFViewer";
import PDFMenu from "./PDFMenu";
+import { UndoManager } from "../../util/UndoManager";
interface IPageProps {
+ size: { width: number, height: number };
pdf: Opt<Pdfjs.PDFDocumentProxy>;
name: string;
numPages: number;
@@ -35,8 +37,8 @@ interface IPageProps {
@observer
export default class Page extends React.Component<IPageProps> {
@observable private _state: string = "N/A";
- @observable private _width: number = 0;
- @observable private _height: number = 0;
+ @observable private _width: number = this.props.size.width;
+ @observable private _height: number = this.props.size.height;
@observable private _page: Opt<Pdfjs.PDFPageProxy>;
@observable private _currPage: number = this.props.page + 1;
@observable private _marqueeX: number = 0;
@@ -51,7 +53,6 @@ export default class Page extends React.Component<IPageProps> {
private _marquee: React.RefObject<HTMLDivElement>;
private _curly: React.RefObject<HTMLImageElement>;
private _marqueeing: boolean = false;
- private _dragging: boolean = false;
private _reactionDisposer?: IReactionDisposer;
constructor(props: IPageProps) {
@@ -151,13 +152,8 @@ export default class Page extends React.Component<IPageProps> {
*/
@action
startDrag = (e: PointerEvent): void => {
- // the first 5 lines is a hack to prevent text selection while dragging
e.preventDefault();
e.stopPropagation();
- if (this._dragging) {
- return;
- }
- this._dragging = true;
let thisDoc = this.props.parent.Document;
// document that this annotation is linked to
let targetDoc = Docs.TextDocument({ width: 200, height: 200, title: "New Annotation" });
@@ -168,7 +164,7 @@ export default class Page extends React.Component<IPageProps> {
if (this._textLayer.current) {
DragManager.StartAnnotationDrag([this._textLayer.current], dragData, e.pageX, e.pageY, {
handlers: {
- dragComplete: action(emptyFunction),
+ dragComplete: emptyFunction,
},
hideSource: false
});
@@ -179,7 +175,6 @@ export default class Page extends React.Component<IPageProps> {
endDrag = (e: PointerEvent): void => {
// document.removeEventListener("pointermove", this.startDrag);
// document.removeEventListener("pointerup", this.endDrag);
- this._dragging = false;
e.stopPropagation();
}
@@ -195,6 +190,9 @@ export default class Page extends React.Component<IPageProps> {
// document.addEventListener("pointerup", this.endDrag);
}
else if (e.button === 0) {
+ PDFMenu.Instance.StartDrag = this.startDrag;
+ PDFMenu.Instance.Highlight = this.highlight;
+ PDFMenu.Instance.Status = "pdf";
PDFMenu.Instance.fadeOut(true);
let target: any = e.target;
if (target && target.parentElement === this._textLayer.current) {
@@ -329,68 +327,6 @@ export default class Page extends React.Component<IPageProps> {
PDFMenu.Instance.StartDrag = this.startDrag;
PDFMenu.Instance.Highlight = this.highlight;
}
- // let x = (e.clientX - boundingRect.left) * (current.offsetWidth / boundingRect.width);
- // let y = (e.clientY - boundingRect.top) * (current.offsetHeight / boundingRect.height);
- // if (this._marqueeing) {
- // this._marqueeing = false;
- // if (this._marquee.current) {
- // let copy = document.createElement("div");
- // // make a copy of the marquee
- // copy.style.left = this._marquee.current.style.left;
- // copy.style.top = this._marquee.current.style.top;
- // copy.style.width = this._marquee.current.style.width;
- // copy.style.height = this._marquee.current.style.height;
-
- // // apply the appropriate background, opacity, and transform
- // let { background, opacity, transform } = this.getCurlyTransform();
- // copy.style.background = background;
- // // if curly bracing, add a curly brace
- // if (opacity === "1" && this._curly.current) {
- // copy.style.opacity = opacity;
- // let img = this._curly.current.cloneNode();
- // (img as any).style.opacity = opacity;
- // (img as any).style.transform = transform;
- // copy.appendChild(img);
- // }
- // else {
- // copy.style.opacity = this._marquee.current.style.opacity;
- // }
- // copy.className = this._marquee.current.className;
- // this.props.createAnnotation(copy, this.props.page);
- // this._marquee.current.style.opacity = "0";
- // }
-
- // this._marqueeHeight = this._marqueeWidth = 0;
- // }
- // else {
- // let sel = window.getSelection();
- // // if selecting over a range of things
- // if (sel && sel.type === "Range") {
- // let clientRects = sel.getRangeAt(0).getClientRects();
- // if (this._textLayer.current) {
- // let boundingRect = this._textLayer.current.getBoundingClientRect();
- // for (let i = 0; i < clientRects.length; i++) {
- // let rect = clientRects.item(i);
- // if (rect) {
- // let annoBox = document.createElement("div");
- // annoBox.className = "pdfViewer-annotationBox";
- // // transforms the positions from screen onto the pdf div
- // annoBox.style.top = ((rect.top - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)).toString();
- // annoBox.style.left = ((rect.left - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)).toString();
- // annoBox.style.width = (rect.width * this._textLayer.current.offsetWidth / boundingRect.width).toString();
- // annoBox.style.height = (rect.height * this._textLayer.current.offsetHeight / boundingRect.height).toString();
- // this.props.createAnnotation(annoBox, this.props.page);
- // }
- // }
- // }
- // // clear selection
- // if (sel.empty) { // Chrome
- // sel.empty();
- // } else if (sel.removeAllRanges) { // Firefox
- // sel.removeAllRanges();
- // }
- // }
- // }
document.removeEventListener("pointermove", this.onSelectStart);
document.removeEventListener("pointerup", this.onSelectEnd);
}
@@ -403,7 +339,6 @@ export default class Page extends React.Component<IPageProps> {
for (let i = 0; i < clientRects.length; i++) {
let rect = clientRects.item(i);
if (rect && rect.width !== this._textLayer.current.getBoundingClientRect().width && rect.height !== this._textLayer.current.getBoundingClientRect().height) {
- console.log(rect);
let annoBox = document.createElement("div");
annoBox.className = "pdfViewer-annotationBox";
// transforms the positions from screen onto the pdf div
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 1b0ff812f..9bacf49ba 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -207,7 +207,7 @@ export namespace Doc {
// gets the document's prototype or returns the document if it is a prototype
export function GetProto(doc: Doc) {
- return Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : doc.proto!;
+ return Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc);
}
export function allKeys(doc: Doc): string[] {
@@ -222,6 +222,16 @@ export namespace Doc {
return Array.from(results);
}
+ export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean) {
+ let list = Cast(target[key], listSpec(Doc));
+ if (list) {
+ let ind = relativeTo ? list.indexOf(relativeTo) : -1;
+ if (ind === -1) list.push(doc);
+ else list.splice(before ? ind : ind + 1, 0, doc);
+ }
+ return true;
+ }
+
export function MakeAlias(doc: Doc) {
if (!GetT(doc, "isPrototype", "boolean", true)) {
return Doc.MakeCopy(doc);
diff --git a/src/server/Search.ts b/src/server/Search.ts
index fd6ef36a6..d776480c6 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -7,7 +7,6 @@ export class Search {
private url = 'http://localhost:8983/solr/';
public async updateDocument(document: any) {
- return;
try {
const res = await rp.post(this.url + "dash/update", {
headers: { 'content-type': 'application/json' },
diff --git a/src/server/index.ts b/src/server/index.ts
index 2901f61ed..e645e29b4 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,3 +1,4 @@
+require('dotenv').config();
import * as bodyParser from 'body-parser';
import { exec } from 'child_process';
import * as cookieParser from 'cookie-parser';
@@ -45,6 +46,17 @@ const probe = require("probe-image-size");
const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));
+const release = process.env.RELEASE === "true";
+if (process.env.RELEASE === "true") {
+ console.log("Running server in release mode");
+} else {
+ console.log("Running server in debug mode");
+}
+console.log(process.env.PWD);
+let clientUtils = fs.readFileSync("./src/client/util/ClientUtils.ts.temp", "utf8");
+clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(release))}`;
+fs.writeFileSync("./src/client/util/ClientUtils.ts", clientUtils, "utf8");
+
const mongoUrl = 'mongodb://localhost:27017/Dash';
mongoose.connect(mongoUrl);
mongoose.connection.on('connected', () => console.log("connected"));
@@ -406,11 +418,21 @@ app.post(RouteStore.reset, postReset);
app.use(RouteStore.corsProxy, (req, res) =>
req.pipe(request(req.url.substring(1))).pipe(res));
-app.get(RouteStore.delete, (req, res) =>
- deleteFields().then(() => res.redirect(RouteStore.home)));
+app.get(RouteStore.delete, (req, res) => {
+ if (release) {
+ res.send("no");
+ return;
+ }
+ deleteFields().then(() => res.redirect(RouteStore.home));
+});
-app.get(RouteStore.deleteAll, (req, res) =>
- deleteAll().then(() => res.redirect(RouteStore.home)));
+app.get(RouteStore.deleteAll, (req, res) => {
+ if (release) {
+ res.send("no");
+ return;
+ }
+ deleteAll().then(() => res.redirect(RouteStore.home));
+});
app.use(wdm(compiler, { publicPath: config.output.publicPath }));
@@ -435,7 +457,9 @@ server.on("connection", function (socket: Socket) {
Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
- Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
+ if (!release) {
+ Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
+ }
Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));