aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorusodhi <61431818+usodhi@users.noreply.github.com>2020-04-27 18:20:22 +0530
committerusodhi <61431818+usodhi@users.noreply.github.com>2020-04-27 18:20:22 +0530
commit7a51214ef9b5b387c614b0139987a2c94d49b902 (patch)
treeaf148c045205d5f165336c69ff3295fa0c699ada /src
parent20beef06518bfa08db60b8c5a06c637dab0f2b92 (diff)
parent0dfe3d9f0790f499b380cbafcef40cfbe0c7ad29 (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into grid_view_secondary
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts12
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts21
-rw-r--r--src/client/documents/Documents.ts10
-rw-r--r--src/client/util/DragManager.ts38
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts18
-rw-r--r--src/client/util/RichTextRules.ts2
-rw-r--r--src/client/util/RichTextSchema.tsx27
-rw-r--r--src/client/views/ContextMenu.scss47
-rw-r--r--src/client/views/ContextMenu.tsx9
-rw-r--r--src/client/views/ContextMenuItem.tsx16
-rw-r--r--src/client/views/DocumentDecorations.scss2
-rw-r--r--src/client/views/DocumentDecorations.tsx30
-rw-r--r--src/client/views/EditableView.tsx2
-rw-r--r--src/client/views/TemplateMenu.tsx18
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx2
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx2
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx53
-rw-r--r--src/client/views/collections/CollectionPileView.scss8
-rw-r--r--src/client/views/collections/CollectionPileView.tsx127
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx8
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx7
-rw-r--r--src/client/views/collections/CollectionSubView.tsx13
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx5
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx51
-rw-r--r--src/client/views/collections/CollectionView.tsx11
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx167
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss11
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx166
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx31
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx31
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx31
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx15
-rw-r--r--src/client/views/nodes/ColorBox.scss1
-rw-r--r--src/client/views/nodes/DocumentBox.tsx6
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx67
-rw-r--r--src/client/views/nodes/DocumentView.tsx182
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss1
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx132
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.scss9
-rw-r--r--src/client/views/nodes/ImageBox.tsx14
-rw-r--r--src/client/views/nodes/LabelBox.scss2
-rw-r--r--src/client/views/nodes/LabelBox.tsx13
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx1
-rw-r--r--src/client/views/nodes/PresBox.scss5
-rw-r--r--src/client/views/nodes/RadialMenu.scss13
-rw-r--r--src/client/views/nodes/RadialMenu.tsx9
-rw-r--r--src/client/views/nodes/SliderBox.tsx2
-rw-r--r--src/client/views/nodes/WebBox.tsx3
-rw-r--r--src/client/views/pdf/Annotation.tsx1
-rw-r--r--src/client/views/pdf/PDFViewer.tsx43
-rw-r--r--src/new_fields/Doc.ts91
-rw-r--r--src/new_fields/RichTextUtils.ts32
-rw-r--r--src/new_fields/documentSchemas.ts5
-rw-r--r--src/scraping/buxton/final/BuxtonImporter.ts43
-rw-r--r--src/server/authentication/models/current_user_utils.ts112
59 files changed, 1121 insertions, 667 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 9acdc8731..ad12c68a1 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -504,8 +504,9 @@ export function setupMoveUpEvents(
e: React.PointerEvent,
moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
upEvent: (e: PointerEvent) => void,
- clickEvent: (e: PointerEvent) => void,
- stopPropagation: boolean = true
+ clickEvent: (e: PointerEvent, doubleTap?: boolean) => void,
+ stopPropagation: boolean = true,
+ stopMovePropagation: boolean = true
) {
(target as any)._downX = (target as any)._lastX = e.clientX;
(target as any)._downY = (target as any)._lastY = e.clientY;
@@ -520,12 +521,15 @@ export function setupMoveUpEvents(
}
(target as any)._lastX = e.clientX;
(target as any)._lastY = e.clientY;
- e.stopPropagation();
+ stopMovePropagation && e.stopPropagation();
};
+ (target as any)._doubleTap = false;
const _upEvent = (e: PointerEvent): void => {
+ (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < 300);
+ (target as any)._lastTap = Date.now();
upEvent(e);
if (Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4) {
- clickEvent(e);
+ clickEvent(e, (target as any)._doubleTap);
}
document.removeEventListener("pointermove", _moveEvent);
document.removeEventListener("pointerup", _upEvent);
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 7c4137f59..8c0149a89 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -1,19 +1,18 @@
-import { Utils } from "../../../Utils";
-import { ImageField } from "../../../new_fields/URLField";
-import { Cast, StrCast } from "../../../new_fields/Types";
-import { Doc, Opt, DocListCastAsync } from "../../../new_fields/Doc";
+import { AssertionError } from "assert";
+import { EditorState } from "prosemirror-state";
+import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import Photos = require('googlephotos');
import { RichTextField } from "../../../new_fields/RichTextField";
import { RichTextUtils } from "../../../new_fields/RichTextUtils";
-import { EditorState } from "prosemirror-state";
-import { FormattedTextBox } from "../../views/nodes/FormattedTextBox";
+import { Cast, StrCast } from "../../../new_fields/Types";
+import { ImageField } from "../../../new_fields/URLField";
+import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/SharedTypes";
+import { Utils } from "../../../Utils";
import { Docs, DocumentOptions } from "../../documents/Documents";
-import { NewMediaItemResult, MediaItem } from "../../../server/apis/google/SharedTypes";
-import { AssertionError } from "assert";
-import { DocumentView } from "../../views/nodes/DocumentView";
import { Networking } from "../../Network";
+import { FormattedTextBox } from "../../views/nodes/FormattedTextBox";
import GoogleAuthenticationManager from "../GoogleAuthenticationManager";
+import Photos = require('googlephotos');
export namespace GooglePhotos {
@@ -340,7 +339,7 @@ export namespace GooglePhotos {
const url = data.url.href;
const target = Doc.MakeAlias(source);
const description = parseDescription(target, descriptionKey);
- await DocumentView.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
+ await Doc.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
media.push({ url, description });
}
if (media.length) {
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5ff8f29ec..c64916897 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -110,7 +110,8 @@ export interface DocumentOptions {
isBackground?: boolean;
isLinkButton?: boolean;
columnWidth?: number;
- fontSize?: number;
+ _fontSize?: number;
+ _fontFamily?: string;
curPage?: number;
currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds
displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
@@ -146,6 +147,7 @@ export interface DocumentOptions {
treeViewHideTitle?: boolean; // whether to hide the title of a tree view
treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
treeViewOpen?: boolean; // whether this document is expanded in a tree view
+ treeViewExpandedView?: string; // which field/thing is displayed when this item is opened in tree view
treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked
limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
// [key: string]: Opt<Field>;
@@ -612,6 +614,10 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Freeform }, id);
}
+ export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Pile }, id);
+ }
+
export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Linear }, id);
}
@@ -973,7 +979,7 @@ export namespace DocUtils {
export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number): void {
ContextMenu.Instance.addItem({
description: "Add Note ...",
- subitems: DocListCast((Doc.UserDoc().noteTypes as Doc).data).map((note, i) => ({
+ subitems: DocListCast((Doc.UserDoc()["template-notes"] as Doc).data).map((note, i) => ({
description: ":" + StrCast(note.title),
event: (args: { x: number, y: number }) => {
const textDoc = Docs.Create.TextDocument("", {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 42a78a4bf..35694a6bd 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,5 +1,5 @@
import { Doc, Field, DocListCast } from "../../new_fields/Doc";
-import { Cast, ScriptCast } from "../../new_fields/Types";
+import { Cast, ScriptCast, StrCast } from "../../new_fields/Types";
import { emptyFunction } from "../../Utils";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import * as globalCssVariables from "../views/globalCssVariables.scss";
@@ -83,6 +83,7 @@ export namespace DragManager {
}
export let AbortDrag: () => void = emptyFunction;
export type MoveFunction = (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+ export type RemoveFunction = (document: Doc) => boolean;
export interface DragDropDisposer { (): void; }
export interface DragOptions {
@@ -138,6 +139,7 @@ export namespace DragManager {
userDropAction: dropActionType;
embedDoc?: boolean;
moveDocument?: MoveFunction;
+ removeDocument?: RemoveFunction;
isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts
}
export class LinkDragData {
@@ -177,7 +179,8 @@ export namespace DragManager {
export function MakeDropTarget(
element: HTMLElement,
- dropFunc: (e: Event, de: DropEvent) => void
+ dropFunc: (e: Event, de: DropEvent) => void,
+ doc?: Doc
): DragDropDisposer {
if ("canDrop" in element.dataset) {
throw new Error(
@@ -185,10 +188,18 @@ export namespace DragManager {
);
}
element.dataset.canDrop = "true";
- const handler = (e: Event) => { dropFunc(e, (e as CustomEvent<DropEvent>).detail); };
+ const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
+ const preDropHandler = (e: Event) => {
+ const de = (e as CustomEvent<DropEvent>).detail;
+ if (de.complete.docDragData && doc?.targetDropAction) {
+ de.complete.docDragData.dropAction = StrCast(doc.targetDropAction) as dropActionType;
+ }
+ };
element.addEventListener("dashOnDrop", handler);
+ doc && element.addEventListener("dashPreDrop", preDropHandler);
return () => {
element.removeEventListener("dashOnDrop", handler);
+ doc && element.removeEventListener("dashPreDrop", preDropHandler);
delete element.dataset.canDrop;
};
}
@@ -351,12 +362,17 @@ export namespace DragManager {
let lastX = downX;
let lastY = downY;
+ let alias = "alias";
const moveHandler = (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (dragData instanceof DocumentDragData) {
dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined;
}
if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) {
+ !dragData.dropAction && (dragData.dropAction = alias);
+ if (dragData.dropAction === "move") {
+ dragData.removeDocument?.(dragData.draggedDocuments[0]);
+ }
AbortDrag();
finishDrag?.(new DragCompleteEvent(true, dragData));
CollectionDockingView.Instance.StartOtherDrag({
@@ -366,7 +382,7 @@ export namespace DragManager {
button: 0
}, dragData.droppedDocuments);
}
- //TODO: Why can't we use e.movementX and e.movementY?
+ alias = "move";
const moveX = e.pageX - lastX;
const moveY = e.pageY - lastY;
lastX = e.pageX;
@@ -418,6 +434,20 @@ export namespace DragManager {
});
if (target) {
const complete = new DragCompleteEvent(false, dragData);
+ target.dispatchEvent(
+ new CustomEvent<DropEvent>("dashPreDrop", {
+ bubbles: true,
+ detail: {
+ x: e.x,
+ y: e.y,
+ complete: complete,
+ shiftKey: e.shiftKey,
+ altKey: e.altKey,
+ metaKey: e.metaKey,
+ ctrlKey: e.ctrlKey
+ }
+ })
+ );
finishDrag?.(complete);
target.dispatchEvent(
new CustomEvent<DropEvent>("dashOnDrop", {
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 680f48f70..356f20ce6 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -7,7 +7,7 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
import { EditorState, Transaction, TextSelection } from "prosemirror-state";
import { SelectionManager } from "./SelectionManager";
import { Docs } from "../documents/Documents";
-import { NumCast, BoolCast, Cast } from "../../new_fields/Types";
+import { NumCast, BoolCast, Cast, StrCast } from "../../new_fields/Types";
import { Doc } from "../../new_fields/Doc";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
import { Id } from "../../new_fields/FieldSymbols";
@@ -153,10 +153,16 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const layoutDoc = props.Document;
const originalDoc = layoutDoc.rootDocument || layoutDoc;
if (originalDoc instanceof Doc) {
+ const layoutKey = StrCast(originalDoc.layoutKey);
const newDoc = Docs.Create.TextDocument("", {
- layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+ layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+ layoutKey,
+ _singleLine: BoolCast(originalDoc._singleLine),
x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
});
+ if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+ newDoc[layoutKey] = originalDoc[layoutKey];
+ }
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
}
@@ -171,10 +177,16 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const layoutDoc = props.Document;
const originalDoc = layoutDoc.rootDocument || layoutDoc;
if (force || props.Document._singleLine) {
+ const layoutKey = StrCast(originalDoc.layoutKey);
const newDoc = Docs.Create.TextDocument("", {
- layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+ layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+ layoutKey,
+ _singleLine: BoolCast(originalDoc._singleLine),
x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
});
+ if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+ newDoc[layoutKey] = originalDoc[layoutKey];
+ }
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
return true;
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 3746199ba..8b11be6fb 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -143,7 +143,7 @@ export class RichTextRules {
textDoc.inlineTextCount = numInlines + 1;
const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
- const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, fontSize: 9, title: "inline comment" });
+ const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: 9, title: "inline comment" });
textDocInline.title = inlineFieldKey; // give the annotation its own title
textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index d23962d5c..d5dd05aa4 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -15,7 +15,7 @@ import { ObjectField } from "../../new_fields/ObjectField";
import { listSpec } from "../../new_fields/Schema";
import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
import { ComputedField } from "../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
+import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../new_fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs } from "../documents/Documents";
@@ -522,8 +522,8 @@ export const marks: { [index: string]: MarkSpec } = {
const min = Math.round(node.attrs.modified / 12);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
- const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
- return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
+ const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : "";
+ return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0];
}
},
// the id of the user who entered the text
@@ -780,7 +780,7 @@ export class DashDocView {
if (dashDocBase instanceof Doc) {
const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
aliasedDoc.layoutKey = "layout";
- node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
+ node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
self.doRender(aliasedDoc, removeDoc, node, view, getPos);
}
});
@@ -879,7 +879,7 @@ export class DashDocView {
export class DashFieldView {
_fieldWrapper: HTMLDivElement; // container for label and value
_labelSpan: HTMLSpanElement; // field label
- _fieldSpan: HTMLDivElement; // field value
+ _fieldSpan: HTMLSpanElement; // field value
_fieldCheck: HTMLInputElement;
_enumerables: HTMLDivElement; // field value
_reactionDisposer: IReactionDisposer | undefined;
@@ -891,11 +891,12 @@ export class DashFieldView {
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
this._fieldKey = node.attrs.fieldKey;
this._textBoxDoc = tbox.props.Document;
- this._fieldWrapper = document.createElement("div");
+ this._fieldWrapper = document.createElement("p");
this._fieldWrapper.style.width = node.attrs.width;
this._fieldWrapper.style.height = node.attrs.height;
+ this._fieldWrapper.style.fontWeight = "bold";
this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-flex";
+ this._fieldWrapper.style.display = "inline-block";
const self = this;
this._enumerables = document.createElement("div");
@@ -903,7 +904,6 @@ export class DashFieldView {
this._enumerables.style.height = "10px";
this._enumerables.style.position = "relative";
this._enumerables.style.display = "none";
- this._enumerables.style.background = "dimGray";
this._enumerables.onpointerdown = async (e) => {
e.stopPropagation();
@@ -946,13 +946,13 @@ export class DashFieldView {
self._dashDoc![self._fieldKey] = e.target.checked;
};
- this._fieldSpan = document.createElement("div");
+ this._fieldSpan = document.createElement("span");
this._fieldSpan.id = Utils.GenerateGuid();
this._fieldSpan.contentEditable = "true";
this._fieldSpan.style.position = "relative";
this._fieldSpan.style.display = "none";
this._fieldSpan.style.minWidth = "12px";
- this._fieldSpan.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
+ this._fieldSpan.style.fontSize = "large";
this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; };
@@ -966,7 +966,7 @@ export class DashFieldView {
this._labelSpan.innerHTML = `${self._fieldKey}: `;
const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null);
this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none";
- this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? "inline-block" : "none";
+ this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none";
};
this._fieldSpan.onkeydown = function (e: any) {
e.stopPropagation();
@@ -987,11 +987,10 @@ export class DashFieldView {
};
this._labelSpan = document.createElement("span");
- this._labelSpan.style.backgroundColor = "rgba(155, 155, 155, 0.44)";
this._labelSpan.style.position = "relative";
- this._labelSpan.style.display = "inline-block";
this._labelSpan.style.fontSize = "small";
this._labelSpan.title = "click to see related tags";
+ this._labelSpan.style.fontSize = "x-small";
this._labelSpan.onpointerdown = function (e: any) {
e.stopPropagation();
let container = tbox.props.ContainingCollectionView;
@@ -1029,7 +1028,7 @@ export class DashFieldView {
this._fieldSpan.innerHTML = Field.toString(fval as Field) || "";
}
this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none";
- this._fieldSpan.style.display = !(boolVal === true || boolVal === false) ? "inline-block" : "none";
+ this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none";
}, { fireImmediately: true });
this._fieldWrapper.appendChild(this._labelSpan);
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 8f112de0c..30938688d 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -61,6 +61,42 @@
letter-spacing: 2px;
text-transform: uppercase;
padding-right: 30px;
+
+ .icon-background {
+ pointer-events: all;
+ background-color: transparent;
+ width: 35px;
+ text-align: center;
+ font-size: 20px;
+ margin-left: 5px;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ height: 20px;
+ }
+}
+.contextMenu-description {
+ // width: 11vw; //10vw
+ background: whitesmoke;
+ display: flex; //comment out to allow search icon to be inline with search text
+ justify-content: left;
+ -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-style: none;
+ // padding: 10px 0px 10px 0px;
+ white-space: nowrap;
+ font-size: 13px;
+ color: grey;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ padding-right: 30px;
+ margin-top: 5px;
+ height: 20px;
+ margin-bottom: 5px;
}
.contextMenu-item:hover {
@@ -121,15 +157,4 @@
padding-left: 10px;
border: solid black 1px;
border-radius: 5px;
-}
-
-.icon-background {
- pointer-events: all;
- height:100%;
- margin-top: 15px;
- background-color: transparent;
- width: 35px;
- text-align: center;
- font-size: 20px;
- margin-left: 5px;
} \ No newline at end of file
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 4d04d4e89..5b66b63ed 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -99,6 +99,15 @@ export class ContextMenu extends React.Component {
}
}
@action
+ moveAfter(item: ContextMenuProps, after: ContextMenuProps) {
+ if (this.findByDescription(after.description)) {
+ const curInd = this._items.findIndex((i) => i.description === item.description);
+ this._items.splice(curInd, 1);
+ const afterInd = this._items.findIndex((i) => i.description === after.description);
+ this._items.splice(afterInd + 1, 0, item);
+ }
+ }
+ @action
setDefaultItem(prefix: string, item: (name: string) => void) {
this._defaultPrefix = prefix;
this._defaultItem = item;
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index fef9e5f60..99840047f 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -51,7 +51,8 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
currentTimeout?: any;
static readonly timeout = 300;
- onPointerEnter = () => {
+ _overPosY = 0;
+ onPointerEnter = (e: React.MouseEvent) => {
if (this.currentTimeout) {
clearTimeout(this.currentTimeout);
this.currentTimeout = undefined;
@@ -59,6 +60,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
if (this.overItem) {
return;
}
+ this._overPosY = e.clientY;
this.currentTimeout = setTimeout(action(() => this.overItem = true), ContextMenuItem.timeout);
}
@@ -88,18 +90,22 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
</div>
);
} else if ("subitems" in this.props) {
+ const where = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "flex-start" : this._overPosY > window.innerHeight * 2 / 3 ? "flex-end" : "center";
+ const marginTop = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "20px" : this._overPosY > window.innerHeight * 2 / 3 ? "-20px" : "";
const submenu = !this.overItem ? (null) :
- <div className="contextMenu-subMenu-cont" style={{ marginLeft: "25%", left: "0px" }}>
+ <div className="contextMenu-subMenu-cont" style={{ marginLeft: "25%", left: "0px", marginTop }}>
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
</div>;
return (
- <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
+ <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} style={{ alignItems: where }}
+ onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
{this.props.icon ? (
- <span className="icon-background" onMouseEnter={this.onPointerLeave}>
+ <span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center" }}>
<FontAwesomeIcon icon={this.props.icon} size="sm" />
</span>
) : null}
- <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} >
+ <div className="contextMenu-description" onMouseEnter={this.onPointerEnter}
+ style={{ alignItems: "center" }} >
{this.props.description}
<FontAwesomeIcon icon={faAngleRight} size="lg" style={{ position: "absolute", right: "10px" }} />
</div>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 353520026..28cf9fd47 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -143,7 +143,6 @@ $linkGap : 3px;
pointer-events: all;
text-align: center;
cursor: pointer;
- padding-right: 10px;
}
.documentDecorations-minimizeButton {
@@ -157,7 +156,6 @@ $linkGap : 3px;
position: absolute;
left: 0px;
top: 0px;
- padding-top: 5px;
width: $MINIMIZED_ICON_SIZE;
height: $MINIMIZED_ICON_SIZE;
max-height: 20px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a4ebde3b3..312acd5b2 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -165,8 +165,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
return true;
}
- onCloseDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, (e, d) => false, (e) => { }, this.onMinimizeClick);
+ onIconifyDown = (e: React.PointerEvent): void => {
+ setupMoveUpEvents(this, e, (e, d) => false, (e) => { }, this.onIconifyClick);
}
@undoBatch
@action
@@ -193,28 +193,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const selectedDocs = SelectionManager.SelectedDocuments();
if (selectedDocs.length) {
//CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0], selectedDocs[0].props.LibraryPath);
- CollectionDockingView.AddRightSplit(selectedDocs[0].props.Document, selectedDocs[0].props.LibraryPath);
+ CollectionDockingView.AddRightSplit(Doc.MakeAlias(selectedDocs[0].props.Document), selectedDocs[0].props.LibraryPath);
}
}
SelectionManager.DeselectAll();
}
@undoBatch
@action
- onMinimizeClick = (e: PointerEvent): void => {
+ onIconifyClick = (e: PointerEvent): void => {
if (e.button === 0) {
- const selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
- selectedDocs.map(dv => {
- const layoutKey = Cast(dv.props.Document.layoutKey, "string", null);
- const collapse = layoutKey !== "layout_icon";
- if (collapse) {
- dv.switchViews(collapse, "icon");
- if (layoutKey && layoutKey !== "layout") dv.props.Document.deiconifyLayout = layoutKey.replace("layout_", "");
- } else {
- const deiconifyLayout = Cast(dv.props.Document.deiconifyLayout, "string", null);
- dv.switchViews(deiconifyLayout ? true : false, deiconifyLayout);
- dv.props.Document.deiconifyLayout = undefined;
- }
- });
+ SelectionManager.SelectedDocuments().forEach(dv => dv?.iconify());
}
SelectionManager.DeselectAll();
}
@@ -408,9 +396,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div className="documentDecorations-contextMenu" title="Show context menu" onPointerDown={this.onSettingsDown}>
<FontAwesomeIcon size="lg" icon="cog" />
</div>) : (
- <div className="documentDecorations-minimizeButton" title="Iconify" onPointerDown={this.onMaximizeDown}>
+ <div className="documentDecorations-minimizeButton" title="Iconify" onPointerDown={this.onIconifyDown}>
{/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
- {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
+ <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
</div>);
const titleArea = this._edtingTitle ?
@@ -465,8 +453,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}}>
{maximizeIcon}
{titleArea}
- <div className="documentDecorations-closeButton" title="Close Document" onPointerDown={this.onCloseDown}>
- <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
+ <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
+ {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
</div>
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 2219966e5..c51173ad3 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -46,7 +46,6 @@ export interface EditableProps {
menuCallback?: (x: number, y: number) => void;
showMenuOnLoad?: boolean;
HeadingObject?: SchemaHeaderField | undefined;
- HeadingsHack?: number;
toggle?: () => void;
color?: string | undefined;
}
@@ -60,7 +59,6 @@ export interface EditableProps {
export class EditableView extends React.Component<EditableProps> {
public static loadId = "";
@observable _editing: boolean = false;
- @observable _headingsHack: number = 1;
constructor(props: EditableProps) {
super(props);
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 6894500dd..665ab4e41 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -14,6 +14,7 @@ import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZer
import { Transform } from "../util/Transform";
import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
import { Scripting } from "../util/Scripting";
+import { List } from "../../new_fields/List";
@observer
class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
@@ -106,14 +107,13 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
return100 = () => 100;
@computed get scriptField() {
- return ScriptField.MakeScript("switchView(firstDoc, this)", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
- { firstDoc: this.props.docViews[0].props.Document });
+ return ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
+ { docs: new List<Doc>(this.props.docViews.map(dv => dv.props.Document)) });
}
render() {
const firstDoc = this.props.docViews[0].props.Document;
const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", "");
- const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null);
- const noteTypes = DocListCast(noteTypesDoc?.data);
+ const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null));
const addedTypes = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data);
const layout = Doc.Layout(firstDoc);
const templateMenu: Array<JSX.Element> = [];
@@ -123,11 +123,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={firstDoc.z ? true : false} toggle={this.toggleFloat} />);
templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
- if (noteTypesDoc) {
- addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self,firstDoc)`, {}, { firstDoc }));
- this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
- <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
- }
+ addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self,firstDoc)`, {}, { firstDoc }));
+ this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
+ <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
return <ul className="template-list" style={{ display: "block" }}>
<input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />
{templateMenu}
@@ -172,7 +170,7 @@ Scripting.addGlobal(function switchView(doc: Doc, template: Doc | undefined) {
template = Cast(template.dragFactory, Doc, null);
}
const templateTitle = StrCast(template?.title);
- return templateTitle && DocumentView.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
+ return templateTitle && Doc.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
});
Scripting.addGlobal(function templateIsUsed(templateDoc: Doc, selDoc: Doc) {
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 9e7248db2..2453acddf 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -27,7 +27,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this._dropDisposer?.();
if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
}
}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index cb0206260..344dca23a 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -64,7 +64,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this._dropDisposer && this._dropDisposer();
if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
}
}
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index b272151c1..42f0c4311 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -4,7 +4,6 @@ import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import Measure from "react-measure";
import { Doc } from "../../../new_fields/Doc";
import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { ScriptField } from "../../../new_fields/ScriptField";
@@ -46,7 +45,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@observable private _background = "inherit";
@observable private _createAliasSelected: boolean = false;
@observable private _collapsed: boolean = false;
- @observable private _headingsHack: number = 1;
@observable private _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
@observable private _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
@@ -55,7 +53,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
private _contRef: React.RefObject<HTMLDivElement> = React.createRef();
private _sensitivity: number = 16;
- private _counter: number = 0;
private _ele: any;
createRowDropRef = (ele: HTMLDivElement | null) => {
@@ -167,7 +164,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
collapseSection = () => {
this._createAliasSelected = false;
if (this.props.headingObject) {
- this._headingsHack++;
this.props.headingObject.setCollapsed(!this.props.headingObject.collapsed);
this.toggleVisibility();
}
@@ -197,6 +193,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
e.stopPropagation();
e.preventDefault();
+ if (this.props.parent.props.Document._chromeStatus === "disabled") {
+ this.collapseSection();
+ }
+
document.removeEventListener("pointermove", this.startDrag);
document.removeEventListener("pointerup", this.pointerUp);
}
@@ -209,7 +209,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
const [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX, e.clientY);
this._startDragPosition = { x: dx, y: dy };
- if (this._createAliasSelected) {
+ if (this.props.parent.props.Document._chromeStatus === "disabled" || this._createAliasSelected) {
document.removeEventListener("pointermove", this.startDrag);
document.addEventListener("pointermove", this.startDrag);
document.removeEventListener("pointerup", this.pointerUp);
@@ -261,12 +261,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
</div>);
}
- handleResize = (size: any) => {
- if (++this._counter !== 1) {
- this.getTrueHeight();
- }
- }
-
@computed get contentLayout() {
const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap))));
const style = this.props.parent;
@@ -277,7 +271,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
SetValue: this.addDocument,
contents: "+ NEW",
HeadingObject: this.props.headingObject,
- HeadingsHack: this._headingsHack,
toggle: this.toggleVisibility,
color: this._color
};
@@ -308,6 +301,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@computed get headingView() {
const heading = this._heading;
+ const noChrome = this.props.parent.props.Document._chromeStatus === "disabled";
const key = StrCast(this.props.parent.props.Document._pivotField);
const evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
const headerEditableViewProps = {
@@ -316,7 +310,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
contents: evContents,
oneLine: true,
HeadingObject: this.props.headingObject,
- HeadingsHack: this._headingsHack,
toggle: this.toggleVisibility,
color: this._color
};
@@ -326,12 +319,12 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
</div> :
!this.props.headingObject ? (null) :
<div className="collectionStackingView-sectionHeader" ref={this._headerRef} >
- <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown}
+ <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown} onClick={noChrome ? undefined : this.collapseSection}
title={evContents === `NO ${key.toUpperCase()} VALUE` ?
`Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "lightgrey" }}>
<EditableView {...headerEditableViewProps} />
- {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
+ {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionColor">
<Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}>
<button className="collectionStackingView-sectionColorButton">
@@ -340,10 +333,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
</ Flyout >
</div>
}
- <button className="collectionStackingView-sectionDelete" onClick={this.collapseSection}>
+ {noChrome ? (null) : <button className="collectionStackingView-sectionDelete" onClick={noChrome ? undefined : this.collapseSection}>
<FontAwesomeIcon icon={this._collapsed ? "chevron-down" : "chevron-up"} size="lg" />
- </button>
- {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
+ </button>}
+ {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionOptions">
<Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}>
<button className="collectionStackingView-sectionOptionButton">
@@ -359,20 +352,14 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
const background = this._background; //to account for observables in Measure
const contentlayout = this.contentLayout;
const headingview = this.headingView;
- return <Measure offset onResize={this.handleResize}>
- {({ measureRef }) => {
- return <div ref={measureRef}>
- <div className="collectionStackingView-masonrySection"
- style={{ width: this.props.parent.NodeWidth, background }}
- ref={this.createRowDropRef}
- onPointerEnter={this.pointerEnteredRow}
- onPointerLeave={this.pointerLeaveRow}
- >
- {headingview}
- {contentlayout}
- </div >
- </div>;
- }}
- </Measure>;
+ return <div className="collectionStackingView-masonrySection"
+ style={{ width: this.props.parent.NodeWidth, background }}
+ ref={this.createRowDropRef}
+ onPointerEnter={this.pointerEnteredRow}
+ onPointerLeave={this.pointerLeaveRow}
+ >
+ {headingview}
+ {contentlayout}
+ </div >;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPileView.scss b/src/client/views/collections/CollectionPileView.scss
new file mode 100644
index 000000000..ac874b663
--- /dev/null
+++ b/src/client/views/collections/CollectionPileView.scss
@@ -0,0 +1,8 @@
+.collectionPileView {
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ overflow: visible;
+}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
new file mode 100644
index 000000000..3bbfcc4d7
--- /dev/null
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -0,0 +1,127 @@
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionPileView.scss";
+import React = require("react");
+import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
+import { UndoManager } from "../../util/UndoManager";
+
+@observer
+export class CollectionPileView extends CollectionSubView(doc => doc) {
+ _lastTap = 0;
+ _doubleTap: boolean | undefined = false;
+ _originalChrome: string = "";
+ @observable _contentsActive = true;
+ @observable _layoutEngine = "pass";
+ @observable _collapsed: boolean = false;
+ @observable _childClickedScript: Opt<ScriptField>;
+ componentDidMount() {
+ this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
+ this.layoutDoc._chromeStatus = "disabled";
+ this.layoutDoc.hideFilterView = true;
+ }
+ componentWillUnmount() {
+ this.layoutDoc.hideFilterView = false;
+ this.layoutDoc._chromeStatus = this._originalChrome;
+ }
+
+ layoutEngine = () => this._layoutEngine;
+
+ @computed get contents() {
+ return <div className="collectionPileView-innards" style={{
+ width: "100%",
+ pointerEvents: this.layoutEngine() !== "pass" && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none"
+ }} >
+ <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
+ </div>;
+ }
+
+ specificMenu = (e: React.MouseEvent) => {
+ const layoutItems: ContextMenuProps[] = [];
+ const doc = this.props.Document;
+
+ ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
+ }
+
+ toggleStarburst = action(() => {
+ if (this._layoutEngine === 'starburst') {
+ const defaultSize = 110;
+ this.layoutDoc.overflow = undefined;
+ this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
+ this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
+ this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
+ this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
+ this._layoutEngine = 'pass';
+ } else {
+ const defaultSize = 25;
+ this.layoutDoc.overflow = 'visible';
+ !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
+ !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
+ if (this._layoutEngine === 'pass') {
+ this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
+ this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
+ this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
+ this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
+ }
+ this.layoutDoc._width = this.layoutDoc._height = defaultSize;
+ this._layoutEngine = 'starburst';
+ }
+ });
+
+ _undoBatch: UndoManager.Batch | undefined;
+ pointerDown = (e: React.PointerEvent) => {
+ let dist = 0;
+ SelectionManager.SetIsDragging(true);
+ // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
+ setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
+ if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) {
+ dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
+ if (dist > 100) {
+ if (!this._undoBatch) {
+ this._undoBatch = UndoManager.StartBatch("layout pile");
+ }
+ const doc = this.childDocs[0];
+ doc.x = e.clientX;
+ doc.y = e.clientY;
+ this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
+ dist = 0;
+ }
+ }
+ return false;
+ }, () => {
+ this._undoBatch?.end();
+ this._undoBatch = undefined;
+ SelectionManager.SetIsDragging(false);
+ if (!this.childDocs.length) {
+ this.props.ContainingCollectionView?.removeDocument(this.props.Document);
+ }
+ }, emptyFunction, false, this.layoutEngine() === "pass" && this.props.isSelected(true)); // this sets _doubleTap
+ }
+
+ onClick = (e: React.MouseEvent) => {
+ if (e.button === 0 && (this._doubleTap || this.layoutEngine() === "starburst")) {
+ SelectionManager.DeselectAll();
+ this.toggleStarburst();
+ e.stopPropagation();
+ }
+ // else if (this.layoutEngine() === "pass") {
+ // runInAction(() => this._contentsActive = false);
+ // setTimeout(action(() => this._contentsActive = true), 300);
+ // }
+ }
+
+ render() {
+
+ return <div className={"collectionPileView"} onContextMenu={this.specificMenu} onClick={this.onClick} onPointerDown={this.pointerDown}
+ style={{ width: this.props.PanelWidth(), height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+ {this.contents}
+ </div>;
+ }
+}
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 670d6dbb2..972714e34 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -54,7 +54,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
}
createColDropTarget = (ele: HTMLDivElement) => {
- this._colDropDisposer && this._colDropDisposer();
+ this._colDropDisposer?.();
if (ele) {
this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 24a3119cc..e3720bf01 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -49,7 +49,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@computed get columnWidth() {
TraceMobx();
return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
- this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
+ this.isStackingView ? Number.MAX_VALUE : this.props.Document.columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.props.Document.columnWidth, 250));
}
@computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
@@ -189,8 +189,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.addDocTab}
- pinToPres={this.props.pinToPres}>
- </ContentFittingDocumentView>;
+ pinToPres={this.props.pinToPres}
+ />;
}
getDocWidth(d?: Doc) {
@@ -303,7 +303,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
this.observer = new _global.ResizeObserver(action((entries: any) => {
if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) {
- Doc.Layout(doc)._height = Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))));
+ Doc.Layout(doc)._height = Math.min(1200, Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
}
}));
this.observer.observe(ref);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 5d926b7c7..323d7be25 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -156,7 +156,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
collapseSection = () => {
if (this.props.headingObject) {
- this._headingsHack++;
this.props.headingObject.setCollapsed(!this.props.headingObject.collapsed);
this.toggleVisibility();
}
@@ -225,8 +224,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
private toggleVisibility = action(() => this.collapsed = !this.collapsed);
- @observable _headingsHack: number = 1;
-
menuCallback = (x: number, y: number) => {
ContextMenu.Instance.clearItems();
const layoutItems: ContextMenuProps[] = [];
@@ -300,7 +297,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
contents: evContents,
oneLine: true,
HeadingObject: this.props.headingObject,
- HeadingsHack: this._headingsHack,
toggle: this.toggleVisibility,
color: this._color
};
@@ -309,7 +305,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
SetValue: this.addDocument,
contents: "+ NEW",
HeadingObject: this.props.headingObject,
- HeadingsHack: this._headingsHack,
toggle: this.toggleVisibility,
color: this._color
};
@@ -364,7 +359,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
<div className="collectionStackingViewFieldColumn" key={heading}
style={{
width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
- height: SelectionManager.GetIsDragging() ? "100%" : undefined,
+ height: undefined, // SelectionManager.GetIsDragging() ? "100%" : undefined,
background: this._background
}}
ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ca38a21b8..49abc6ee6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -11,7 +11,7 @@ import { Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { DocumentType } from "../../documents/DocumentTypes";
import { Docs, DocumentOptions } from "../../documents/Documents";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
@@ -64,7 +64,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.multiTouchDisposer?.();
if (ele) {
this._mainCont = ele;
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
}
@@ -398,8 +398,13 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
generatedDocuments.push(doc);
}
if (generatedDocuments.length) {
- generatedDocuments.forEach(addDocument);
- completed && completed();
+ const set = generatedDocuments.length > 1 && generatedDocuments.map(d => Doc.iconify(d));
+ if (set) {
+ addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!));
+ } else {
+ generatedDocuments.forEach(addDocument);
+ }
+ completed?.();
} else {
if (text && !text.includes("https://")) {
addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 06ebf6d2d..045134225 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -19,7 +19,6 @@ const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
import React = require("react");
-import { DocumentView } from "../nodes/DocumentView";
@observer
export class CollectionTimeView extends CollectionSubView(doc => doc) {
@@ -29,7 +28,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
@observable _childClickedScript: Opt<ScriptField>;
@observable _viewDefDivClick: Opt<ScriptField>;
async componentDidMount() {
- const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || DocumentView.findTemplate("detailView", StrCast(this.props.Document.type), "");
+ const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), "");
const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
runInAction(() => {
this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
@@ -84,7 +83,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
}
@computed get contents() {
- return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}>
+ return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%", pointerEvents: this.props.active() ? undefined : "none" }} onPointerDown={this.contentsDown}>
<CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
</div>;
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index a052d045c..d938bd7ad 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -3,13 +3,14 @@ import { faAngleRight, faArrowsAltH, faBell, faCamera, faCaretDown, faCaretRight
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction, untracked } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, Field, HeightSym, WidthSym, DataSym, Opt } from '../../../new_fields/Doc';
+import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
+import { RichTextField } from '../../../new_fields/RichTextField';
import { Document, listSpec } from '../../../new_fields/Schema';
import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types';
-import { emptyFunction, emptyPath, returnFalse, Utils, returnOne, returnZero, returnTransparent, returnTrue, simulateMouseClick } from '../../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
@@ -24,16 +25,14 @@ import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from "../EditableView";
import { MainView } from '../MainView';
import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
+import { DocumentView } from '../nodes/DocumentView';
import { ImageBox } from '../nodes/ImageBox';
import { KeyValueBox } from '../nodes/KeyValueBox';
-import { ScriptBox } from '../ScriptBox';
import { Templates } from '../Templates';
-import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
+import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
+import { CollectionViewType } from './CollectionView';
import React = require("react");
-import { CollectionViewType, CollectionView } from './CollectionView';
-import { RichTextField } from '../../../new_fields/RichTextField';
-import { DocumentView } from '../nodes/DocumentView';
export interface TreeViewProps {
@@ -144,7 +143,7 @@ class TreeView extends React.Component<TreeViewProps> {
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer && this._treedropDisposer();
- ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)));
+ ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)), this.props.document);
}
onPointerEnter = (e: React.PointerEvent): void => {
@@ -262,7 +261,7 @@ class TreeView extends React.Component<TreeViewProps> {
docHeight = () => {
const layoutDoc = Doc.Layout(this.props.document);
const bounds = this.boundsOfCollectionDocument;
- return Math.min(this.MAX_EMBED_HEIGHT, (() => {
+ return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => {
const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
if (aspect) return this.docWidth() * aspect;
if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
@@ -270,7 +269,7 @@ class TreeView extends React.Component<TreeViewProps> {
Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
NumCast(this.props.containingCollection._height)))) :
NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
- })());
+ })()));
}
@computed get expandedField() {
@@ -322,6 +321,9 @@ class TreeView extends React.Component<TreeViewProps> {
return rows;
}
+ rtfWidth = () => Math.min(Doc.Layout(this.props.document)?.[WidthSym](), this.props.panelWidth() - 20);
+ rtfHeight = () => this.rtfWidth() < Doc.Layout(this.props.document)?.[WidthSym]() ? Math.min(Doc.Layout(this.props.document)?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+
@computed get renderContent() {
const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
if (expandKey !== undefined) {
@@ -346,7 +348,9 @@ class TreeView extends React.Component<TreeViewProps> {
</div></ul>;
} else {
const layoutDoc = Doc.Layout(this.props.document);
- return <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}>
+ const panelHeight = layoutDoc.type === DocumentType.RTF ? this.rtfHeight : this.docHeight;
+ const panelWidth = layoutDoc.type === DocumentType.RTF ? this.rtfWidth : this.docWidth;
+ return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.props.document[Id] + this.props.document.title}>
<ContentFittingDocumentView
Document={layoutDoc}
DataDocument={this.templateDataDoc}
@@ -356,8 +360,10 @@ class TreeView extends React.Component<TreeViewProps> {
backgroundColor={this.props.backgroundColor}
fitToBox={this.boundsOfCollectionDocument !== undefined}
FreezeDimensions={true}
- PanelWidth={this.docWidth}
- PanelHeight={this.docHeight}
+ NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
+ NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
+ PanelWidth={panelWidth}
+ PanelHeight={panelHeight}
getTransform={this.docTransform}
CollectionDoc={this.props.containingCollection}
CollectionView={undefined}
@@ -664,7 +670,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this.treedropDisposer?.();
if (this._mainEle = ele) {
- this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
+ this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.props.Document);
}
}
@@ -723,21 +729,10 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
}
});
});
- const { TextDocument, ImageDocument, CarouselDocument, TreeDocument } = Docs.Create;
+ const { ImageDocument } = Docs.Create;
const { Document } = this.props;
const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif";
- const detailedTemplate = `{ "doc": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "year" } } ] }, { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "company" } } ] } ] }, "selection":{"type":"text","anchor":1,"head":1},"storedMarks":[] }`;
-
- const textDoc = TextDocument("", { title: "details", _autoHeight: true });
- const detailView = Docs.Create.StackingDocument([
- CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }),
- textDoc,
- TextDocument("", { title: "shortDescription", _autoHeight: true }),
- TreeDocument([], { title: "narratives", _height: 75, treeViewHideTitle: true })
- ], { _chromeStatus: "disabled", _width: 300, _height: 300, _autoHeight: true, title: "detailView" });
- textDoc.data = new RichTextField(detailedTemplate, "year company");
- detailView.isTemplateDoc = makeTemplate(detailView);
-
+ const detailView = Cast(Cast(Doc.UserDoc()["template-button-detail"], Doc, null)?.dragFactory, Doc, null);
const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
heroView.proto!.layout = ImageBox.LayoutString("hero");
heroView._showTitle = "title";
@@ -768,7 +763,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
onClicks.push({
- description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
+ description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
});
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 9ddd9b59c..e8edf7279 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -47,6 +47,7 @@ import { ObjectField } from '../../../new_fields/ObjectField';
import CollectionMapView from './CollectionMapView';
import { Transform } from 'prosemirror-transform';
import { CollectionGridView } from './collectionGrid/CollectionGridView';
+import { CollectionPileView } from './CollectionPileView';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -69,7 +70,8 @@ export enum CollectionViewType {
Linear = "linear",
Staff = "staff",
Map = "map",
- Grid = "grid"
+ Grid = "grid",
+ Pile = "pileup"
}
export interface CollectionRenderProps {
@@ -171,6 +173,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView key="collview" {...props} />);
case CollectionViewType.Multirow: return (<CollectionMultirowView key="rpwview" {...props} />);
case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
+ case CollectionViewType.Pile: { return (<CollectionPileView key="collview" {...props} />); }
case CollectionViewType.Carousel: { return (<CollectionCarouselView key="collview" {...props} />); }
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
@@ -204,7 +207,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
}
subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" });
- subItems.push({ description: "Treeview", event: () => func(CollectionViewType.Tree), icon: "tree" });
+ subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" });
subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking), icon: "ellipsis-v" });
subItems.push({ description: "Stacking (AutoHeight)", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" });
subItems.push({ description: "Staff", event: () => func(CollectionViewType.Staff), icon: "music" });
@@ -256,7 +259,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
!existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" });
this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
- this.setupViewTypes("Open New Perspective...", vtype => {
+ this.setupViewTypes("Add a Perspective...", vtype => {
const newRendition = Doc.MakeAlias(this.props.Document);
newRendition._viewType = vtype;
this.props.addDocTab(newRendition, "onRight");
@@ -524,7 +527,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
{!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
<div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} />
}
- {this.filterView}
+ {this.facetWidth() < 10 ? (null) : this.filterView}
</div>);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 7315d2c4e..d26e3a38b 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -215,9 +215,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
- this.dropDisposer && this.dropDisposer();
+ this.dropDisposer?.();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.document);
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 0a5ea3baf..9a864078a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -9,13 +9,15 @@ import React = require("react");
import { Id, ToString } from "../../../../new_fields/FieldSymbols";
import { ObjectField } from "../../../../new_fields/ObjectField";
import { RefField } from "../../../../new_fields/RefField";
+import { listSpec } from "../../../../new_fields/Schema";
export interface ViewDefBounds {
type: string;
- text?: string;
+ payload: any;
x: number;
y: number;
z?: number;
+ text?: string;
zIndex?: number;
width?: number;
height?: number;
@@ -23,12 +25,13 @@ export interface ViewDefBounds {
fontSize?: number;
highlight?: boolean;
color?: string;
- payload: any;
+ replica?: string;
+ pair?: { layout: Doc, data?: Doc };
}
export interface PoolData {
- x?: number;
- y?: number;
+ x: number;
+ y: number;
z?: number;
zIndex?: number;
width?: number;
@@ -36,6 +39,8 @@ export interface PoolData {
color?: string;
transition?: string;
highlight?: boolean;
+ replica: string;
+ pair: { layout: Doc, data?: Doc };
}
export interface ViewDefResult {
@@ -72,64 +77,103 @@ function getTextWidth(text: string, font: string): number {
interface PivotColumn {
docs: Doc[];
+ replicas: string[];
filters: string[];
}
+export function computerPassLayout(
+ poolData: Map<string, PoolData>,
+ pivotDoc: Doc,
+ childPairs: { layout: Doc, data?: Doc }[],
+ panelDim: number[],
+ viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+) {
+ const docMap = new Map<string, PoolData>();
+ childPairs.forEach(({ layout, data }, i) => {
+ docMap.set(layout[Id], {
+ x: NumCast(layout.x),
+ y: NumCast(layout.y),
+ width: layout[WidthSym](),
+ height: layout[HeightSym](),
+ pair: { layout, data },
+ replica: ""
+ });
+ });
+ return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
+}
export function computerStarburstLayout(
poolData: Map<string, PoolData>,
pivotDoc: Doc,
- childDocs: Doc[],
- filterDocs: Doc[],
childPairs: { layout: Doc, data?: Doc }[],
panelDim: number[],
viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
) {
- const docMap = new Map<Doc, ViewDefBounds>();
- const burstDim = [NumCast(pivotDoc.starburstRadius, panelDim[0]), NumCast(pivotDoc.starburstRadius, panelDim[1])]
- childDocs.forEach((doc, i) => {
- const deg = i / childDocs.length * Math.PI * 2;
- docMap.set(doc, {
- type: "doc",
- x: Math.sin(deg) * burstDim[0] / 3 - NumCast(pivotDoc.starburstX),
- y: Math.cos(deg) * burstDim[1] / 3 - NumCast(pivotDoc.starburstY),
- width: doc[WidthSym](),
- height: doc[HeightSym](),
- payload: undefined
+ const docMap = new Map<string, PoolData>();
+ const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
+ const docScale = NumCast(pivotDoc._starburstDocScale);
+ const docSize = docScale * 100; // assume a icon sized at 100
+ const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
+ childPairs.forEach(({ layout, data }, i) => {
+ const deg = i / childPairs.length * Math.PI * 2;
+ docMap.set(layout[Id], {
+ x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * layout[WidthSym]() / 2,
+ y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * layout[HeightSym]() / 2,
+ width: docScale * layout[WidthSym](),
+ height: docScale * layout[HeightSym](),
+ pair: { layout, data },
+ replica: ""
});
});
- return normalizeResults(burstDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+ return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
}
export function computePivotLayout(
poolData: Map<string, PoolData>,
pivotDoc: Doc,
- childDocs: Doc[],
- filterDocs: Doc[],
childPairs: { layout: Doc, data?: Doc }[],
panelDim: number[],
viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
) {
+ const docMap = new Map<string, PoolData>();
const fieldKey = "data";
const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
const pivotFieldKey = toLabel(pivotDoc._pivotField);
- for (const doc of filterDocs) {
- const val = Field.toString(doc[pivotFieldKey] as Field);
- if (val) {
- !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] });
- pivotColumnGroups.get(val)!.docs.push(doc);
+ childPairs.map(pair => {
+ const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+ const val = Field.toString(pair.layout[pivotFieldKey] as Field);
+ if (lval) {
+ lval.forEach((val, i) => {
+ !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
+ pivotColumnGroups.get(val)!.docs.push(pair.layout);
+ pivotColumnGroups.get(val)!.replicas.push(i.toString());
+ });
+ } else if (val) {
+ !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
+ pivotColumnGroups.get(val)!.docs.push(pair.layout);
+ pivotColumnGroups.get(val)!.replicas.push("");
+ } else {
+ docMap.set(pair.layout[Id], {
+ x: 0,
+ y: 0,
+ zIndex: -99,
+ width: 0,
+ height: 0,
+ pair,
+ replica: ""
+ });
}
- }
+ });
let nonNumbers = 0;
- childDocs.map(doc => {
- const num = toNumber(doc[pivotFieldKey]);
+ childPairs.map(pair => {
+ const num = toNumber(pair.layout[pivotFieldKey]);
if (num === undefined || Number.isNaN(num)) {
nonNumbers++;
}
});
- const pivotNumbers = nonNumbers / childDocs.length < .1;
+ const pivotNumbers = nonNumbers / childPairs.length < .1;
if (pivotColumnGroups.size > 10) {
const arrayofKeys = Array.from(pivotColumnGroups.keys());
const sortedKeys = pivotNumbers ? arrayofKeys.sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : arrayofKeys.sort();
@@ -141,6 +185,7 @@ export function computePivotLayout(
const newgrp = pivotColumnGroups.get(sortedKeys[j])!;
curgrp.docs.push(...newgrp.docs);
curgrp.filters.push(...newgrp.filters);
+ curgrp.replicas.push(...newgrp.replicas);
pivotColumnGroups.delete(sortedKeys[j]);
}
}
@@ -168,7 +213,6 @@ export function computePivotLayout(
}
}
- const docMap = new Map<Doc, ViewDefBounds>();
const groupNames: ViewDefBounds[] = [];
const expander = 1.05;
@@ -191,7 +235,7 @@ export function computePivotLayout(
fontSize,
payload: val
});
- for (const doc of val.docs) {
+ val.docs.forEach((doc, i) => {
const layoutDoc = Doc.Layout(doc);
let wid = pivotAxisWidth;
let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
@@ -199,27 +243,27 @@ export function computePivotLayout(
hgt = pivotAxisWidth;
wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
}
- docMap.set(doc, {
- type: "doc",
+ docMap.set(doc[Id] + (val.replicas || ""), {
x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0),
y: -y + (pivotAxisWidth - hgt) / 2,
width: wid,
height: hgt,
- payload: undefined
+ pair: { layout: doc },
+ replica: val.replicas[i]
});
xCount++;
if (xCount >= numCols) {
xCount = 0;
y += pivotAxisWidth * expander;
}
- }
+ });
x += pivotAxisWidth * (numCols * expander + gap);
});
const dividers = sortedPivotKeys.map((key, i) =>
({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
groupNames.push(...dividers);
- return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+ return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
}
function toNumber(val: FieldResult<Field>) {
@@ -229,15 +273,13 @@ function toNumber(val: FieldResult<Field>) {
export function computeTimelineLayout(
poolData: Map<string, PoolData>,
pivotDoc: Doc,
- childDocs: Doc[],
- filterDocs: Doc[],
childPairs: { layout: Doc, data?: Doc }[],
panelDim: number[],
viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
) {
const fieldKey = "data";
const pivotDateGroups = new Map<number, Doc[]>();
- const docMap = new Map<Doc, ViewDefBounds>();
+ const docMap = new Map<string, PoolData>();
const groupNames: ViewDefBounds[] = [];
const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]);
@@ -253,11 +295,11 @@ export function computeTimelineLayout(
let minTime = minTimeReq === undefined ? Number.MAX_VALUE : minTimeReq;
let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq;
- filterDocs.map(doc => {
- const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey])));
+ childPairs.forEach(pair => {
+ const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey])));
if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
!pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
- pivotDateGroups.get(num)!.push(doc);
+ pivotDateGroups.get(num)!.push(pair.layout);
minTime = Math.min(num, minTime);
maxTime = Math.max(num, maxTime);
}
@@ -316,7 +358,7 @@ export function computeTimelineLayout(
}
const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
- return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c)));
+ return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
function layoutDocsAtTime(keyDocs: Doc[], key: number) {
keyDocs.forEach(doc => {
@@ -328,44 +370,55 @@ export function computeTimelineLayout(
hgt = pivotAxisWidth;
wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
}
- docMap.set(doc, {
- type: "doc",
+ docMap.set(doc[Id], {
x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
- zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt / (Math.max(stack, 1)), payload: undefined
+ zIndex: (curTime === key ? 1000 : zind++),
+ highlight: curTime === key,
+ width: wid / (Math.max(stack, 1)),
+ height: hgt / (Math.max(stack, 1)),
+ pair: { layout: doc },
+ replica: ""
});
stacking[stack] = x + pivotAxisWidth;
});
}
}
-function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
- poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
- extraDocs: Doc[]): ViewDefResult[] {
-
+function normalizeResults(
+ panelDim: number[],
+ fontHeight: number,
+ docMap: Map<string, PoolData>,
+ poolData: Map<string, PoolData>,
+ viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+ groupNames: ViewDefBounds[],
+ minWidth: number,
+ extras: ViewDefBounds[]
+): ViewDefResult[] {
const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
- const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
- const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0);
+ const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
+ const aggBounds = aggregateBounds(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" }))).filter(e => e.zIndex !== -99), 0, 0);
aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
if (Number.isNaN(scale)) scale = 1;
- childPairs.filter(d => docMap.get(d.layout)).map(pair => {
- const newPosRaw = docMap.get(pair.layout);
+ Array.from(docMap.entries()).filter(ele => ele[1].pair).map(ele => {
+ const newPosRaw = ele[1];
if (newPosRaw) {
const newPos = {
x: newPosRaw.x * scale,
y: newPosRaw.y * scale,
z: newPosRaw.z,
+ replica: newPosRaw.replica,
highlight: newPosRaw.highlight,
zIndex: newPosRaw.zIndex,
width: (newPosRaw.width || 0) * scale,
- height: newPosRaw.height! * scale
+ height: newPosRaw.height! * scale,
+ pair: ele[1].pair
};
- poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos });
+ poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos });
}
});
- extraDocs.map(ed => poolData.set(ed[Id], { x: 0, y: 0, zIndex: -99 }));
return viewDefsToJSX(extras.concat(groupNames).map(gname => ({
type: gname.type,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 702b02a20..4b5e977df 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -36,7 +36,7 @@ export class CollectionFreeFormLinksView extends React.Component {
}
render() {
- return <div className="collectionfreeformlinksview-container">
+ return SelectionManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container">
<svg className="collectionfreeformlinksview-svgCanvas">
{this.uniqueConnections}
</svg>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index a00311a9c..60c39c825 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -9,6 +9,17 @@
height: 100%;
transform-origin: left top;
border-radius: inherit;
+ touch-action: none;
+ border-radius: inherit;
+}
+
+.collectionfreeformview-viewdef {
+ > .collectionFreeFormDocumentView-container {
+ pointer-events: none;
+ .contentFittingDocumentDocumentView-previewDoc {
+ pointer-events: all;
+ }
+ }
}
.collectionfreeformview-ease {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5967f36f9..28b461313 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -37,7 +37,7 @@ import { pageSchema } from "../../nodes/ImageBox";
import PDFMenu from "../../pdf/PDFMenu";
import { CollectionDockingView } from "../CollectionDockingView";
import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computerStarburstLayout } from "./CollectionFreeFormLayoutEngines";
+import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computerStarburstLayout, computerPassLayout } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
@@ -85,8 +85,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private _clusterDistance: number = 75;
private _hitCluster = false;
private _layoutComputeReaction: IReactionDisposer | undefined;
- private _layoutPoolData = new ObservableMap<string, any>();
- private _cachedPool: Map<string, any> = new Map();
+ private _layoutPoolData = new ObservableMap<string, PoolData>();
+ private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>();
+ private _cachedPool: Map<string, PoolData> = new Map();
@observable private _pullCoords: number[] = [0, 0];
@observable private _pullDirection: string = "";
@@ -94,6 +95,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@observable _clusterSets: (Doc[])[] = [];
+ @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
@computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
@computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
@computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
@@ -104,8 +106,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private easing = () => this.props.Document.panTransformType === "Ease";
private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
- private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ?
- Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
+ private zoomScaling = () => (this.fitToContentScaling / this.parentScaling) * (this.fitToContent ?
+ Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
+ this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
this.Document.scale || 1)
private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections
@@ -506,7 +509,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
if (Date.now() - this._lastTap < 300) {
const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
- this.scaleAtPt(docpt, NumCast(this.layoutDoc.targetScale, NumCast(this.layoutDoc.scale)));
+ this.scaleAtPt(docpt, 1);
e.stopPropagation();
e.preventDefault();
}
@@ -726,7 +729,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (!this.isAnnotationOverlay && clamp) {
// this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
- const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc));
+ const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc, "")).map(doc => this.childDataProvider(doc, ""));
if (measuredDocs.length) {
const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content
({
@@ -819,18 +822,18 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
- if (!willZoom && DocumentView._focusHack.length) {
- Doc.BrushDoc(this.props.Document);
- !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
- } else {
- if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
- if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
- }
- Doc.BrushDoc(this.props.Document);
- this.props.focus(this.props.Document);
- willZoom && this.setScaleToZoom(layoutdoc, scale);
+ // if (!willZoom && DocumentView._focusHack.length) {
+ // Doc.BrushDoc(this.props.Document);
+ // !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
+ // } else {
+ if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
+ if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
}
+ Doc.BrushDoc(this.props.Document);
+ this.props.focus(this.props.Document);
+ willZoom && this.setScaleToZoom(layoutdoc, scale);
Doc.linkFollowHighlight(doc);
+ //}
afterFocus && setTimeout(() => {
if (afterFocus?.()) {
@@ -851,7 +854,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
@computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
backgroundHalo = () => BoolCast(this.Document.useClusters);
-
+ @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+ parentActive = () => this.props.active() || this.backgroundActive ? true : false;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
...this.props,
@@ -877,29 +881,36 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
focus: this.focusDocument,
backgroundColor: this.getClusterColor,
backgroundHalo: this.backgroundHalo,
- parentActive: this.props.active,
+ parentActive: this.parentActive,
bringToFront: this.bringToFront,
addDocTab: this.addDocTab,
};
}
- addDocTab = (doc: Doc, where: string) => {
+ addDocTab = action((doc: Doc, where: string) => {
+ if (where === "inParent") {
+ const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ doc.x = pt[0];
+ doc.y = pt[1];
+ this.props.addDocument(doc);
+ return true;
+ }
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
return true;
}
return this.props.addDocTab(doc, where);
- }
- getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
+ });
+ getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
const result = this.Document.arrangeScript?.script.run(params, console.log);
if (result?.success) {
- return { ...result, transition: "transform 1s" };
+ return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
}
- const layoutDoc = Doc.Layout(params.doc);
- const { x, y, z, color, zIndex } = params.doc;
+ const layoutDoc = Doc.Layout(params.pair.layout);
+ const { x, y, z, color, zIndex } = params.pair.layout;
return {
x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
- width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number")
+ width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
};
}
@@ -937,21 +948,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
}
- childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) {
- return this._layoutPoolData.get(doc[Id]);
+ childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
+ return this._layoutPoolData.get(doc[Id] + (replica || ""));
+ }.bind(this));
+ childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) {
+ return this._layoutSizeData.get(doc[Id] + (replica || ""));
}.bind(this));
doEngineLayout(poolData: Map<string, PoolData>,
engine: (
poolData: Map<string, PoolData>,
pivotDoc: Doc,
- childDocs: Doc[],
- filterDocs: Doc[],
childPairs: { layout: Doc, data?: Doc }[],
panelDim: number[],
- viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]) {
- return engine(poolData, this.props.Document, this.childDocs, this.childDocs,
- this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
+ viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]
+ ) {
+ return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
}
doFreeformLayout(poolData: Map<string, PoolData>) {
@@ -961,17 +973,20 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
- const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
+ const pos = this.getCalculatedPositions({ pair, index: i, collection: this.Document, docs: layoutDocs, state });
poolData.set(pair.layout[Id], pos);
});
return elements;
}
@computed get doInternalLayoutComputation() {
- const newPool = new Map<string, any>();
- const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.();
+ TraceMobx();
+
+
+ const newPool = new Map<string, PoolData>();
+ const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
switch (engine) {
- case "pass": break;
+ case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
case "starburst": return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
@@ -983,29 +998,39 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
get doLayoutComputation() {
const { newPool, computedElementData } = this.doInternalLayoutComputation;
runInAction(() =>
- Array.from(newPool.keys()).map(key => {
- const lastPos = this._cachedPool.get(key); // last computed pos
- const newPos = newPool.get(key);
- if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
- this._layoutPoolData.set(key, newPos);
+ Array.from(newPool.entries()).map(entry => {
+ const lastPos = this._cachedPool.get(entry[0]); // last computed pos
+ const newPos = entry[1];
+ if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
+ this._layoutPoolData.set(entry[0], newPos);
+ }
+ if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
+ this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
}
}));
this._cachedPool.clear();
- Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
+ Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
const elements: ViewDefResult[] = computedElementData.slice();
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+ const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry =>
elements.push({
ele: <CollectionFreeFormDocumentView
- key={pair.layout[Id]}
- {...this.getChildDocumentViewProps(pair.layout, pair.data)}
+ key={entry[1].pair.layout[Id] + (entry[1].replica || "")}
+ {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)}
+ replica={entry[1].replica}
dataProvider={this.childDataProvider}
+ sizeProvider={this.childSizeProvider}
LayoutDoc={this.childLayoutDocFunc}
- pointerEvents={this.props.layoutEngine?.() || this.props.Document._layoutEngine ? false : undefined}
- jitterRotation={NumCast(this.props.Document.jitterRotation)}
- fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
+ pointerEvents={
+ this.backgroundActive ?
+ true :
+ (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
+ jitterRotation={NumCast(this.props.Document._jitterRotation)}
+ //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
+ fitToBox={BoolCast(this.props.freezeChildDimensions)} // bcz: check this
FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
/>,
- bounds: this.childDataProvider(pair.layout)
+ bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
}));
return elements;
@@ -1028,17 +1053,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
- layoutStarburst = action(() => {
- if (this.layoutDoc._layoutEngine === undefined) {
- this.layoutDoc._layoutEngine = "starburst";
- this.layoutDoc.overflow = "visible";
- } else {
-
- this.layoutDoc._layoutEngine = "pass";
- this.layoutDoc.overflow = "hidden";
- }
-
- });
+ promoteCollection = undoBatch(action(() => {
+ this.childDocs.forEach(doc => {
+ const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ doc.x = scr?.[0];
+ doc.y = scr?.[1];
+ this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
+ });
+ this.props.ContainingCollectionView?.removeDocument(this.props.Document);
+ }));
layoutDocsInGrid = () => {
UndoManager.RunInBatch(() => {
const docs = this.childLayoutPairs;
@@ -1072,10 +1095,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
- optionItems.push({ description: "Arrange Starburst", event: this.layoutStarburst, icon: "table" });
+ this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
// layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
- optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = (this.props.Document.jitterRotation ? 0 : 10)), icon: "paint-brush" });
+ optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" });
optionItems.push({
description: "Import document", icon: "upload", event: ({ x, y }) => {
const input = document.createElement("input");
@@ -1141,8 +1164,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@computed get marqueeView() {
return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
- <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
- easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} shifted={!this.nativeHeight && !this.isAnnotationOverlay}
+ easing={this.easing} viewDefDivClick={this.props.viewDefDivClick} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
{this.children}
</CollectionFreeFormViewPannableContents>
</MarqueeView>;
@@ -1156,6 +1179,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const wscale = nw ? this.props.PanelWidth() / nw : 1;
return wscale < hscale ? wscale : hscale;
}
+ @computed get backgroundEvents() { return this.layoutDoc.isBackground && SelectionManager.GetIsDragging(); }
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
@@ -1171,7 +1195,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
style={{
- pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ pointerEvents: this.backgroundEvents ? "all" : undefined,
transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
transformOrigin: this.contentScaling ? "left top" : "",
width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
@@ -1215,19 +1239,25 @@ interface CollectionFreeFormViewPannableContentsProps {
panY: () => number;
zoomScaling: () => number;
easing: () => boolean;
+ viewDefDivClick?: ScriptField;
children: () => JSX.Element[];
+ shifted: boolean;
}
@observer
class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
render() {
- const freeformclass = "collectionfreeformview" + (this.props.easing() ? "-ease" : "-none");
+ const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : (this.props.easing() ? "-ease" : "-none"));
const cenx = this.props.centeringShiftX();
const ceny = this.props.centeringShiftY();
const panx = -this.props.panX();
const pany = -this.props.panY();
const zoom = this.props.zoomScaling();
- return <div className={freeformclass} style={{ touchAction: "none", borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}>
+ return <div className={freeformclass}
+ style={{
+ width: this.props.shifted ? 0 : undefined, height: this.props.shifted ? 0 : undefined,
+ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`
+ }}>
{this.props.children()}
</div>;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 454c3a5f2..cd8166309 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,12 +1,12 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, DataSym, WidthSym, HeightSym, Opt } from "../../../../new_fields/Doc";
import { InkField, InkData } from "../../../../new_fields/InkField";
import { List } from "../../../../new_fields/List";
import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types";
import { Utils } from "../../../../Utils";
-import { Docs, DocUtils } from "../../../documents/Documents";
+import { Docs, DocUtils, DocumentOptions } from "../../../documents/Documents";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
@@ -20,6 +20,7 @@ import { CognitiveServices } from "../../../cognitive_services/CognitiveServices
import { RichTextField } from "../../../../new_fields/RichTextField";
import { CollectionView } from "../CollectionView";
import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { ScriptField } from "../../../../new_fields/ScriptField";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -309,11 +310,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
- getCollection = (selected: Doc[], asTemplate: boolean, isBackground?: boolean) => {
+ getCollection = (selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => {
const bounds = this.Bounds;
// const inkData = this.ink ? this.ink.inkData : undefined;
- const creator = asTemplate ? Docs.Create.StackingDocument : Docs.Create.FreeformDocument;
- const newCollection = creator(selected, {
+ const newCollection = (creator || Docs.Create.FreeformDocument)(selected, {
x: bounds.left,
y: bounds.top,
_panX: 0,
@@ -333,6 +333,18 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
@action
+ pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ const selected = this.marqueeSelect(false);
+ SelectionManager.DeselectAll();
+ selected.forEach(d => this.props.removeDocument(d));
+ const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
+ this.props.addDocument(newCollection);
+ this.props.selectDocuments([newCollection], []);
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ this.hideMarquee();
+ }
+
+ @action
collection = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const bounds = this.Bounds;
const selected = this.marqueeSelect(false);
@@ -345,7 +357,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return d;
});
}
- const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t");
+ const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined);
this.props.addDocument(newCollection);
this.props.selectDocuments([newCollection], []);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -456,7 +468,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
@action
background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
- const newCollection = this.getCollection([], false, true);
+ const newCollection = this.getCollection([], undefined, true);
this.props.addDocument(newCollection);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
@@ -476,7 +488,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.delete();
e.stopPropagation();
}
- if (e.key === "c" || e.key === "b" || e.key === "t" || e.key === "s" || e.key === "S") {
+ if (e.key === "c" || e.key === "b" || e.key === "t" || e.key === "s" || e.key === "S" || e.key === "p") {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
@@ -490,6 +502,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (e.key === "b") {
this.background(e);
}
+ if (e.key === "p") {
+ this.pileup(e);
+ }
this.cleanupInteractions(false);
}
}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 0e1cc2010..9d09ecc3b 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -5,7 +5,7 @@ import { Doc } from '../../../../new_fields/Doc';
import { documentSchema } from '../../../../new_fields/documentSchemas';
import { makeInterface } from '../../../../new_fields/Schema';
import { BoolCast, NumCast, ScriptCast, StrCast, Cast } from '../../../../new_fields/Types';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
@@ -214,21 +214,32 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
}
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
return <ContentFittingDocumentView
- {...this.props}
Document={layout}
DataDocument={layout.resolvedDataDoc as Doc}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- addDocTab={this.addDocTab}
- fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
- FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)}
backgroundColor={this.props.backgroundColor}
- CollectionDoc={this.props.Document}
+ LayoutDoc={this.props.childLayoutTemplate}
+ LibraryPath={this.props.LibraryPath}
+ FreezeDimensions={this.props.freezeChildDimensions}
+ renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
- getTransform={dxf}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+ rootSelected={this.rootSelected}
+ dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
- renderDepth={this.props.renderDepth + 1}
+ getTransform={dxf}
+ focus={this.props.focus}
+ CollectionDoc={this.props.CollectionView?.props.Document}
+ CollectionView={this.props.CollectionView}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
+ active={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ addDocTab={this.addDocTab}
+ pinToPres={this.props.pinToPres}
/>;
}
/**
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 1eb486c4f..af0cc3b5c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -13,7 +13,7 @@ import { Transform } from '../../../util/Transform';
import HeightLabel from './MultirowHeightLabel';
import ResizeBar from './MultirowResizer';
import { undoBatch } from '../../../util/UndoManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
import { List } from '../../../../new_fields/List';
type MultirowDocument = makeInterface<[typeof documentSchema]>;
@@ -214,21 +214,32 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
}
getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
return <ContentFittingDocumentView
- {...this.props}
Document={layout}
DataDocument={layout.resolvedDataDoc as Doc}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- addDocTab={this.addDocTab}
- fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
- FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)}
backgroundColor={this.props.backgroundColor}
- CollectionDoc={this.props.Document}
+ LayoutDoc={this.props.childLayoutTemplate}
+ LibraryPath={this.props.LibraryPath}
+ FreezeDimensions={this.props.freezeChildDimensions}
+ renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
- getTransform={dxf}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+ rootSelected={this.rootSelected}
+ dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
- renderDepth={this.props.renderDepth + 1}
+ getTransform={dxf}
+ focus={this.props.focus}
+ CollectionDoc={this.props.CollectionView?.props.Document}
+ CollectionView={this.props.CollectionView}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
+ active={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ addDocTab={this.addDocTab}
+ pinToPres={this.props.pinToPres}
/>;
}
/**
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 4b282b0c9..1c7d116c5 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,8 @@ import { TraceMobx } from "../../../new_fields/util";
import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- dataProvider?: (doc: Doc) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string } | undefined;
+ dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, z: number, transition?: string } | undefined;
+ sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
x?: number;
y?: number;
z?: number;
@@ -23,6 +24,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
jitterRotation: number;
transition?: string;
fitToBox?: boolean;
+ replica: string;
}
@observer
@@ -40,13 +42,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
get Highlight() { return this.dataProvider?.highlight; }
- get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); }
+ get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.width : this.layoutDoc[WidthSym](); }
get height() {
- const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym]();
+ const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.height : this.layoutDoc[HeightSym]();
return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
}
@computed get freezeDimensions() { return this.props.FreezeDimensions; }
- @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
+ @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
+ @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); }
@computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
@@ -70,8 +73,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
- panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth?.());
- panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight?.());
+ panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
+ panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
getTransform = (): Transform => this.props.ScreenToLocalTransform()
.translate(-this.X, -this.Y)
.scale(1 / this.contentScaling())
diff --git a/src/client/views/nodes/ColorBox.scss b/src/client/views/nodes/ColorBox.scss
index bf334c939..da3266dc1 100644
--- a/src/client/views/nodes/ColorBox.scss
+++ b/src/client/views/nodes/ColorBox.scss
@@ -3,6 +3,7 @@
height:100%;
position: relative;
pointer-events: none;
+ transform-origin: top left;
.sketch-picker {
margin:auto;
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 7583aa070..0d18baaed 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -111,7 +111,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
const childTemplateName = StrCast(this.props.Document.childTemplateName);
if (containedDoc && childTemplateName && !containedDoc["layout_" + childTemplateName]) {
setTimeout(() => {
- DocumentView.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName);
+ Doc.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName);
Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null), containedDoc, undefined);
}, 0);
}
@@ -120,8 +120,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
DataDocument={undefined}
LibraryPath={emptyPath}
CollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
- fitToBox={this.props.fitToBox}
- layoutKey={"layout_" + childTemplateName}
+ fitToBox={true}
+ layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
rootSelected={this.props.isSelected}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index a022f2e02..cd78ac7b3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -58,35 +58,34 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
interface HTMLtagProps {
Document: Doc;
+ RootDoc: Doc;
htmltag: string;
onClick?: ScriptField;
+ onInput?: ScriptField;
}
//"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'><ImageBox {...props} fieldKey={'data'}/><HTMLspan width='100%' marginTop='50%' height='10%' position='absolute' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}grey'>{this.title}</HTMLspan></HTMLdiv>"@observer
@observer
export class HTMLtag extends React.Component<HTMLtagProps> {
click = (e: React.MouseEvent) => {
const clickScript = (this.props as any).onClick as Opt<ScriptField>;
- clickScript?.script.run({ this: this.props.Document });
+ clickScript?.script.run({ this: this.props.Document, self: this.props.RootDoc });
+ }
+ onInput = (e: React.FormEvent<HTMLDivElement>) => {
+ const onInputScript = (this.props as any).onInput as Opt<ScriptField>;
+ onInputScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, value: (e.target as any).textContent });
}
render() {
const style: { [key: string]: any } = {};
- const divKeys = OmitKeys(this.props, ["children", "htmltag", "Document", "key", "onClick", "__proto__"]).omit;
+ const divKeys = OmitKeys(this.props, ["children", "htmltag", "RootDoc", "Document", "key", "onInput", "onClick", "__proto__"]).omit;
Object.keys(divKeys).map((prop: string) => {
- let p = (this.props as any)[prop] as string;
- const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: extend this to support expression -- is this really a script?
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string || "";
- };
- p = p?.replace(/{([^.'][^}']+)}/g, replacer);
-
- const replacer2 = (match: any, key: string, offset: any, string: any) => { // bcz: extend this to support expression -- is this really a script?
- const n = Cast(this.props.Document[key], "number", null);
- return n ? n.toString() : StrCast(this.props.Document[key], p);
+ const p = (this.props as any)[prop] as string;
+ const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value
+ return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ self: this.props.RootDoc, this: this.props.Document }).result as string || "";
};
- style[prop] = p?.replace(/@([a-zA-Z0-9-_]+)/g, replacer2);
-
+ style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer);
});
const Tag = this.props.htmltag as keyof JSX.IntrinsicElements;
- return <Tag style={style} onClick={this.click}>
+ return <Tag style={style} onClick={this.click} onInput={this.onInput as any}>
{this.props.children}
</Tag>;
}
@@ -105,7 +104,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
@computed get layout(): string {
TraceMobx();
if (!this.layoutDoc) return "<p>awaiting layout</p>";
- const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string");
+ // const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string"); // bcz: replaced this with below... is it right?
+ const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, "layout")], "string");
if (this.props.layoutKey === "layout_keyValue") {
return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data"));
} else
@@ -126,16 +126,22 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
}
get layoutDoc() {
const params = StrCast(this.props.Document.PARAMS);
- const template: Doc = this.props.LayoutDoc?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
+ // bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script
+ // const template: Doc = this.props.LayoutDoc?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
+ const template: Doc = this.props.LayoutDoc?.() ||
+ (this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) ||
+ Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined);
return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey);
}
- CreateBindings(onClick: Opt<ScriptField>): JsxBindings {
+ CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
const list = {
...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit,
+ RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc,
Document: this.layoutDoc,
DataDoc: this.dataDoc,
- onClick: onClick
+ onClick: onClick,
+ onInput: onInput
};
return { props: list };
}
@@ -152,7 +158,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
// replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'>
const replacer2 = (match: any, p1: string, offset: any, string: any) => {
- return `<HTMLtag Document={props.Document} htmltag='${p1}'`;
+ return `<HTMLtag RootDoc={props.RootDoc} Document={props.Document} htmltag='${p1}'`;
};
layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
@@ -163,15 +169,20 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
layoutFrame = layoutFrame.replace(/<\/HTML([a-zA-Z0-9_-]+)/g, replacer3);
// add onClick function to props
- const splits = layoutFrame.split("onClick=");
- let onClick: Opt<ScriptField>;
- if (splits.length > 1) {
- const code = XRegExp.matchRecursive(splits[1], "{", "}", "", { valueNames: ["between", "left", "match", "right", "between"] });
- layoutFrame = splits[0] + " onClick={props.onClick} " + splits[1].substring(code[1].end + 1);
- onClick = ScriptField.MakeScript(code[1].value, { this: Doc.name, self: Doc.name });
- }
-
- const bindings = this.CreateBindings(onClick);
+ const makeFuncProp = (func: string) => {
+ const splits = layoutFrame.split(`func=`);
+ if (splits.length > 1) {
+ const code = XRegExp.matchRecursive(splits[1], "{", "}", "", { valueNames: ["between", "left", "match", "right", "between"] });
+ layoutFrame = splits[0] + ` ${func}={props.onClick} ` + splits[1].substring(code[1].end + 1);
+ return ScriptField.MakeScript(code[1].value, { this: Doc.name, self: Doc.name, value: "string" });
+ }
+ return undefined;
+ // add input function to props
+ };
+ const onClick = makeFuncProp("onClick");
+ const onInput = makeFuncProp("onInput");
+
+ const bindings = this.CreateBindings(onClick, onInput);
// layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) :
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f05366a15..4b5fadd93 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -191,7 +191,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@action
componentDidMount() {
- this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
+ this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document));
this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
// this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
@@ -208,7 +208,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.multiTouchDisposer?.();
this.holdDisposer?.();
if (this._mainCont.current) {
- this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this));
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this));
this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
@@ -234,6 +234,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
dragData.dropAction = dropAction;
+ dragData.removeDocument = this.props.removeDocument;
dragData.moveDocument = this.props.moveDocument;// this.Document.onDragStart ? undefined : this.props.moveDocument;
dragData.dragDivName = this.props.dragDivName;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
@@ -280,12 +281,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- onClick = (e: React.MouseEvent | React.PointerEvent) => {
+ onClick = action((e: React.MouseEvent | React.PointerEvent) => {
if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
let stopPropagate = true;
let preventDefault = true;
- this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
+ !this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
if (!(e.nativeEvent as any).formattedHandled) {
const fullScreenAlias = Doc.MakeAlias(this.props.Document);
@@ -307,29 +308,28 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
UndoManager.RunInBatch(func, "on click");
} else func();
} else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
- UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
+ UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
//ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
- } else if (this.Document.isLinkButton) {
+ } else if (this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
} else {
if ((this.props.Document.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
} else {
- if (this.props.Document.type === DocumentType.RTF) {
- DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
- DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
+ // if (this.props.Document.type === DocumentType.RTF) {
+ // DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
+ // DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
- this.props.focus(this.props.Document, false);
- }
- SelectionManager.SelectDoc(this, e.ctrlKey);
+ // this.props.focus(this.props.Document, false);
+ // }
+ SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey);
}
preventDefault = false;
}
stopPropagate && e.stopPropagation();
preventDefault && e.preventDefault();
}
- }
- static _focusHack: number[] = []; // bcz :this will get fixed...
+ });
// follows a link - if the target is on screen, it highlights/pans to it.
// if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
@@ -344,11 +344,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.
return false; // we must return false here so that the zoom to the document is not reversed. If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
};
- this.props.addDocTab(doc, where) && this.props.focus(doc, true, undefined, hackToCallFinishAfterFocus); // add the target and focus on it.
+ this.props.addDocTab(doc, where) && this.props.focus(doc, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); // add the target and focus on it.
return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
};
- // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
- this.props.focus(this.props.Document, true, 1, targetFocusAfterDocFocus);
+ if (!this.Document.followLinkZoom) {
+ targetFocusAfterDocFocus();
+ } else {
+ // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
+ this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus);
+ }
};
await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
}
@@ -403,6 +407,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
+ public iconify() {
+ const layoutKey = Cast(this.props.Document.layoutKey, "string", null);
+ const collapse = layoutKey !== "layout_icon";
+ if (collapse) {
+ this.switchViews(collapse, "icon");
+ if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.props.Document.deiconifyLayout = layoutKey.replace("layout_", "");
+ } else {
+ const deiconifyLayout = Cast(this.props.Document.deiconifyLayout, "string", null);
+ this.switchViews(deiconifyLayout ? true : false, deiconifyLayout);
+ this.props.Document.deiconifyLayout = undefined;
+ }
+ }
+
@action
handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
@@ -547,56 +564,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); }
- // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
- static makeCustomViewClicked = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
- const batch = UndoManager.StartBatch("makeCustomViewClicked");
- runInAction(() => {
- doc.layoutKey = "layout_" + templateSignature;
- if (doc[doc.layoutKey] === undefined) {
- DocumentView.createCustomView(doc, creator, templateSignature, docLayoutTemplate);
- }
- });
- batch.end();
- }
- static findTemplate(templateName: string, type: string, signature: string) {
- let docLayoutTemplate: Opt<Doc>;
- const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data);
- const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
- const noteTypes = DocListCast(Cast(Doc.UserDoc().noteTypes, Doc, null)?.data);
- const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
- const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
- // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
- // first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc));
- !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
- return docLayoutTemplate;
- }
- static createCustomView = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
- const templateName = templateSignature.replace(/\(.*\)/, "");
- docLayoutTemplate = docLayoutTemplate || DocumentView.findTemplate(templateName, StrCast(doc.type), templateSignature);
-
- const customName = "layout_" + templateSignature;
- const _width = NumCast(doc._width);
- const _height = NumCast(doc._height);
- const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false };
-
- let fieldTemplate: Opt<Doc>;
- if (doc.data instanceof RichTextField || typeof (doc.data) === "string") {
- fieldTemplate = Docs.Create.TextDocument("", options);
- } else if (doc.data instanceof PdfField) {
- fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
- } else if (doc.data instanceof VideoField) {
- fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
- } else if (doc.data instanceof AudioField) {
- fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
- } else if (doc.data instanceof ImageField) {
- fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
- }
- const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
-
- fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
- docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
- }
@undoBatch
toggleLinkButtonBehavior = (): void => {
@@ -606,6 +573,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.Document.onClick = undefined;
} else {
this.Document.isLinkButton = true;
+ this.Document.followLinkZoom = false;
this.Document.followLinkLocation = undefined;
}
}
@@ -616,11 +584,25 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.Document.isLinkButton = false;
} else {
this.Document.isLinkButton = true;
+ this.Document.followLinkZoom = true;
this.Document.followLinkLocation = "inPlace";
}
}
@undoBatch
+ toggleFollowOnRight = (): void => {
+ if (this.Document.isLinkButton) {
+ this.Document.isLinkButton = false;
+ } else {
+ this.Document.isLinkButton = true;
+ this.Document.followLinkZoom = false;
+ const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+ first && (first.hidden = true);
+ this.Document.followLinkLocation = "onRight";
+ }
+ }
+
+ @undoBatch
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
if (de.complete.annoDragData) {
@@ -664,20 +646,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
}
+ this.Document.followLinkZoom = true;
this.Document.isLinkButton = true;
}
@undoBatch
@action
- setCustomView = (custom: boolean, layout: string): void => {
- Doc.setNativeView(this.props.Document);
- if (custom) {
- DocumentView.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
- }
- }
-
- @undoBatch
- @action
toggleBackground = (temporary: boolean): void => {
this.Document.overflow = temporary ? "visible" : "hidden";
this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
@@ -725,20 +699,27 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
- const existing = cm.findByDescription("Options...");
- const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
- layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
- layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
- layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
- !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "compass" });
-
- const open = cm.findByDescription("Open New Perspective...");
+ let open = cm.findByDescription("Add a Perspective...");
const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
openItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
templateDoc && openItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
- !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
+ if (!open) {
+ open = { description: "Add a Perspective....", subitems: openItems, icon: "external-link-alt" };
+ cm.addItem(open);
+ }
+
+ let options = cm.findByDescription("Options...");
+ const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+ optionItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+ optionItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+ optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+ optionItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+ if (!options) {
+ options = { description: "Options...", subitems: optionItems, icon: "compass" };
+ cm.addItem(options);
+ }
+ cm.moveAfter(options, open);
const existingOnClick = cm.findByDescription("OnClick...");
const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
@@ -746,8 +727,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
+ onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
- onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
+ onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
!existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
const funcs: ContextMenuProps[] = [];
@@ -1090,9 +1072,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
layoutKey={this.finalLayoutKey} />
</div>);
const titleView = (!showTitle ? (null) :
- <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{
+ <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
position: showTextTitle ? "relative" : "absolute",
- pointerEvents: SelectionManager.GetIsDragging() || this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
+ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
}}>
<EditableView ref={this._titleRef}
contents={(this.props.DataDoc || this.props.Document)[showTitle]?.toString()}
@@ -1104,17 +1086,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return !showTitle && !showCaption ?
this.contents :
<div className="documentView-styleWrapper" >
- <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}>
- {this.contents}
- </div>
- {titleView}
+ {this.Document.type !== DocumentType.RTF ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
{captionView}
</div>;
}
@computed get ignorePointerEvents() {
- return this.props.pointerEvents === false || (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+ return this.props.pointerEvents === false ||
+ (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) ||
+ (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+ }
+ @undoBatch
+ @action
+ setCustomView = (custom: boolean, layout: string): void => {
+ Doc.setNativeView(this.props.Document);
+ if (custom) {
+ Doc.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
+ }
}
-
@observable _animate = 0;
switchViews = action((custom: boolean, view: string) => {
SelectionManager.SetIsDragging(true);
@@ -1156,7 +1144,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
background: finalColor,
- opacity: this.Document.opacity
+ opacity: this.Document.opacity,
+ fontFamily: StrCast(this.Document._fontFamily, "inherit"),
+ fontSize: Cast(this.Document._fontSize, "number", null)
}}>
{this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <>
{this.innards}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 7a05ec3a3..3bedb7127 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -24,7 +24,6 @@
overflow-y: auto;
overflow-x: hidden;
color: initial;
- height: 100%;
max-height: 100%;
display: flex;
flex-direction: row;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index fd7462a10..3d6ca66f0 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -13,22 +13,24 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCastAsync, Field, HeightSym, Opt, WidthSym, DocListCast } from "../../../new_fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
import { documentSchema } from '../../../new_fields/documentSchemas';
import { Id } from '../../../new_fields/FieldSymbols';
import { InkTool } from '../../../new_fields/InkField';
+import { PrefetchProxy } from '../../../new_fields/Proxy';
import { RichTextField } from "../../../new_fields/RichTextField";
import { RichTextUtils } from '../../../new_fields/RichTextUtils';
import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast, DateCast } from "../../../new_fields/Types";
+import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
import { TraceMobx } from '../../../new_fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue, returnZero } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DictationManager } from '../../util/DictationManager';
import { DragManager } from "../../util/DragManager";
+import { makeTemplate } from '../../util/DropConverter';
import buildKeymap from "../../util/ProsemirrorExampleTransfer";
import RichTextMenu from '../../util/RichTextMenu';
import { RichTextRules } from "../../util/RichTextRules";
@@ -46,9 +48,6 @@ import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
import React = require("react");
-import { PrefetchProxy } from '../../../new_fields/Proxy';
-import { makeTemplate } from '../../util/DropConverter';
-import { DocumentView } from './DocumentView';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -84,17 +83,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _lastX = 0;
private _lastY = 0;
private _undoTyping?: UndoManager.Batch;
- private _searchReactionDisposer?: Lambda;
- private _recordReactionDisposer: Opt<IReactionDisposer>;
- private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>;
- private _reactionDisposer: Opt<IReactionDisposer>;
- private _heightReactionDisposer: Opt<IReactionDisposer>;
- private _proxyReactionDisposer: Opt<IReactionDisposer>;
- private _pullReactionDisposer: Opt<IReactionDisposer>;
- private _pushReactionDisposer: Opt<IReactionDisposer>;
- private _buttonBarReactionDisposer: Opt<IReactionDisposer>;
- private _linkMakerDisposer: Opt<IReactionDisposer>;
- private _scrollDisposer: Opt<IReactionDisposer>;
+ private _disposers: { [name: string]: IReactionDisposer } = {};
private dropDisposer?: DragManager.DragDropDisposer;
@computed get _recording() { return this.dataDoc.audioState === "recording"; }
@@ -195,7 +184,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const tsel = this._editorView.state.selection.$from;
tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
- const curText = state.doc.textBetween(0, state.doc.content.size, "\n\n");
+ const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);
if (!this._applyingChange) {
this._applyingChange = true;
@@ -223,6 +212,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
+ // needs a better API for taking in a set of words with target documents instead of just one target
+ public hyperlinkTerms = (terms: string[], target: Doc) => {
+ if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
+ const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
+ let tr = this._editorView.state.tr;
+ const flattened: TextSelection[] = [];
+ res.map(r => r.map(h => flattened.push(h)));
+ const lastSel = Math.min(flattened.length - 1, this._searchIndex);
+ this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
+ const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!;
+ const link = this._editorView.state.schema.marks.link.create({
+ href: Utils.prepend("/doc/" + alink[Id]),
+ title: "a link", location: location, linkId: alink[Id], targetId: target[Id]
+ });
+ this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
+ }
+ }
public highlightSearchTerms = (terms: string[]) => {
if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
@@ -254,7 +260,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
protected createDropTarget = (ele: HTMLDivElement) => {
this.ProseRef = ele;
this.dropDisposer?.();
- ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
+ ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
}
@undoBatch
@@ -342,10 +348,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
updateHighlights = () => {
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-remote", { background: "yellow" });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
}
if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
}
if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
@@ -360,15 +366,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
}
if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
const min = Math.round(Date.now() / 1000 / 60);
- numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
setTimeout(() => this.updateHighlights());
}
if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
const hr = Math.round(Date.now() / 1000 / 60 / 60);
- numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
}
@@ -404,8 +410,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
!this.props.Document.rootDocument && funcs.push({
description: "Make Template", event: () => {
- this.props.Document.isTemplateDoc = makeTemplate(this.props.Document, true);
- Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document);
+ this.props.Document.isTemplateDoc = makeTemplate(this.props.Document);
+ Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document);
}, icon: "eye"
});
funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
@@ -433,30 +439,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const change = cm.findByDescription("Change Perspective...");
const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
- const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null);
- const noteTypes = DocListCast(noteTypesDoc?.data);
- noteTypes.forEach(note => {
+ const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
+ DocListCast(noteTypesDoc?.data).forEach(note => {
changeItems.push({
- description: StrCast(note.title), event: () => {
+ description: StrCast(note.title), event: undoBatch(() => {
Doc.setNativeView(this.props.Document);
- DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
- }, icon: "eye"
+ Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+ }), icon: "eye"
});
});
- changeItems.push({ description: "FreeForm", event: () => DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
+ changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
!change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
- const open = cm.findByDescription("Open New Perspective...");
+ const open = cm.findByDescription("Add a Perspective...");
const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
openItems.push({
- description: "FreeForm", event: () => {
+ description: "FreeForm", event: undoBatch(() => {
const alias = Doc.MakeAlias(this.rootDoc);
- DocumentView.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
+ Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
this.props.addDocTab(alias, "onRight");
- }, icon: "eye"
+ }), icon: "eye"
});
- !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
+ !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
}
@@ -566,7 +571,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
componentDidMount() {
- this._buttonBarReactionDisposer = reaction(
+ this._disposers.buttonBar = reaction(
() => DocumentButtonBar.Instance,
instance => {
if (instance) {
@@ -575,7 +580,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
);
- this._linkMakerDisposer = reaction(
+ this._disposers.linkMaker = reaction(
() => this.props.makeLink?.(),
(linkDoc: Opt<Doc>) => {
if (linkDoc) {
@@ -586,8 +591,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
},
{ fireImmediately: true }
);
-
- this._reactionDisposer = reaction(
+ this._disposers.editorState = reaction(
() => {
if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) {
return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
@@ -602,8 +606,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
);
-
- this._pullReactionDisposer = reaction(
+ this._disposers.pullDoc = reaction(
() => this.props.Document[Pulls],
() => {
if (!DocumentButtonBar.hasPulledHack) {
@@ -613,8 +616,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
);
-
- this._pushReactionDisposer = reaction(
+ this._disposers.pushDoc = reaction(
() => this.props.Document[Pushes],
() => {
if (!DocumentButtonBar.hasPushedHack) {
@@ -623,19 +625,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
);
-
- this._heightReactionDisposer = reaction(
+ this._disposers.height = reaction(
() => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
() => this.tryUpdateHeight()
);
this.setupEditor(this.config, this.props.fieldKey);
- this._searchReactionDisposer = reaction(() => this.rootDoc.searchMatch,
+ this._disposers.search = reaction(() => this.rootDoc.searchMatch,
search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
{ fireImmediately: true });
- this._recordReactionDisposer = reaction(() => this._recording,
+ this._disposers.record = reaction(() => this._recording,
() => {
if (this._recording) {
setTimeout(action(() => {
@@ -645,8 +646,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
} else setTimeout(() => this.stopDictation(true), 0);
}
);
-
- this._scrollToRegionReactionDisposer = reaction(
+ this._disposers.scrollToRegion = reaction(
() => StrCast(this.layoutDoc.scrollToLinkID),
async (scrollToLinkID) => {
const findLinkFrag = (frag: Fragment, editor: EditorView) => {
@@ -691,8 +691,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
},
{ fireImmediately: true }
);
-
- this._scrollDisposer = reaction(() => NumCast(this.props.Document.scrollPos),
+ this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos),
pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
);
@@ -878,7 +877,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
if (startupText) {
- this._editorView.dispatch(this._editorView.state.tr.insertText(startupText));
+ const { state: { tr }, dispatch } = this._editorView;
+ dispatch(tr.insertText(startupText));
}
}
@@ -908,17 +908,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
componentWillUnmount() {
- this._scrollDisposer?.();
- this._scrollToRegionReactionDisposer?.();
- this._reactionDisposer?.();
- this._proxyReactionDisposer?.();
- this._pushReactionDisposer?.();
- this._pullReactionDisposer?.();
- this._heightReactionDisposer?.();
- this._searchReactionDisposer?.();
- this._recordReactionDisposer?.();
- this._buttonBarReactionDisposer?.();
- this._linkMakerDisposer?.();
+ Object.values(this._disposers).forEach(disposer => disposer?.());
this._editorView?.destroy();
}
@@ -1213,13 +1203,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return (
<div className={`formattedTextBox-cont`} ref={this._ref}
style={{
- height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined,
+ height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"]),
opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
color: this.props.hideOnLeave ? "white" : "inherit",
pointerEvents: interactive ? "none" : undefined,
- fontSize: NumCast(this.layoutDoc.fontSize, 13),
- fontFamily: StrCast(this.layoutDoc.fontFamily, "Crimson Text"),
+ fontSize: Cast(this.layoutDoc._fontSize, "number", null),
+ fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyPress}
@@ -1237,7 +1227,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
<div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
<div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
style={{
- padding: `${NumCast(this.layoutDoc._xMargin, 0)}px ${NumCast(this.layoutDoc._yMargin, 0)}px`,
+ padding: `${NumCast(this.layoutDoc._yMargin, 0)}px ${NumCast(this.layoutDoc._xMargin, 0)}px`,
pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
}} />
</div>
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 41df5b3c1..7d441a48b 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -16,7 +16,6 @@ import React = require("react");
import { Docs } from "../../documents/Documents";
import wiki from "wikijs";
import { DocumentType } from "../../documents/DocumentTypes";
-import { DocumentView } from "./DocumentView";
export let formattedTextBoxCommentPlugin = new Plugin({
view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -88,7 +87,6 @@ export class FormattedTextBoxComment {
if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
} else {
- DocumentView._focusHack = [];
DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
(doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 49425c2c2..15148d01d 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,5 +1,4 @@
-.imageBox,
-.imageBox-dragging {
+.imageBox {
border-radius: inherit;
width: 100%;
height: 100%;
@@ -11,12 +10,6 @@
}
}
-.imageBox-dragging {
- .imageBox-fader {
- pointer-events: none;
- }
-}
-
#upload-icon {
position: absolute;
bottom: 0;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index cf6a7ba5b..08917d281 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -76,7 +76,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer && this._dropDisposer();
- ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
+ ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
}
@undoBatch
@@ -223,8 +223,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
return url.href;//Why is this here
}
const ext = path.extname(url.href);
- const suffix = this.props.renderDepth < 1 ? "_o" : this._curSuffix;
- return url.href.replace(ext, suffix + ext);
+ this._curSuffix = this.props.renderDepth < 1 ? "_o" : this.layoutDoc[WidthSym]() < 100 ? "_s" : "_m";
+ return url.href.replace(ext, this._curSuffix + ext);
}
@observable _smallRetryCount = 1;
@@ -360,7 +360,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
}
@computed get nativeSize() {
- const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
+ TraceMobx();
+ const pw = this.props.PanelWidth?.() || 50;
const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], pw);
const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1);
return { nativeWidth, nativeHeight };
@@ -374,7 +375,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
@computed get paths() {
const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
const alts = DocListCast(this.dataDoc[this.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
- const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url.href).filter(url => url); // access the primary layout data of the alternate documents
+ const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
return paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
}
@@ -438,8 +439,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
contentFunc = () => [this.content];
render() {
TraceMobx();
- const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
- return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
+ return (<div className={`imageBox`} onContextMenu={this.specificContextMenu}
style={{
transform: this.props.PanelWidth() ? `translate(0px, ${this.ycenter}px)` : `scale(${this.props.ContentScaling()})`,
width: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index 56dd86ff9..7c7e92379 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -18,8 +18,6 @@
.labelBox-mainButtonCenter {
overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
display: inline;
align-items: center;
margin: auto;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 5c2fc3ffe..3cdec8acb 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -32,7 +32,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer?.();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document);
}
}
@@ -72,9 +72,16 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
<div className="labelBox-mainButton" style={{
background: StrCast(this.layoutDoc.backgroundColor),
color: StrCast(this.layoutDoc.color, "inherit"),
- fontSize: NumCast(this.layoutDoc.fontSize) || "inherit",
+ fontSize: NumCast(this.layoutDoc._fontSize) || "inherit",
+ fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit",
letterSpacing: StrCast(this.layoutDoc.letterSpacing),
- textTransform: StrCast(this.layoutDoc.textTransform) as any
+ textTransform: StrCast(this.layoutDoc.textTransform) as any,
+ paddingLeft: NumCast(this.layoutDoc._xPadding),
+ paddingRight: NumCast(this.layoutDoc._xPadding),
+ paddingTop: NumCast(this.layoutDoc._yPadding),
+ paddingBottom: NumCast(this.layoutDoc._yPadding),
+ textOverflow: this.layoutDoc._singleLine ? "ellipsis" : undefined,
+ whiteSpace: this.layoutDoc._singleLine ? "nowrap" : "pre-wrap"
}} >
<div className="labelBox-mainButtonCenter">
{StrCast(this.rootDoc.text, StrCast(this.rootDoc.title))}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index eb647d0e4..707b9d12a 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -75,7 +75,6 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
this._timeout = setTimeout(action(() => {
- DocumentView._focusHack = [];
DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "inTab")), false);
this._editing = false;
}), 300 - (Date.now() - this._lastTap));
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index 6676d8cd2..78c19f351 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -11,6 +11,11 @@
overflow: hidden;
transition: 0.7s opacity ease;
+ .presBox-listCont {
+ position: absolute;
+ height: calc(100% - 25px);
+ width: 100%;
+ }
.presBox-buttons {
padding: 10px;
width: 100%;
diff --git a/src/client/views/nodes/RadialMenu.scss b/src/client/views/nodes/RadialMenu.scss
index ce0c263ef..daa620d12 100644
--- a/src/client/views/nodes/RadialMenu.scss
+++ b/src/client/views/nodes/RadialMenu.scss
@@ -67,17 +67,4 @@ s
margin-left: 5px;
text-align: left;
display: inline; //need this?
-}
-
-
-
-.icon-background {
- pointer-events: all;
- height:100%;
- margin-top: 15px;
- background-color: transparent;
- width: 35px;
- text-align: center;
- font-size: 20px;
- margin-left: 5px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 0ffed78de..ddfdb67b4 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -1,12 +1,9 @@
import React = require("react");
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { action, observable, computed, IReactionDisposer, reaction, runInAction } from "mobx";
-import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import Measure from "react-measure";
-import "./RadialMenu.scss";
-import MobileInkOverlay from "../../../mobile/MobileInkOverlay";
import MobileInterface from "../../../mobile/MobileInterface";
+import "./RadialMenu.scss";
+import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem";
@observer
export class RadialMenu extends React.Component {
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index b2d451ea8..cb2526769 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -56,7 +56,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
style={{ boxShadow: this.layoutDoc.opacity === 0 ? undefined : StrCast(this.layoutDoc.boxShadow, "") }}>
<div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{
background: StrCast(this.layoutDoc.backgroundColor), color: StrCast(this.layoutDoc.color, "black"),
- fontSize: NumCast(this.layoutDoc.fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
+ fontSize: NumCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
}} >
<Slider
mode={2}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 66ddf64c9..4e383e468 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -317,7 +317,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (field instanceof HtmlField) {
view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
- view = <iframe ref={this._iframeRef} onLoad={this.iframeLoaded} src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+ const url = this.layoutDoc.UseCors ? Utils.CorsProxy(field.url.href) : field.url.href;
+ view = <iframe ref={this._iframeRef} onLoad={this.iframeLoaded} src={url} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
} else {
view = <iframe ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index eaf80d252..672d3adb8 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -98,7 +98,6 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
else if (e.button === 0) {
const annoGroup = await Cast(this.props.document.group, Doc);
if (annoGroup) {
- DocumentView._focusHack = [];
DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined);
e.stopPropagation();
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 46b60b554..acaa4363e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,37 +1,36 @@
-import { action, computed, IReactionDisposer, observable, reaction, trace, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
+import * as rp from "request-promise";
import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, FieldResult, WidthSym, Opt, HeightSym } from "../../../new_fields/Doc";
-import { Id, Copy } from "../../../new_fields/FieldSymbols";
+import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { InkTool } from "../../../new_fields/InkField";
import { List } from "../../../new_fields/List";
-import { makeInterface, createSchema } from "../../../new_fields/Schema";
-import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero, emptyPath } from "../../../Utils";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, NumCast } from "../../../new_fields/Types";
+import { PdfField } from "../../../new_fields/URLField";
+import { TraceMobx } from "../../../new_fields/util";
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DragManager } from "../../util/DragManager";
import { CompiledScript, CompileScript } from "../../util/Scripting";
-import { Transform } from "../../util/Transform";
-import PDFMenu from "./PDFMenu";
-import "./PDFViewer.scss";
-import React = require("react");
-import * as rp from "request-promise";
-import { CollectionView } from "../collections/CollectionView";
-import Annotation from "./Annotation";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CollectionView } from "../collections/CollectionView";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { documentSchema } from "../../../new_fields/documentSchemas";
import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
-import { InkTool } from "../../../new_fields/InkField";
-import { TraceMobx } from "../../../new_fields/util";
-import { PdfField } from "../../../new_fields/URLField";
-import { DocumentView } from "../nodes/DocumentView";
+import Annotation from "./Annotation";
+import PDFMenu from "./PDFMenu";
+import "./PDFViewer.scss";
+import React = require("react");
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
const pdfjsLib = require("pdfjs-dist");
@@ -574,7 +573,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
clipDoc.rootDocument = targetDoc;
- DocumentView.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
+ Doc.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
targetDoc.layoutKey = "layout";
// const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
// Doc.GetProto(targetDoc).snipped = this.dataDoc[this.props.fieldKey][Copy]();
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 5f790f886..337274774 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -14,10 +14,11 @@ import { PrefetchProxy, ProxyField } from "./Proxy";
import { FieldId, RefField } from "./RefField";
import { RichTextField } from "./RichTextField";
import { listSpec } from "./Schema";
-import { ComputedField } from "./ScriptField";
+import { ComputedField, ScriptField } from "./ScriptField";
import { Cast, FieldValue, NumCast, StrCast, ToConstructor, ScriptCast } from "./Types";
import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
-import { Docs } from "../client/documents/Documents";
+import { Docs, DocumentOptions } from "../client/documents/Documents";
+import { PdfField, VideoField, AudioField, ImageField } from "./URLField";
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -913,6 +914,92 @@ export namespace Doc {
return false;
}
+ // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
+ export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
+ const batch = UndoManager.StartBatch("makeCustomViewClicked");
+ runInAction(() => {
+ doc.layoutKey = "layout_" + templateSignature;
+ if (doc[doc.layoutKey] === undefined) {
+ createCustomView(doc, creator, templateSignature, docLayoutTemplate);
+ }
+ });
+ batch.end();
+ }
+ export function findTemplate(templateName: string, type: string, signature: string) {
+ let docLayoutTemplate: Opt<Doc>;
+ const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data);
+ const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
+ const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
+ const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
+ const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
+ // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized
+ // first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName>
+ !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc));
+ !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
+ return docLayoutTemplate;
+ }
+ export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
+ const templateName = templateSignature.replace(/\(.*\)/, "");
+ docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.type), templateSignature);
+
+ const customName = "layout_" + templateSignature;
+ const _width = NumCast(doc._width);
+ const _height = NumCast(doc._height);
+ const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false };
+
+ let fieldTemplate: Opt<Doc>;
+ if (doc.data instanceof RichTextField || typeof (doc.data) === "string") {
+ fieldTemplate = Docs.Create.TextDocument("", options);
+ } else if (doc.data instanceof PdfField) {
+ fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
+ } else if (doc.data instanceof VideoField) {
+ fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
+ } else if (doc.data instanceof AudioField) {
+ fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
+ } else if (doc.data instanceof ImageField) {
+ fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
+ }
+ const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+
+ fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
+ docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+ }
+ export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
+ Doc.setNativeView(doc);
+ if (custom) {
+ makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined);
+ }
+ }
+ export function iconify(doc: Doc) {
+ const layoutKey = Cast(doc.layoutKey, "string", null);
+ Doc.makeCustomViewClicked(doc, Docs.Create.StackingDocument, "icon", undefined);
+ if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
+ }
+
+ export function pileup(selected: Doc[], x: number, y: number) {
+ const newCollection = Docs.Create.PileDocument(selected, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
+ let w = 0, h = 0;
+ selected.forEach((d, i) => {
+ Doc.iconify(d);
+ w = Math.max(d[WidthSym](), w);
+ h = Math.max(d[HeightSym](), h);
+ });
+ h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable
+ selected.forEach((d, i) => {
+ d.x = Math.cos(Math.PI * 2 * i / selected.length) * 10 - w / 2;
+ d.y = Math.sin(Math.PI * 2 * i / selected.length) * 10 - h / 2;
+ d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ });
+ newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
+ newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
+ newCollection._width = newCollection._height = 110;
+ //newCollection.borderRounding = "40px";
+ newCollection._jitterRotation = 10;
+ newCollection._backgroundColor = "gray";
+ newCollection.overflow = "visible";
+ return newCollection;
+ }
+
export async function addFieldEnumerations(doc: Opt<Doc>, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) {
let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey);
diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts
index c211b3d3c..635fd053d 100644
--- a/src/new_fields/RichTextUtils.ts
+++ b/src/new_fields/RichTextUtils.ts
@@ -1,23 +1,21 @@
-import { EditorState, Transaction, TextSelection } from "prosemirror-state";
-import { Node, Fragment, Mark } from "prosemirror-model";
-import { RichTextField } from "./RichTextField";
+import { AssertionError } from "assert";
import { docs_v1 } from "googleapis";
-import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils";
-import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox";
-import { Opt, Doc } from "./Doc";
-import Color = require('color');
+import { Fragment, Mark, Node } from "prosemirror-model";
import { sinkListItem } from "prosemirror-schema-list";
-import { Utils } from "../Utils";
-import { Docs } from "../client/documents/Documents";
-import { schema } from "../client/util/RichTextSchema";
+import { EditorState, TextSelection, Transaction } from "prosemirror-state";
+import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils";
import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils";
import { DocServer } from "../client/DocServer";
-import { Cast, StrCast } from "./Types";
-import { Id } from "./FieldSymbols";
-import { DocumentView } from "../client/views/nodes/DocumentView";
-import { AssertionError } from "assert";
+import { Docs } from "../client/documents/Documents";
import { Networking } from "../client/Network";
-import { extname } from "path";
+import { schema } from "../client/util/RichTextSchema";
+import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox";
+import { Utils } from "../Utils";
+import { Doc, Opt } from "./Doc";
+import { Id } from "./FieldSymbols";
+import { RichTextField } from "./RichTextField";
+import { Cast, StrCast } from "./Types";
+import Color = require('color');
export namespace RichTextUtils {
@@ -274,7 +272,7 @@ export namespace RichTextUtils {
const backingDocId = StrCast(textNote[guid]);
if (!backingDocId) {
const backingDoc = Docs.Create.ImageDocument(agnostic, { _width: 300, _height: 300 });
- DocumentView.makeCustomViewClicked(backingDoc, Docs.Create.FreeformDocument);
+ Doc.makeCustomViewClicked(backingDoc, Docs.Create.FreeformDocument);
docid = backingDoc[Id];
textNote[guid] = docid;
} else {
@@ -403,7 +401,7 @@ export namespace RichTextUtils {
let exported = (await Cast(linkDoc.anchor2, Doc))!;
if (!exported.customLayout) {
exported = Doc.MakeAlias(exported);
- DocumentView.makeCustomViewClicked(exported, Docs.Create.FreeformDocument);
+ Doc.makeCustomViewClicked(exported, Docs.Create.FreeformDocument);
linkDoc.anchor2 = exported;
}
url = Utils.shareUrl(exported[Id]);
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 61b185e5c..7a0be8863 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -8,7 +8,8 @@ export const documentSchema = createSchema({
layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below
layoutKey: "string", // holds the field key for the field that actually holds the current lyoat
title: "string", // document title (can be on either data document or layout)
- dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy")
+ dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
+ targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move")
childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
_autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents
_nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set
@@ -29,6 +30,8 @@ export const documentSchema = createSchema({
_replacedChrome: "string", // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
_chromeStatus: "string", // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
_freezeChildDimensions: "boolean", // freezes child document dimensions (e.g., used by time/pivot view to make sure all children will be scaled to fit their display rectangle)
+ _fontSize: "number",
+ _fontFamily: "string",
isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
color: "string", // foreground color of document
backgroundColor: "string", // background color of document
diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts
index e7a0d367d..713207a07 100644
--- a/src/scraping/buxton/final/BuxtonImporter.ts
+++ b/src/scraping/buxton/final/BuxtonImporter.ts
@@ -16,6 +16,7 @@ interface DocumentContents {
hyperlinks: string[];
captions: string[];
embeddedFileNames: string[];
+ longDescription: string;
}
export interface DeviceDocument {
@@ -186,10 +187,6 @@ const RegexMap = new Map<keyof DeviceDocument, Processor<any>>([
exp: /Short Description:\s+(.*)Bill Buxton[’']s Notes/,
transformer: Utilities.correctSentences
}],
- ["longDescription", {
- exp: /Bill Buxton[’']s Notes(.*)Device Details/,
- transformer: Utilities.correctSentences
- }],
]);
const sourceDir = path.resolve(__dirname, "source");
@@ -267,7 +264,12 @@ async function extractFileContents(pathToDocument: string): Promise<DocumentCont
const body = document.root()?.text() ?? "No body found. Check the import script's XML parser.";
const captions: string[] = [];
const embeddedFileNames: string[] = [];
- const captionTargets = document.find(tableCellXPath).map(node => node.text());
+ const captionTargets = document.find(tableCellXPath).map(node => node.text().trim());
+
+ const paragraphs = document.find('//*[name()="w:p"]').map(node => Utilities.correctSentences(node.text()).transformed!);
+ const start = paragraphs.indexOf(paragraphs.find(el => /Bill Buxton[’']s Notes/.test(el))!) + 1;
+ const end = paragraphs.indexOf("Device Details");
+ const longDescription = paragraphs.slice(start, end).filter(paragraph => paragraph.length).join("\n\n");
const { length } = captionTargets;
strictEqual(length > 3, true, "No captions written.");
@@ -275,8 +277,8 @@ async function extractFileContents(pathToDocument: string): Promise<DocumentCont
for (let i = 3; i < captionTargets.length; i += 3) {
const row = captionTargets.slice(i, i + 3);
- captions.push(row[1]);
- embeddedFileNames.push(row[2]);
+ embeddedFileNames.push(row[1]);
+ captions.push(row[2]);
}
// extract all hyperlinks embedded in the document
@@ -290,7 +292,7 @@ async function extractFileContents(pathToDocument: string): Promise<DocumentCont
zip.close();
- return { body, imageData, captions, embeddedFileNames, hyperlinks };
+ return { body, longDescription, imageData, captions, embeddedFileNames, hyperlinks };
}
const imageEntry = /^word\/media\/\w+\.(jpeg|jpg|png|gif)/;
@@ -306,26 +308,30 @@ async function writeImages(zip: any): Promise<ImageData[]> {
const imageEntries = allEntries.filter(name => imageEntry.test(name));
const imageUrls: ImageData[] = [];
- for (const mediaPath of imageEntries) {
- const getImageStream = () => new Promise<Readable>((resolve, reject) => {
- zip.stream(mediaPath, (error: any, stream: any) => error ? reject(error) : resolve(stream));
- });
+ const valid: any[] = [];
+ const getImageStream = (mediaPath: string) => new Promise<Readable>((resolve, reject) => {
+ zip.stream(mediaPath, (error: any, stream: any) => error ? reject(error) : resolve(stream));
+ });
+
+ for (const mediaPath of imageEntries) {
const { width, height, type } = await new Promise<Dimensions>(async resolve => {
const sizeStream = (createImageSizeStream() as PassThrough).on('size', (dimensions: Dimensions) => {
readStream.destroy();
resolve(dimensions);
}).on("error", () => readStream.destroy());
- const readStream = await getImageStream();
+ const readStream = await getImageStream(mediaPath);
readStream.pipe(sizeStream);
});
- if (Math.abs(width - height) < 10) {
- continue;
+
+ if (Math.abs(width - height) > 10) {
+ valid.push({ width, height, type, mediaPath });
}
+ }
+ for (const { type, width, height, mediaPath } of valid) {
const generatedFileName = `upload_${Utils.GenerateGuid()}.${type.toLowerCase()}`;
- await DashUploadUtils.outputResizedImages(getImageStream, generatedFileName, imageDir);
-
+ await DashUploadUtils.outputResizedImages(() => getImageStream(mediaPath), generatedFileName, imageDir);
imageUrls.push({
url: `/files/images/buxton/${generatedFileName}`,
nativeWidth: width,
@@ -337,11 +343,12 @@ async function writeImages(zip: any): Promise<ImageData[]> {
}
function analyze(fileName: string, contents: DocumentContents): AnalysisResult {
- const { body, imageData, captions, hyperlinks, embeddedFileNames } = contents;
+ const { body, imageData, captions, hyperlinks, embeddedFileNames, longDescription } = contents;
const device: any = {
hyperlinks,
captions,
embeddedFileNames,
+ longDescription,
__images: imageData
};
const errors: { [key: string]: string } = { fileName };
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 82efe5d4e..b5a44a8bc 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -82,8 +82,49 @@ export class CurrentUserUtils {
});
}
+ if (doc["template-button-detail"] === undefined) {
+ const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create;
+
+ const carousel = CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" });
+
+ const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true });
+ const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 50, _autoHeight: true });
+ const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 350, _autoHeight: true });
+
+ const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"];
+ const detailedTemplate = {
+ doc: {
+ type: "doc", content: buxtonFieldKeys.map(fieldKey => ({
+ type: "paragraph",
+ content: [{ type: "dashField", attrs: { fieldKey } }]
+ }))
+ },
+ selection: { type: "text", anchor: 1, head: 1 },
+ storedMarks: []
+ };
+ details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" "));
+
+ const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 };
+ const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 };
+ const descriptionWrapperOpts = { title: "descriptions", _height: 300, columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" };
+
+ const descriptionWrapper = MasonryDocument([short, long], { ...shared, ...descriptionWrapperOpts });
+ const detailView = Docs.Create.StackingDocument([carousel, details, descriptionWrapper], { ...shared, ...detailViewOpts });
+ detailView.isTemplateDoc = makeTemplate(detailView);
+
+ short.title = "A Short Description";
+ long.title = "Long Description";
+
+ doc["template-button-detail"] = CurrentUserUtils.ficon({
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ dragFactory: new PrefetchProxy(detailView) as any as Doc,
+ removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "window-maximize"
+ });
+ }
+
if (doc["template-buttons"] === undefined) {
- doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, doc["template-button-query"] as Doc], {
+ doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc,
+ doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc], {
title: "Compound Item Creators", _xMargin: 0, _showTitle: "title",
_autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
@@ -96,31 +137,50 @@ export class CurrentUserUtils {
// setup the different note type skins
static setupNoteTemplates(doc: Doc) {
- if (doc.noteTypes === undefined) {
- const taskStatusValues = [
- { title: "todo", _backgroundColor: "blue", color: "white" },
- { title: "in progress", _backgroundColor: "yellow", color: "black" },
- { title: "completed", _backgroundColor: "green", color: "white" }
- ];
- const noteTemplates = [
- Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" }),
- Docs.Create.TextDocument("", { title: "text", style: "Idea", isTemplateDoc: true, backgroundColor: "pink" }),
- Docs.Create.TextDocument("", { title: "text", style: "Topic", isTemplateDoc: true, backgroundColor: "lightBlue" }),
- Docs.Create.TextDocument("", { title: "text", style: "Person", isTemplateDoc: true, backgroundColor: "lightGreen" }),
- Docs.Create.TextDocument("", {
- title: "text", style: "Todo", isTemplateDoc: true, backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption",
- layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus")
- })
- ];
+ if (doc["template-note-Note"] === undefined) {
+ const noteView = Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" });
+ noteView.isTemplateDoc = makeTemplate(noteView, true, "Note");
+ doc["template-note-Note"] = new PrefetchProxy(noteView);
+ }
+ if (doc["template-note-Idea"] === undefined) {
+ const noteView = Docs.Create.TextDocument("", { title: "text", style: "Idea", backgroundColor: "pink" });
+ noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea");
+ doc["template-note-Idea"] = new PrefetchProxy(noteView);
+ }
+ if (doc["template-note-Topic"] === undefined) {
+ const noteView = Docs.Create.TextDocument("", { title: "text", style: "Topic", backgroundColor: "lightBlue" });
+ noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic");
+ doc["template-note-Topic"] = new PrefetchProxy(noteView);
+ }
+ if (doc["template-note-Todo"] === undefined) {
+ const noteView = Docs.Create.TextDocument("", {
+ title: "text", style: "Todo", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption",
+ layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus")
+ });
+ noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo");
+ doc["template-note-Todo"] = new PrefetchProxy(noteView);
+ }
+ const taskStatusValues = [
+ { title: "todo", _backgroundColor: "blue", color: "white" },
+ { title: "in progress", _backgroundColor: "yellow", color: "black" },
+ { title: "completed", _backgroundColor: "green", color: "white" }
+ ];
+ if (doc.fieldTypes === undefined) {
doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" });
- Doc.addFieldEnumerations(Doc.GetProto(noteTemplates[4]), "taskStatus", taskStatusValues);
- doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(noteTemplates.map(nt => makeTemplate(nt, true, StrCast(nt.style)) ? nt : nt),
+ Doc.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues);
+ }
+
+ if (doc["template-notes"] === undefined) {
+ doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc,
+ doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc],
{ title: "Note Layouts", _height: 75 }));
} else {
- DocListCast(Cast(doc.noteTypes, Doc, null)?.data); // prefetch templates
+ const noteTypes = Cast(doc["template-notes"], Doc, null);
+ DocListCastAsync(noteTypes).then(list => noteTypes.data = new List<Doc>([doc["template-note-Note"] as any as Doc,
+ doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc]));
}
- return doc.noteTypes as Doc;
+ return doc["template-notes"] as Doc;
}
// creates Note templates, and initial "user" templates
@@ -148,7 +208,7 @@ export class CurrentUserUtils {
doc["template-icon-view"] = new PrefetchProxy(iconView);
}
if (doc["template-icon-view-rtf"] === undefined) {
- const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") });
+ const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") });
iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
}
@@ -195,7 +255,7 @@ export class CurrentUserUtils {
{ title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
{ title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
{ title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
- { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
+ { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' },
{ title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc },
{ title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' },
{ title: "Drag a scripting box", label: "Script", icon: "terminal", ignoreClick: true, drag: 'Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250 title: "untitled script" })' },
@@ -350,7 +410,7 @@ export class CurrentUserUtils {
if (doc["tabs-button-tools"] === undefined) {
doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 35, _height: 25, title: "Tools", fontSize: 10,
+ _width: 35, _height: 25, title: "Tools", _fontSize: 10,
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
sourcePanel: new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], {
_width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true
@@ -407,7 +467,7 @@ export class CurrentUserUtils {
if (doc["tabs-button-library"] === undefined) {
doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 50, _height: 25, title: "Library", fontSize: 10,
+ _width: 50, _height: 25, title: "Library", _fontSize: 10,
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildren: true
@@ -423,7 +483,7 @@ export class CurrentUserUtils {
static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) {
if (doc["tabs-button-search"] === undefined) {
doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 50, _height: 25, title: "Search", fontSize: 10,
+ _width: 50, _height: 25, title: "Search", _fontSize: 10,
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
sourcePanel: new PrefetchProxy(Docs.Create.QueryDocument({ title: "search stack", })) as any as Doc,
searchFileTypes: new List<string>([DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.VID, DocumentType.WEB, DocumentType.SCRIPTING]),