aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorvkalev <vjk1883@gmail.com>2021-07-15 22:13:41 -0500
committervkalev <vjk1883@gmail.com>2021-07-15 22:13:41 -0500
commit0f1e83c036c5ece55179c50251a239daae219771 (patch)
tree96c2b17e8de0ba06eb47163a30daff02fab92fc6 /src
parent48620bbe25f92eb179d53846aae5f0164ca6f1c2 (diff)
parent31a00f5e5d374b8a2945525a75f80f4148c143b7 (diff)
Merge branch 'master' of https://github.com/brown-dash/Dash-Web into ink-gfx-victor
Merging master changes into ink branch
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts18
-rw-r--r--src/client/util/DragManager.ts2
-rw-r--r--src/client/util/SelectionManager.ts2
-rw-r--r--src/client/views/DocComponent.tsx7
-rw-r--r--src/client/views/GestureOverlay.tsx2
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/MarqueeAnnotator.tsx27
-rw-r--r--src/client/views/StyleProvider.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx22
-rw-r--r--src/client/views/collections/CollectionSubView.tsx20
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx2
-rw-r--r--src/client/views/collections/TreeView.tsx33
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx (renamed from src/client/views/collections/CollectionSchemaCells.tsx)112
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx (renamed from src/client/views/collections/CollectionSchemaHeaders.tsx)16
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx128
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx (renamed from src/client/views/collections/CollectionSchemaMovableTableHOC.tsx)141
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss (renamed from src/client/views/collections/CollectionSchemaView.scss)97
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx575
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTable.tsx (renamed from src/client/views/collections/SchemaTable.tsx)50
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx5
-rw-r--r--src/client/views/nodes/ImageBox.tsx30
-rw-r--r--src/client/views/nodes/PDFBox.tsx13
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx4
-rw-r--r--src/client/views/nodes/VideoBox.tsx2
-rw-r--r--src/client/views/nodes/WebBox.tsx29
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss11
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx20
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx2
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx3
-rw-r--r--src/client/views/pdf/PDFViewer.tsx10
-rw-r--r--src/client/views/search/SearchBox.tsx10
-rw-r--r--src/fields/SchemaHeaderField.ts2
-rw-r--r--src/pen-gestures/GestureUtils.ts4
-rw-r--r--src/server/DashUploadUtils.ts2
36 files changed, 1007 insertions, 404 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 24682cbd0..040571b10 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1329,16 +1329,14 @@ export namespace DocUtils {
d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
});
});
- if (x !== undefined && y !== undefined) {
- const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _overflow: "visible" });
- 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";
- return newCollection;
- }
+ const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, _overflow: "visible" });
+ 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";
+ return newCollection;
}
export function LeavePushpin(doc: Doc, annotationField: string) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index d8c2f913e..88bf6f36d 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -340,7 +340,7 @@ export namespace DragManager {
dragLabel.style.zIndex = "100001";
dragLabel.style.fontSize = "10px";
dragLabel.style.position = "absolute";
- dragLabel.innerText = "press 'a' to embed on drop"; // bcz: need to move this to a status bar
+ dragLabel.innerText = "drag titlebar to embed on drop"; // bcz: need to move this to a status bar
dragDiv.appendChild(dragLabel);
DragManager.Root().appendChild(dragDiv);
}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index ca5ef75d2..00f0894c7 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,7 +1,7 @@
import { action, observable, ObservableMap } from "mobx";
import { computedFn } from "mobx-utils";
import { Doc, Opt } from "../../fields/Doc";
-import { CollectionSchemaView } from "../views/collections/CollectionSchemaView";
+import { CollectionSchemaView } from "../views/collections/collectionSchema/CollectionSchemaView";
import { CollectionViewType } from "../views/collections/CollectionView";
import { DocumentView } from "../views/nodes/DocumentView";
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index a878a7afb..da8af7cc0 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -119,7 +119,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
styleFromLayoutString = (scale: number) => {
const style: { [key: string]: any } = {};
- const divKeys = ["width", "height", "fontSize", "left", "background", "top", "pointerEvents", "position"];
+ const divKeys = ["width", "height", "fontSize", "left", "background", "left", "right", "top", "bottom", "pointerEvents", "position"];
const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a property expression string: { script } into a value
return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result as string || "";
};
@@ -154,7 +154,10 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
doc.context = undefined;
- recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
+ if (recent) {
+ Doc.RemoveDocFromList(recent, "data", doc);
+ Doc.AddDocToList(recent, "data", doc, undefined, true, true);
+ }
});
this.props.select(false);
return true;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 491bf18b2..6a4f55bef 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -634,7 +634,7 @@ export class GestureOverlay extends Touchable {
} else {
this._points = [];
}
- CollectionFreeFormViewChrome.Instance.primCreated();
+ CollectionFreeFormViewChrome.Instance?.primCreated();
}
makePolygon = (shape: string, gesture: boolean) => {
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c1130ce61..76eb4c142 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -89,8 +89,6 @@ export class KeyManager {
private unmodified = action((keyname: string, e: KeyboardEvent) => {
switch (keyname) {
- case "a": SnappingManager.GetIsDragging() && (DragManager.CanEmbed = true);
- break;
case "u":
if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") {
return { stopPropagation: false, preventDefault: false };
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index d2074d653..717bd0768 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -65,10 +65,9 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
doc.addEventListener("pointermove", this.onSelectMove);
doc.addEventListener("pointerup", this.onSelectEnd);
- AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
- this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true));
- };
+ AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true));
AnchorMenu.Instance.Highlight = this.highlight;
+ AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight("rgba(173, 216, 230, 0.75)", true, savedAnnotations);
/**
* This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
* It also initiates a Drag/Drop interaction to place the text annotation.
@@ -103,11 +102,13 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
@undoBatch
@action
- makeAnnotationDocument = (color: string, isLinkButton?: boolean): Opt<Doc> => {
- if (this.props.savedAnnotations.size === 0) return undefined;
- if ((Array.from(this.props.savedAnnotations.values())[0][0] as any).marqueeing) {
+ makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
+ const savedAnnoMap = savedAnnotations ?? this.props.savedAnnotations;
+ if (savedAnnoMap.size === 0) return undefined;
+ const savedAnnos = Array.from(savedAnnoMap.values())[0];
+ if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) {
const scale = this.props.scaling?.() || 1;
- const anno = Array.from(this.props.savedAnnotations.values())[0][0];
+ const anno = savedAnnos[0];
const containerOffset = this.props.containerOffset?.() || [0, 0];
const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title });
marqueeAnno.x = (parseInt(anno.style.left || "0") - containerOffset[0]) / scale;
@@ -115,7 +116,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
marqueeAnno._height = parseInt(anno.style.height || "0") / scale;
marqueeAnno._width = parseInt(anno.style.width || "0") / scale;
anno.remove();
- this.props.savedAnnotations.clear();
+ savedAnnoMap.clear();
return marqueeAnno;
}
@@ -123,7 +124,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
let maxX = -Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
const annoDocs: Doc[] = [];
- this.props.savedAnnotations.forEach((value: HTMLDivElement[], key: number) => value.map(anno => {
+ savedAnnoMap.forEach((value: HTMLDivElement[], key: number) => value.map(anno => {
const textRegion = new Doc();
textRegion.x = parseInt(anno.style.left ?? "0");
textRegion.y = parseInt(anno.style.top ?? "0");
@@ -142,15 +143,15 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
textRegionAnnoProto.x = Math.max(maxX, 0);
// mainAnnoDocProto.text = this._selectionText;
textRegionAnnoProto.textInlineAnnotations = new List<Doc>(annoDocs);
- this.props.savedAnnotations.clear();
+ savedAnnoMap.clear();
return textRegionAnno;
}
@action
- highlight = (color: string, isLinkButton: boolean) => {
+ highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => {
// creates annotation documents for current highlights
const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]);
- const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton);
- annotationDoc && this.props.addDocument(annotationDoc);
+ const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
+ !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
return annotationDoc as Doc ?? undefined;
}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 9e61351c4..47a4a192c 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -127,7 +127,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break;
case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
case DocumentType.LINKANCHOR: docColor = isAnchor ? "lightblue" : "transparent"; break;
- case DocumentType.LINK: docColor = docColor || "transparent"; break;
+ case DocumentType.LINK: docColor = (isAnchor ? docColor : "") || "transparent"; break;
case DocumentType.IMG:
case DocumentType.WEB:
case DocumentType.PDF:
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b33c437a9..8f2847139 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -147,43 +147,43 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Any)}>
<FontAwesomeIcon icon={"align-justify"} size="sm" />
- Any
- </div>;
+ Any
+ </div>;
const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Number)}>
<FontAwesomeIcon icon={"hashtag"} size="sm" />
- Number
- </div>;
+ Number
+ </div>;
const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.String)}>
<FontAwesomeIcon icon={"font"} size="sm" />
Text
- </div>;
+ </div>;
const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
<FontAwesomeIcon icon={"check-square"} size="sm" />
Checkbox
- </div>;
+ </div>;
const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.List)}>
<FontAwesomeIcon icon={"list-ul"} size="sm" />
List
- </div>;
+ </div>;
const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
<FontAwesomeIcon icon={"file"} size="sm" />
Document
- </div>;
+ </div>;
const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Image)}>
<FontAwesomeIcon icon={"image"} size="sm" />
Image
- </div>;
+ </div>;
const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Date)}>
<FontAwesomeIcon icon={"calendar"} size="sm" />
- Date
- </div>;
+ Date
+ </div>;
const allColumnTypes = <div className="columnMenu-types">
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8d549bd56..ca45536f4 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -241,7 +241,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
@undoBatch
@action
- protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
+ protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: (docs: Doc[]) => void) {
if (e.ctrlKey) {
e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
return;
@@ -439,7 +439,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
}
- slowLoadDocuments = async (files: (File[] | string), options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: (() => void) | undefined, clientX: number, clientY: number, addDocument: (doc: Doc | Doc[]) => boolean) => {
+ slowLoadDocuments = async (files: (File[] | string), options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, clientX: number, clientY: number, addDocument: (doc: Doc | Doc[]) => boolean) => {
const disposer = OverlayView.Instance.addElement(
<ReactLoading type={"spinningBubbles"} color={"green"} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 });
if (typeof files === "string") {
@@ -448,13 +448,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options));
}
if (generatedDocuments.length) {
- const set = generatedDocuments.length > 1 && generatedDocuments.map(d => DocUtils.iconify(d));
- if (set) {
- addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
- } else {
- generatedDocuments.forEach(addDocument);
+ const isFreeformView = this.props.Document._viewType === CollectionViewType.Freeform;
+ const set = !isFreeformView ? generatedDocuments :
+ generatedDocuments.length > 1 ? generatedDocuments.map(d => { DocUtils.iconify(d); return d; }) : [];
+ if (completed) completed(set);
+ else {
+ if (isFreeformView) {
+ addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+ } else {
+ generatedDocuments.forEach(addDocument);
+ }
}
- completed?.();
} else {
if (text && !text.includes("https://")) {
addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 }));
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 82c8a9114..3eece0086 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -154,7 +154,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
}
}
- onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
+ onTreeDrop = (e: React.DragEvent, addDocs?: (docs: Doc[]) => void) => this.onExternalDrop(e, {}, addDocs);
@undoBatch
makeTextCollection = (childDocs: Doc[]) => {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index fb60265e3..e225c4a11 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -29,7 +29,7 @@ import CollectionMapView from './CollectionMapView';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
import { CollectionPileView } from './CollectionPileView';
-import { CollectionSchemaView } from "./CollectionSchemaView";
+import { CollectionSchemaView } from "./collectionSchema/CollectionSchemaView";
import { CollectionStackingView } from './CollectionStackingView';
import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 2e98fb508..c1125c233 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -261,15 +261,19 @@ export class TreeView extends React.Component<TreeViewProps> {
if (docDragData) {
e.stopPropagation();
if (docDragData.draggedDocuments[0] === this.doc) return true;
- const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
- const canAdd = !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add") || docDragData.treeViewDoc === this.props.treeView.props.Document;
- const localAdd = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false;
- const addDoc = !inside ? parentAddDoc :
- (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean);
- const move = (!docDragData.dropAction || docDragData.dropAction === "proto" || docDragData.dropAction === "move" || docDragData.dropAction === "same") && docDragData.moveDocument;
- if (canAdd) {
- UndoManager.RunInTempBatch(() => docDragData.droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (docDragData.dropAction === "proto" ? addDoc(d) : false) : addDoc(d)) || added, false));
- }
+ this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document);
+ }
+ }
+
+ dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
+ const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
+ const canAdd = !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add") || forceAdd;
+ const localAdd = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false;
+ const addDoc = !inside ? parentAddDoc :
+ (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean);
+ const move = (!dropAction || dropAction === "proto" || dropAction === "move" || dropAction === "same") && moveDocument;
+ if (canAdd) {
+ UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false) : addDoc(d)) || added, false));
}
}
@@ -727,12 +731,23 @@ export class TreeView extends React.Component<TreeViewProps> {
</div>;
}
+ onTreeDrop = (de: React.DragEvent) => {
+ const pt = [de.clientX, de.clientY];
+ const rect = this._header.current!.getBoundingClientRect();
+ const before = pt[1] < rect.top + rect.height / 2;
+ const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length);
+
+ const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, "copy", undefined, false));
+ }
+
render() {
TraceMobx();
const hideTitle = this.doc.treeViewHideHeader || this.props.treeView.outlineMode;
return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
<div className={`treeView-container${this.props.isContentActive() ? "-active" : ""}`}
ref={this.createTreeDropTarget}
+
+ onDrop={this.onTreeDrop}
//onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
onKeyDown={this.onKeyDown}>
<li className="collection-child">
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index 2e6186680..f75179cea 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -6,33 +6,34 @@ import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { CellInfo } from "react-table";
import "react-table/react-table.css";
-import { DateField } from "../../../fields/DateField";
-import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { ComputedField } from "../../../fields/ScriptField";
-import { BoolCast, Cast, DateCast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
-import { ImageField } from "../../../fields/URLField";
-import { Utils, emptyFunction } from "../../../Utils";
-import { Docs } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager } from "../../util/DragManager";
-import { KeyCodes } from "../../util/KeyCodes";
-import { CompileScript } from "../../util/Scripting";
-import { SearchUtil } from "../../util/SearchUtil";
-import { SnappingManager } from "../../util/SnappingManager";
-import { undoBatch } from "../../util/UndoManager";
-import '../DocumentDecorations.scss';
-import { EditableView } from "../EditableView";
-import { MAX_ROW_HEIGHT } from '../globalCssVariables.scss';
-import { DocumentIconContainer } from "../nodes/DocumentIcon";
-import { OverlayView } from "../OverlayView";
+import { DateField } from "../../../../fields/DateField";
+import { Doc, DocListCast, Field, Opt } from "../../../../fields/Doc";
+import { Id } from "../../../../fields/FieldSymbols";
+import { List } from "../../../../fields/List";
+import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { ComputedField } from "../../../../fields/ScriptField";
+import { BoolCast, Cast, DateCast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
+import { ImageField } from "../../../../fields/URLField";
+import { Utils, emptyFunction } from "../../../../Utils";
+import { Docs } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DragManager } from "../../../util/DragManager";
+import { KeyCodes } from "../../../util/KeyCodes";
+import { CompileScript } from "../../../util/Scripting";
+import { SearchUtil } from "../../../util/SearchUtil";
+import { SnappingManager } from "../../../util/SnappingManager";
+import { undoBatch } from "../../../util/UndoManager";
+import '../../../views/DocumentDecorations.scss';
+import { EditableView } from "../../EditableView";
+import { MAX_ROW_HEIGHT } from '../../globalCssVariables.scss';
+import { DocumentIconContainer } from "../../nodes/DocumentIcon";
+import { OverlayView } from "../../OverlayView";
import "./CollectionSchemaView.scss";
-import { CollectionView } from "./CollectionView";
+import { CollectionView } from "../CollectionView";
const path = require('path');
+// intialize cell properties
export interface CellProps {
row: number;
col: number;
@@ -236,14 +237,69 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
}}
SetValue={action((value: string) => {
+ // sets what is displayed after the user makes an input
let retVal = false;
if (value.startsWith(":=") || value.startsWith("=:=")) {
+ // decides how to compute a value when given either of the above strings
const script = value.substring(value.startsWith("=:=") ? 3 : 2);
retVal = this.props.setComputed(script, value.startsWith(":=") ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col);
} else {
- const inputscript = value.substring(value.startsWith("=") ? 1 : 0);
- const script = CompileScript(inputscript, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- script.compiled && (retVal = this.applyToDoc(inputscript.length !== value.length ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
+ // check if the input is a number
+ let inputIsNum = true;
+ for (let s of value) {
+ if (isNaN(parseInt(s)) && !(s == ".") && !(s == ",")) {
+ inputIsNum = false;
+ }
+ }
+ // check if the input is a boolean
+ let inputIsBool: boolean = value == "false" || value == "true";
+ // what to do in the case
+ if (!inputIsNum && !inputIsBool && !value.startsWith("=")) {
+ // if it's not a number, it's a string, and should be processed as such
+ // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically
+ // after each edit
+ let valueSansQuotes = value;
+ if (this._isEditing) {
+ const vsqLength = valueSansQuotes.length;
+ // get rid of outer quotes
+ valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0,
+ valueSansQuotes.charAt(vsqLength - 1) == "\"" ? vsqLength - 1 : vsqLength);
+ }
+ let inputAsString = '"';
+ // escape any quotes in the string
+ for (const i of valueSansQuotes) {
+ if (i == '"') {
+ inputAsString += '\\"';
+ } else {
+ inputAsString += i;
+ }
+ }
+ // add a closing quote
+ inputAsString += '"';
+ //two options here: we can strip off outer quotes or we can figure out what's going on with the script
+ const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length
+ script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
+ // handle numbers and expressions
+ } else if (inputIsNum || value.startsWith("=")) {
+ //TODO: make accept numbers
+ const inputscript = value.substring(value.startsWith("=") ? 1 : 0);
+ // if commas are not stripped, the parser only considers the numbers after the last comma
+ let inputSansCommas = "";
+ for (let s of inputscript) {
+ if (!(s == ",")) {
+ inputSansCommas += s;
+ }
+ }
+ const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ const changeMade = value.length !== value.length || value.length - 2 !== value.length
+ script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
+ // handle booleans
+ } else if (inputIsBool) {
+ const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ const changeMade = value.length !== value.length || value.length - 2 !== value.length
+ script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
+ }
}
if (retVal) {
this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing'
@@ -318,7 +374,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
const script = CompileScript(value, {
addReturn: true,
- typecheck: false,
+ typecheck: true,
transformer: DocumentIconContainer.getTransformer()
});
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
index 3b52e6408..b2115b22e 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
@@ -3,16 +3,16 @@ import { IconProp, library } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { listSpec } from "../../../fields/Schema";
-import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { ScriptField } from "../../../fields/ScriptField";
-import { Cast, StrCast } from "../../../fields/Types";
-import { undoBatch } from "../../util/UndoManager";
-import { SearchBox } from "../search/SearchBox";
+import { Doc, DocListCast, Opt } from "../../../../fields/Doc";
+import { listSpec } from "../../../../fields/Schema";
+import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { ScriptField } from "../../../../fields/ScriptField";
+import { Cast, StrCast } from "../../../../fields/Types";
+import { undoBatch } from "../../../util/UndoManager";
+import { SearchBox } from "../../search/SearchBox";
import { ColumnType } from "./CollectionSchemaView";
import "./CollectionSchemaView.scss";
-import { CollectionView } from "./CollectionView";
+import { CollectionView } from "../CollectionView";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
new file mode 100644
index 000000000..456c38c68
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
@@ -0,0 +1,128 @@
+import React = require("react");
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action } from "mobx";
+import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table";
+import { Doc } from "../../../../fields/Doc";
+import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { Cast, FieldValue, StrCast } from "../../../../fields/Types";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DragManager, dropActionType, SetupDrag } from "../../../util/DragManager";
+import { SnappingManager } from "../../../util/SnappingManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { ContextMenu } from "../../ContextMenu";
+import "./CollectionSchemaView.scss";
+
+export interface MovableColumnProps {
+ columnRenderer: TableCellRenderer;
+ columnValue: SchemaHeaderField;
+ allColumns: SchemaHeaderField[];
+ reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void;
+ ScreenToLocalTransform: () => Transform;
+}
+export class MovableColumn extends React.Component<MovableColumnProps> {
+ private _header?: React.RefObject<HTMLDivElement> = React.createRef();
+ private _colDropDisposer?: DragManager.DragDropDisposer;
+ private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
+ private _sensitivity: number = 16;
+ private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+
+ onPointerEnter = (e: React.PointerEvent): void => {
+ if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
+ this._header!.current!.className = "collectionSchema-col-wrapper";
+ document.addEventListener("pointermove", this.onDragMove, true);
+ }
+ }
+ onPointerLeave = (e: React.PointerEvent): void => {
+ this._header!.current!.className = "collectionSchema-col-wrapper";
+ document.removeEventListener("pointermove", this.onDragMove, true);
+ !e.buttons && document.removeEventListener("pointermove", this.onPointerMove);
+ }
+ onDragMove = (e: PointerEvent): void => {
+ const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+ const rect = this._header!.current!.getBoundingClientRect();
+ const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
+ const before = x[0] < bounds[0];
+ this._header!.current!.className = "collectionSchema-col-wrapper";
+ if (before) this._header!.current!.className += " col-before";
+ if (!before) this._header!.current!.className += " col-after";
+ e.stopPropagation();
+ }
+
+ createColDropTarget = (ele: HTMLDivElement) => {
+ this._colDropDisposer?.();
+ if (ele) {
+ this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
+ }
+ }
+
+ colDrop = (e: Event, de: DragManager.DropEvent) => {
+ document.removeEventListener("pointermove", this.onDragMove, true);
+ const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const rect = this._header!.current!.getBoundingClientRect();
+ const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
+ const before = x[0] < bounds[0];
+ const colDragData = de.complete.columnDragData;
+ if (colDragData) {
+ e.stopPropagation();
+ this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns);
+ return true;
+ }
+ return false;
+ }
+
+ onPointerMove = (e: PointerEvent) => {
+ const onRowMove = (e: PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+
+ document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointerup', onRowUp);
+ const dragData = new DragManager.ColumnDragData(this.props.columnValue);
+ DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y);
+ };
+ const onRowUp = (): void => {
+ document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointerup', onRowUp);
+ };
+ if (e.buttons === 1) {
+ const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
+ if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ e.stopPropagation();
+
+ document.addEventListener("pointermove", onRowMove);
+ document.addEventListener("pointerup", onRowUp);
+ }
+ }
+ }
+
+ onPointerUp = (e: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+
+ @action
+ onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => {
+ this._dragRef = ref;
+ const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY);
+ if (!(e.target as any)?.tagName.includes("INPUT")) {
+ this._startDragPosition = { x: dx, y: dy };
+ document.addEventListener("pointermove", this.onPointerMove);
+ }
+ }
+
+
+ render() {
+ const reference = React.createRef<HTMLDivElement>();
+
+ return (
+ <div className="collectionSchema-col" ref={this.createColDropTarget}>
+ <div className="collectionSchema-col-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+ <div className="col-dragger" ref={reference} onPointerDown={e => this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}>
+ {this.props.columnRenderer}
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
index 881246bd4..f48906ba5 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
@@ -2,131 +2,17 @@ import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action } from "mobx";
import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table";
-import { Doc } from "../../../fields/Doc";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { Cast, FieldValue, StrCast } from "../../../fields/Types";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { ContextMenu } from "../ContextMenu";
+import { Doc } from "../../../../fields/Doc";
+import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { Cast, FieldValue, StrCast } from "../../../../fields/Types";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DragManager, dropActionType, SetupDrag } from "../../../util/DragManager";
+import { SnappingManager } from "../../../util/SnappingManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { ContextMenu } from "../../ContextMenu";
import "./CollectionSchemaView.scss";
-export interface MovableColumnProps {
- columnRenderer: TableCellRenderer;
- columnValue: SchemaHeaderField;
- allColumns: SchemaHeaderField[];
- reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columns: SchemaHeaderField[]) => void;
- ScreenToLocalTransform: () => Transform;
-}
-export class MovableColumn extends React.Component<MovableColumnProps> {
- private _header?: React.RefObject<HTMLDivElement> = React.createRef();
- private _colDropDisposer?: DragManager.DragDropDisposer;
- private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
- private _sensitivity: number = 16;
- private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
-
- onPointerEnter = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
- this._header!.current!.className = "collectionSchema-col-wrapper";
- document.addEventListener("pointermove", this.onDragMove, true);
- }
- }
- onPointerLeave = (e: React.PointerEvent): void => {
- this._header!.current!.className = "collectionSchema-col-wrapper";
- document.removeEventListener("pointermove", this.onDragMove, true);
- !e.buttons && document.removeEventListener("pointermove", this.onPointerMove);
- }
- onDragMove = (e: PointerEvent): void => {
- const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
- const rect = this._header!.current!.getBoundingClientRect();
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
- const before = x[0] < bounds[0];
- this._header!.current!.className = "collectionSchema-col-wrapper";
- if (before) this._header!.current!.className += " col-before";
- if (!before) this._header!.current!.className += " col-after";
- e.stopPropagation();
- }
-
- createColDropTarget = (ele: HTMLDivElement) => {
- this._colDropDisposer?.();
- if (ele) {
- this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
- }
- }
-
- colDrop = (e: Event, de: DragManager.DropEvent) => {
- document.removeEventListener("pointermove", this.onDragMove, true);
- const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
- const rect = this._header!.current!.getBoundingClientRect();
- const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
- const before = x[0] < bounds[0];
- const colDragData = de.complete.columnDragData;
- if (colDragData) {
- e.stopPropagation();
- this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns);
- return true;
- }
- return false;
- }
-
- onPointerMove = (e: PointerEvent) => {
- const onRowMove = (e: PointerEvent) => {
- e.stopPropagation();
- e.preventDefault();
-
- document.removeEventListener("pointermove", onRowMove);
- document.removeEventListener('pointerup', onRowUp);
- const dragData = new DragManager.ColumnDragData(this.props.columnValue);
- DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y);
- };
- const onRowUp = (): void => {
- document.removeEventListener("pointermove", onRowMove);
- document.removeEventListener('pointerup', onRowUp);
- };
- if (e.buttons === 1) {
- const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
- if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
- document.removeEventListener("pointermove", this.onPointerMove);
- e.stopPropagation();
-
- document.addEventListener("pointermove", onRowMove);
- document.addEventListener("pointerup", onRowUp);
- }
- }
- }
-
- onPointerUp = (e: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.onPointerMove);
- }
-
- @action
- onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => {
- this._dragRef = ref;
- const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY);
- if (!(e.target as any)?.tagName.includes("INPUT")) {
- this._startDragPosition = { x: dx, y: dy };
- document.addEventListener("pointermove", this.onPointerMove);
- }
- }
-
-
- render() {
- const reference = React.createRef<HTMLDivElement>();
-
- return (
- <div className="collectionSchema-col" ref={this.createColDropTarget}>
- <div className="collectionSchema-col-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- <div className="col-dragger" ref={reference} onPointerDown={e => this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}>
- {this.props.columnRenderer}
- </div>
- </div>
- </div>
- );
- }
-}
-
export interface MovableRowProps {
rowInfo: RowInfo;
ScreenToLocalTransform: () => Transform;
@@ -143,16 +29,20 @@ export class MovableRow extends React.Component<MovableRowProps> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _rowDropDisposer?: DragManager.DragDropDisposer;
+ // Event listeners are only necessary when the user is hovering over the table
+ // Create one when the mouse starts hovering...
onPointerEnter = (e: React.PointerEvent): void => {
if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
this._header!.current!.className = "collectionSchema-row-wrapper";
document.addEventListener("pointermove", this.onDragMove, true);
}
}
+ // ... and delete it when the mouse leaves
onPointerLeave = (e: React.PointerEvent): void => {
this._header!.current!.className = "collectionSchema-row-wrapper";
document.removeEventListener("pointermove", this.onDragMove, true);
}
+ // The method for the event listener, reorders columns when dragged to their new locations.
onDragMove = (e: PointerEvent): void => {
const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
const rect = this._header!.current!.getBoundingClientRect();
@@ -167,14 +57,14 @@ export class MovableRow extends React.Component<MovableRowProps> {
this._rowDropDisposer?.();
}
-
+ //
createRowDropTarget = (ele: HTMLDivElement) => {
this._rowDropDisposer?.();
if (ele) {
this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
}
-
+ // Controls what hppens when a row is dragged and dropped
rowDrop = (e: Event, de: DragManager.DropEvent) => {
this.onPointerLeave(e as any);
const rowDoc = FieldValue(Cast(this.props.rowInfo.original, Doc));
@@ -201,7 +91,6 @@ export class MovableRow extends React.Component<MovableRowProps> {
}
onRowContextMenu = (e: React.MouseEvent): void => {
- e.preventDefault();
const description = this.props.rowWrapped ? "Unwrap text on row" : "Text wrap row";
ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: "file-pdf" });
}
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 2bdd280ec..b57fee0e4 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,5 +1,4 @@
-@import "../globalCssVariables";
-
+@import "../../globalCssVariables";
.collectionSchemaView-container {
border-width: $COLLECTION_BORDER_WIDTH;
border-color: $intermediate-color;
@@ -16,17 +15,13 @@
justify-content: space-between;
flex-wrap: nowrap;
touch-action: none;
-
div {
touch-action: none;
}
-
-
.collectionSchemaView-tableContainer {
width: 100%;
height: 100%;
}
-
.collectionSchemaView-dividerDragger {
position: relative;
height: 100%;
@@ -37,7 +32,6 @@
background: gray;
cursor: col-resize;
}
-
// .documentView-node:first-child {
// background: $light-color;
// }
@@ -60,17 +54,13 @@
flex-wrap: nowrap;
touch-action: none;
padding: 2px;
-
div {
touch-action: none;
}
-
-
.collectionSchemaView-tableContainer {
width: 100%;
height: 100%;
}
-
.collectionSchemaView-dividerDragger {
position: relative;
height: 100%;
@@ -81,7 +71,6 @@
background: gray;
cursor: col-resize;
}
-
// .documentView-node:first-child {
// background: $light-color;
// }
@@ -93,7 +82,6 @@
box-sizing: border-box;
border: none !important;
float: none !important;
-
.rt-table {
height: 100%;
display: -webkit-inline-box;
@@ -103,12 +91,10 @@
.rt-noData {
display: none;
}
-
.rt-thead {
width: 100%;
z-index: 100;
overflow-y: visible;
-
&.-header {
font-size: 12px;
height: 30px;
@@ -116,12 +102,10 @@
z-index: 100;
overflow-y: visible;
}
-
.rt-resizable-header-content {
height: 100%;
overflow: visible;
}
-
.rt-th {
padding: 0;
border: solid lightgray;
@@ -129,38 +113,31 @@
border-bottom: 2px solid lightgray;
}
}
-
.rt-th {
font-size: 13px;
text-align: center;
-
&:last-child {
overflow: visible;
}
}
-
.rt-tbody {
width: 100%;
direction: rtl;
overflow: visible;
-
.rt-td {
border-right: 1px solid rgba(0, 0, 0, 0.2);
}
}
-
.rt-tr-group {
direction: ltr;
flex: 0 1 auto;
min-height: 30px;
border: 0 !important;
}
-
.rt-tr {
width: 100%;
min-height: 30px;
}
-
.rt-td {
padding: 0;
font-size: 13px;
@@ -168,18 +145,15 @@
white-space: nowrap;
display: flex;
align-items: center;
-
.imageBox-cont {
position: relative;
max-height: 100%;
}
-
.imageBox-cont img {
object-fit: contain;
max-width: 100%;
height: 100%;
}
-
.videoBox-cont {
object-fit: contain;
width: auto;
@@ -191,20 +165,16 @@
align-items: center;
height: inherit;
}
-
.rt-resizer {
width: 8px;
right: -4px;
}
-
.rt-resizable-header {
padding: 0;
height: 30px;
}
-
.rt-resizable-header:last-child {
overflow: visible;
-
.rt-resizer {
width: 5px !important;
}
@@ -221,7 +191,6 @@
height: 100%;
}
-
.collectionSchema-header-menu {
height: auto;
z-index: 100;
@@ -231,7 +200,6 @@
position: fixed;
background: white;
border: black 1px solid;
-
.collectionSchema-header-toggler {
z-index: 100;
width: 100%;
@@ -239,7 +207,6 @@
padding: 4px;
letter-spacing: 2px;
text-transform: uppercase;
-
svg {
margin-right: 4px;
}
@@ -264,62 +231,51 @@ button.add-column {
color: black;
width: 180px;
text-align: left;
-
.collectionSchema-headerMenu-group {
padding: 7px 0;
border-bottom: 1px solid lightgray;
cursor: pointer;
-
&:first-child {
padding-top: 0;
}
-
&:last-child {
border: none;
text-align: center;
padding: 12px 0 0 0;
}
}
-
label {
color: $main-accent;
font-weight: normal;
letter-spacing: 2px;
text-transform: uppercase;
}
-
input {
color: black;
width: 100%;
}
-
.columnMenu-option {
cursor: pointer;
padding: 3px;
background-color: white;
transition: background-color 0.2s;
-
&:hover {
background-color: $light-color-secondary;
}
-
&.active {
font-weight: bold;
border: 2px solid $light-color-secondary;
}
-
svg {
color: gray;
margin-right: 5px;
width: 10px;
}
}
-
.keys-dropdown {
position: relative;
//width: 100%;
background-color: white;
-
input {
border: 2px solid $light-color-secondary;
padding: 3px;
@@ -327,12 +283,10 @@ button.add-column {
font-weight: bold;
letter-spacing: "2px";
text-transform: "uppercase";
-
&:focus {
font-weight: normal;
}
}
-
.keys-options-wrapper {
width: 100%;
max-height: 150px;
@@ -341,34 +295,28 @@ button.add-column {
top: 28px;
box-shadow: 0 10px 16px rgba(0, 0, 0, 0.1);
background-color: white;
-
.key-option {
background-color: white;
border: 1px solid lightgray;
padding: 2px 3px;
-
&:not(:first-child) {
border-top: 0;
}
-
&:hover {
background-color: $light-color-secondary;
}
}
}
}
-
.columnMenu-colors {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
-
.columnMenu-colorPicker {
cursor: pointer;
width: 20px;
height: 20px;
border-radius: 10px;
-
&.active {
border: 2px solid white;
box-shadow: 0 0 0 2px lightgray;
@@ -380,17 +328,14 @@ button.add-column {
.collectionSchema-row {
height: 100%;
background-color: white;
-
&.row-focused .rt-td {
background-color: #bfffc0; //$light-color-secondary;
}
-
&.row-wrapped {
.rt-td {
white-space: normal;
}
}
-
.row-dragger {
display: flex;
justify-content: space-around;
@@ -403,7 +348,6 @@ button.add-column {
color: lightgray;
background-color: white;
transition: color 0.1s ease;
-
.row-option {
// padding: 5px;
cursor: pointer;
@@ -413,27 +357,21 @@ button.add-column {
flex-direction: column;
justify-content: center;
z-index: 2;
-
&:hover {
color: gray;
}
}
}
-
.collectionSchema-row-wrapper {
-
&.row-above {
border-top: 1px solid red;
}
-
&.row-below {
border-bottom: 1px solid red;
}
-
&.row-inside {
border: 1px solid red;
}
-
.row-dragging {
background-color: blue;
}
@@ -450,16 +388,12 @@ button.add-column {
padding: 4px;
text-align: left;
padding-left: 19px;
-
position: relative;
-
&:focus {
outline: none;
}
-
&.editing {
padding: 0;
-
input {
outline: 0;
border: none;
@@ -470,50 +404,36 @@ button.add-column {
min-height: 26px;
}
}
-
&.focused {
-
&.inactive {
border: none;
}
}
-
p {
width: 100%;
height: 100%;
}
-
&:hover .collectionSchemaView-cellContents-docExpander {
display: block;
}
-
-
.collectionSchemaView-cellContents-document {
display: inline-block;
}
-
.collectionSchemaView-cellContents-docButton {
float: right;
width: "15px";
height: "15px";
}
-
.collectionSchemaView-dropdownWrapper {
-
border: grey;
border-style: solid;
border-width: 1px;
height: 30px;
-
.collectionSchemaView-dropdownButton {
-
//display: inline-block;
float: left;
height: 100%;
-
-
}
-
.collectionSchemaView-dropdownText {
display: inline-block;
//float: right;
@@ -523,14 +443,11 @@ button.add-column {
justify-content: "center";
align-items: "center";
}
-
}
-
.collectionSchemaView-dropdownContainer {
position: absolute;
border: 1px solid rgba(0, 0, 0, 0.04);
box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14);
-
.collectionSchemaView-dropdownOption:hover {
background-color: rgba(0, 0, 0, 0.14);
cursor: pointer;
@@ -546,7 +463,6 @@ button.add-column {
top: 0;
right: 0;
background-color: lightgray;
-
}
.doc-drag-over {
@@ -563,7 +479,6 @@ button.add-column {
justify-content: flex-end;
padding: 0 10px;
border-bottom: 2px solid gray;
-
.collectionSchemaView-toolbar-item {
display: flex;
flex-direction: column;
@@ -586,27 +501,24 @@ button.add-column {
.rt-td.rt-expandable {
overflow: visible;
position: relative;
- height:100%;
+ height: 100%;
z-index: 1;
}
+
.reactTable-sub {
background-color: rgb(252, 252, 252);
width: 100%;
-
.rt-thead {
display: none;
}
-
.row-dragger {
background-color: rgb(252, 252, 252);
}
-
.rt-table {
background-color: rgb(252, 252, 252);
}
-
.collectionSchemaView-table {
- width: 100%;
+ width: 100%;
border: solid 1px;
overflow: visible;
padding: 0px;
@@ -621,7 +533,6 @@ button.add-column {
width: 20;
height: auto;
left: 55;
-
svg {
position: absolute;
top: 50%;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
new file mode 100644
index 000000000..ef28f75c8
--- /dev/null
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -0,0 +1,575 @@
+import React = require("react");
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, untracked } from "mobx";
+import { observer } from "mobx-react";
+import Measure from "react-measure";
+import { Resize } from "react-table";
+import "react-table/react-table.css";
+import { Doc, Opt } from "../../../../fields/Doc";
+import { List } from "../../../../fields/List";
+import { listSpec } from "../../../../fields/Schema";
+import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { Cast, NumCast } from "../../../../fields/Types";
+import { TraceMobx } from "../../../../fields/util";
+import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist, returnTrue } from "../../../../Utils";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { SnappingManager } from "../../../util/SnappingManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../globalCssVariables.scss';
+import { ContextMenu } from "../../ContextMenu";
+import { ContextMenuProps } from "../../ContextMenuItem";
+import '../../../views/DocumentDecorations.scss';
+import { DocumentView } from "../../nodes/DocumentView";
+import { DefaultStyleProvider } from "../../StyleProvider";
+import "./CollectionSchemaView.scss";
+import { CollectionSubView } from "../CollectionSubView";
+import { SchemaTable } from "./SchemaTable";
+import { DocUtils } from "../../../documents/Documents";
+// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
+
+export enum ColumnType {
+ Any,
+ Number,
+ String,
+ Boolean,
+ Doc,
+ Image,
+ List,
+ Date
+}
+// this map should be used for keys that should have a const type of value
+const columnTypes: Map<string, ColumnType> = new Map([
+ ["title", ColumnType.String],
+ ["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number],
+ ["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
+ ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
+]);
+
+@observer
+export class CollectionSchemaView extends CollectionSubView(doc => doc) {
+ private _previewCont?: HTMLDivElement;
+
+ @observable _previewDoc: Doc | undefined = undefined;
+ @observable _focusedTable: Doc = this.props.Document;
+ @observable _col: any = "";
+ @observable _menuWidth = 0;
+ @observable _headerOpen = false;
+ @observable _headerIsEditing = false;
+ @observable _menuHeight = 0;
+ @observable _pointerX = 0;
+ @observable _pointerY = 0;
+ @observable _openTypes: boolean = false;
+
+ @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
+ @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
+ @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); }
+ @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
+ @computed get scale() { return this.props.ScreenToLocalTransform().Scale; }
+ @computed get columns() { return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []); }
+ set columns(columns: SchemaHeaderField[]) { this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns); }
+
+ @computed get menuCoordinates() {
+ let searchx = 0;
+ let searchy = 0;
+ if (this.props.Document._searchDoc) {
+ const el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0];
+ if (el !== undefined) {
+ const rect = el.getBoundingClientRect();
+ searchx = rect.x;
+ searchy = rect.y;
+ }
+ }
+ const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx;
+ const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy;
+ return this.props.ScreenToLocalTransform().transformPoint(x, y);
+ }
+
+ get documentKeys() {
+ const docs = this.childDocs;
+ const keys: { [key: string]: boolean } = {};
+ // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
+ // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
+ // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
+ // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
+ // is displayed (unlikely) it won't show up until something else changes.
+ //TODO Types
+ untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false))));
+
+ this.columns.forEach(key => keys[key.heading] = true);
+ return Array.from(Object.keys(keys));
+ }
+
+ @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing;
+
+ @undoBatch
+ setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => {
+ this._openTypes = false;
+ if (columnTypes.get(columnField.heading)) return;
+
+ const columns = this.columns;
+ const index = columns.indexOf(columnField);
+ if (index > -1) {
+ columnField.setType(NumCast(type));
+ columns[index] = columnField;
+ this.columns = columns;
+ }
+ });
+
+ @undoBatch
+ setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
+ const columns = this.columns;
+ const index = columns.indexOf(columnField);
+ if (index > -1) {
+ columnField.setColor(color);
+ columns[index] = columnField;
+ this.columns = columns; // need to set the columns to trigger rerender
+ }
+ }
+
+ @undoBatch
+ @action
+ setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
+ const columns = this.columns;
+ columns.forEach(col => col.setDesc(undefined));
+
+ const index = columns.findIndex(c => c.heading === columnField.heading);
+ const column = columns[index];
+ column.setDesc(descending);
+ columns[index] = column;
+ this.columns = columns;
+ }
+
+ renderTypes = (col: any) => {
+ if (columnTypes.get(col.heading)) return (null);
+
+ const type = col.type;
+
+ const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Any)}>
+ <FontAwesomeIcon icon={"align-justify"} size="sm" />
+ Any
+ </div>;
+
+ const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Number)}>
+ <FontAwesomeIcon icon={"hashtag"} size="sm" />
+ Number
+ </div>;
+
+ const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.String)}>
+ <FontAwesomeIcon icon={"font"} size="sm" />
+ Text
+ </div>;
+
+ const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
+ <FontAwesomeIcon icon={"check-square"} size="sm" />
+ Checkbox
+ </div>;
+
+ const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.List)}>
+ <FontAwesomeIcon icon={"list-ul"} size="sm" />
+ List
+ </div>;
+
+ const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
+ <FontAwesomeIcon icon={"file"} size="sm" />
+ Document
+ </div>;
+
+ const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Image)}>
+ <FontAwesomeIcon icon={"image"} size="sm" />
+ Image
+ </div>;
+
+ const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Date)}>
+ <FontAwesomeIcon icon={"calendar"} size="sm" />
+ Date
+ </div>;
+
+
+ const allColumnTypes = <div className="columnMenu-types">
+ {anyType}
+ {numType}
+ {textType}
+ {boolType}
+ {listType}
+ {docType}
+ {imageType}
+ {dateType}
+ </div>;
+
+ const justColType = type === ColumnType.Any ? anyType : type === ColumnType.Number ? numType :
+ type === ColumnType.String ? textType : type === ColumnType.Boolean ? boolType :
+ type === ColumnType.List ? listType : type === ColumnType.Doc ? docType :
+ type === ColumnType.Date ? dateType : imageType;
+
+ return (
+ <div className="collectionSchema-headerMenu-group" onClick={action(() => this._openTypes = !this._openTypes)}>
+ <div>
+ <label style={{ cursor: "pointer" }}>Column type:</label>
+ <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right", transform: `rotate(${this._openTypes ? "180deg" : 0})`, transition: "0.2s all ease" }} />
+ </div>
+ {this._openTypes ? allColumnTypes : justColType}
+ </div >
+ );
+ }
+
+ renderSorting = (col: any) => {
+ const sort = col.desc;
+ return (
+ <div className="collectionSchema-headerMenu-group">
+ <label>Sort by:</label>
+ <div className="columnMenu-sort">
+ <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.setColumnSort(col, true)}>
+ <FontAwesomeIcon icon="sort-amount-down" size="sm" />
+ Sort descending
+ </div>
+ <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.setColumnSort(col, false)}>
+ <FontAwesomeIcon icon="sort-amount-up" size="sm" />
+ Sort ascending
+ </div>
+ <div className="columnMenu-option" onClick={() => this.setColumnSort(col, undefined)}>
+ <FontAwesomeIcon icon="times" size="sm" />
+ Clear sorting
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ renderColors = (col: any) => {
+ const selected = col.color;
+
+ const pink = PastelSchemaPalette.get("pink2");
+ const purple = PastelSchemaPalette.get("purple2");
+ const blue = PastelSchemaPalette.get("bluegreen1");
+ const yellow = PastelSchemaPalette.get("yellow4");
+ const red = PastelSchemaPalette.get("red2");
+ const gray = "#f1efeb";
+
+ return (
+ <div className="collectionSchema-headerMenu-group">
+ <label>Color:</label>
+ <div className="columnMenu-colors">
+ <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.setColumnColor(col, pink!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div>
+ </div>
+ </div>
+ );
+ }
+
+ @undoBatch
+ @action
+ changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => {
+ const columns = this.columns;
+ if (columns === undefined) {
+ this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]);
+ } else {
+ if (addNew) {
+ columns.push(new SchemaHeaderField(newKey, "f1efeb"));
+ this.columns = columns;
+ } else {
+ const index = columns.map(c => c.heading).indexOf(oldKey);
+ if (index > -1) {
+ const column = columns[index];
+ column.setHeading(newKey);
+ columns[index] = column;
+ this.columns = columns;
+ if (filter) {
+ Doc.setDocFilter(this.props.Document, newKey, filter, "match");
+ }
+ else {
+ this.props.Document._docFilters = undefined;
+ }
+ }
+ }
+ }
+ }
+
+ @action
+ openHeader = (col: any, screenx: number, screeny: number) => {
+ this._col = col;
+ this._headerOpen = true;
+ this._pointerX = screenx;
+ this._pointerY = screeny;
+ }
+
+ @action
+ closeHeader = () => { this._headerOpen = false; }
+
+ @undoBatch
+ @action
+ deleteColumn = (key: string) => {
+ const columns = this.columns;
+ if (columns === undefined) {
+ this.columns = new List<SchemaHeaderField>([]);
+ } else {
+ const index = columns.map(c => c.heading).indexOf(key);
+ if (index > -1) {
+ columns.splice(index, 1);
+ this.columns = columns;
+ }
+ }
+ this.closeHeader();
+ }
+
+ getPreviewTransform = (): Transform => {
+ return this.props.ScreenToLocalTransform().translate(- this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, - this.borderWidth);
+ }
+
+ @action
+ onHeaderClick = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ }
+
+ @action
+ onWheel(e: React.WheelEvent) {
+ const scale = this.props.ScreenToLocalTransform().Scale;
+ this.props.isContentActive(true) && e.stopPropagation();
+ }
+
+ @computed get renderMenuContent() {
+ TraceMobx();
+ return <div className="collectionSchema-header-menuOptions">
+ {this.renderTypes(this._col)}
+ {this.renderColors(this._col)}
+ <div className="collectionSchema-headerMenu-group">
+ <button onClick={() => { this.deleteColumn(this._col.heading); }}
+ >Delete Column</button>
+ </div>
+ </div>;
+ }
+
+ private createTarget = (ele: HTMLDivElement) => {
+ this._previewCont = ele;
+ super.CreateDropTarget(ele);
+ }
+
+ isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable;
+
+ @action setFocused = (doc: Doc) => this._focusedTable = doc;
+
+ @action setPreviewDoc = (doc: Opt<Doc>) => {
+ SelectionManager.SelectSchemaView(this, doc);
+ this._previewDoc = doc;
+ }
+
+ //toggles preview side-panel of schema
+ @action
+ toggleExpander = () => {
+ this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
+ }
+
+ onDividerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander);
+ }
+ @action
+ onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ const nativeWidth = this._previewCont!.getBoundingClientRect();
+ const minWidth = 40;
+ const maxWidth = 1000;
+ const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
+ const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
+ this.props.Document.schemaPreviewWidth = width;
+ return false;
+ }
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (this.props.isSelected(true)) e.stopPropagation();
+ else this.props.select(false);
+ }
+ }
+
+ @computed
+ get previewDocument(): Doc | undefined { return this._previewDoc; }
+
+ @computed
+ get dividerDragger() {
+ return this.previewWidth() === 0 ? (null) :
+ <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} >
+ <div className="collectionSchemaView-dividerDragger" />
+ </div>;
+ }
+
+ @computed
+ get previewPanel() {
+ return <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
+ {!this.previewDocument ? (null) :
+ <DocumentView
+ Document={this.previewDocument}
+ DataDoc={undefined}
+ fitContentsToDoc={returnTrue}
+ freezeDimensions={true}
+ dontCenter={"y"}
+ focus={DocUtils.DefaultFocus}
+ renderDepth={this.props.renderDepth}
+ rootSelected={this.rootSelected}
+ PanelWidth={this.previewWidth}
+ PanelHeight={this.previewHeight}
+ isContentActive={returnTrue}
+ isDocumentActive={returnFalse}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ docFilters={this.docFilters}
+ docRangeFilters={this.docRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ moveDocument={this.props.moveDocument}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ />}
+ </div>;
+ }
+
+ @computed
+ get schemaTable() {
+ return <SchemaTable
+ Document={this.props.Document}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.props.PanelWidth}
+ childDocs={this.childDocs}
+ CollectionView={this.props.CollectionView}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ fieldKey={this.props.fieldKey}
+ renderDepth={this.props.renderDepth}
+ moveDocument={this.props.moveDocument}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ active={this.props.isContentActive}
+ onDrop={this.onExternalDrop}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ isSelected={this.props.isSelected}
+ isFocused={this.isFocused}
+ setFocused={this.setFocused}
+ setPreviewDoc={this.setPreviewDoc}
+ deleteDocument={this.props.removeDocument}
+ addDocument={this.props.addDocument}
+ dataDoc={this.props.DataDoc}
+ columns={this.columns}
+ documentKeys={this.documentKeys}
+ headerIsEditing={this._headerIsEditing}
+ openHeader={this.openHeader}
+ onClick={this.onTableClick}
+ onPointerDown={emptyFunction}
+ onResizedChange={this.onResizedChange}
+ setColumns={this.setColumns}
+ reorderColumns={this.reorderColumns}
+ changeColumns={this.changeColumns}
+ setHeaderIsEditing={this.setHeaderIsEditing}
+ changeColumnSort={this.setColumnSort}
+ />;
+ }
+
+ @computed
+ public get schemaToolbar() {
+ return <div className="collectionSchemaView-toolbar">
+ <div className="collectionSchemaView-toolbar-item">
+ <div id="preview-schema-checkbox-div">
+ <input type="checkbox" key={"Show Preview"} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} />
+ Show Preview
+ </div>
+ </div>
+ </div>;
+ }
+
+ onSpecificMenu = (e: React.MouseEvent) => {
+ if ((e.target as any)?.className?.includes?.("collectionSchemaView-cell") || (e.target instanceof HTMLSpanElement)) {
+ const cm = ContextMenu.Instance;
+ const options = cm.findByDescription("Options...");
+ const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+ optionItems.push({ description: "remove", event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: "trash" });
+ !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+ cm.displayMenu(e.clientX, e.clientY);
+ (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this.
+ e.stopPropagation();
+ }
+ }
+
+ @action
+ onTableClick = (e: React.MouseEvent): void => {
+ if (!(e.target as any)?.className?.includes?.("collectionSchemaView-cell") && !(e.target instanceof HTMLSpanElement)) {
+ this.setPreviewDoc(undefined);
+ } else {
+ e.stopPropagation();
+ }
+ this.setFocused(this.props.Document);
+ this.closeHeader();
+ }
+
+ onResizedChange = (newResized: Resize[], event: any) => {
+ const columns = this.columns;
+ newResized.forEach(resized => {
+ const index = columns.findIndex(c => c.heading === resized.id);
+ const column = columns[index];
+ column.setWidth(resized.value);
+ columns[index] = column;
+ });
+ this.columns = columns;
+ }
+
+ @action
+ setColumns = (columns: SchemaHeaderField[]) => this.columns = columns
+
+ @undoBatch
+ reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => {
+ const columns = [...columnsValues];
+ const oldIndex = columns.indexOf(toMove);
+ const relIndex = columns.indexOf(relativeTo);
+ const newIndex = (oldIndex > relIndex && !before) ? relIndex + 1 : (oldIndex < relIndex && before) ? relIndex - 1 : relIndex;
+
+ if (oldIndex === newIndex) return;
+
+ columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
+ this.columns = columns;
+ }
+
+ onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation();
+
+ render() {
+ TraceMobx();
+ if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0);
+ const menuContent = this.renderMenuContent;
+ const menu = <div className="collectionSchema-header-menu"
+ onWheel={e => this.onZoomMenu(e)}
+ onPointerDown={e => this.onHeaderClick(e)}
+ style={{ transform: `translate(${(this.menuCoordinates[0])}px, ${(this.menuCoordinates[1])}px)` }}>
+ <Measure offset onResize={action((r: any) => {
+ const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height);
+ this._menuWidth = dim[0]; this._menuHeight = dim[1];
+ })}>
+ {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
+ </Measure>
+ </div>;
+ return <div className={"collectionSchemaView" + (this.props.Document._searchDoc ? "-searchContainer" : "-container")}
+ style={{
+ overflow: this.props.scrollOverflow === true ? "scroll" : undefined, backgroundColor: "white",
+ pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined,
+ width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative",
+ }} >
+ <div className="collectionSchemaView-tableContainer"
+ style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
+ onContextMenu={this.onSpecificMenu}
+ onPointerDown={this.onPointerDown}
+ onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}
+ onDrop={e => this.onExternalDrop(e, {})}
+ ref={this.createTarget}>
+ {this.schemaTable}
+ </div>
+ {this.dividerDragger}
+ {!this.previewWidth() ? (null) : this.previewPanel}
+ {this._headerOpen && this.props.isContentActive() ? menu : null}
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx
index 0c69ee030..0d5c9e077 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx
@@ -5,32 +5,33 @@ import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table";
import "react-table/react-table.css";
-import { DateField } from "../../../fields/DateField";
-import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { ComputedField } from "../../../fields/ScriptField";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
-import { ImageField } from "../../../fields/URLField";
-import { GetEffectiveAcl } from "../../../fields/util";
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils";
-import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { CompileScript, Transformer, ts } from "../../util/Scripting";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/globalCssVariables.scss';
-import { ContextMenu } from "../ContextMenu";
-import '../DocumentDecorations.scss';
-import { DocumentView } from "../nodes/DocumentView";
-import { DefaultStyleProvider } from "../StyleProvider";
+import { DateField } from "../../../../fields/DateField";
+import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from "../../../../fields/Doc";
+import { Id } from "../../../../fields/FieldSymbols";
+import { List } from "../../../../fields/List";
+import { listSpec } from "../../../../fields/Schema";
+import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
+import { ComputedField } from "../../../../fields/ScriptField";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
+import { ImageField } from "../../../../fields/URLField";
+import { GetEffectiveAcl } from "../../../../fields/util";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../../Utils";
+import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { CompileScript, Transformer, ts } from "../../../util/Scripting";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../globalCssVariables.scss';
+import { ContextMenu } from "../../ContextMenu";
+import '../../../views/DocumentDecorations.scss';
+import { DocumentView } from "../../nodes/DocumentView";
+import { DefaultStyleProvider } from "../../StyleProvider";
import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells";
import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders";
-import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
+import { MovableColumn } from "./CollectionSchemaMovableColumn";
+import { MovableRow } from "./CollectionSchemaMovableRow";
import "./CollectionSchemaView.scss";
-import { CollectionView } from "./CollectionView";
+import { CollectionView } from "../CollectionView";
enum ColumnType {
@@ -457,8 +458,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
expanded={expanded}
resized={this.resized}
onResizedChange={this.props.onResizedChange}
+ // if it has a child, render another table with the children
SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== DocumentType.COL) ? (null) :
- <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
+ <div style={{ paddingLeft: 57 + "px" }} className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
/>;
}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f0a54e4ac..a0a40becb 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -8,7 +8,7 @@ import { emptyPath, OmitKeys, Without } from "../../../Utils";
import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
+import { CollectionSchemaView } from "../collections/collectionSchema/CollectionSchemaView";
import { CollectionView } from "../collections/CollectionView";
import { InkingStroke } from "../InkingStroke";
import { PresElementBox } from "../presentationview/PresElementBox";
@@ -180,7 +180,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
return prefix + (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string || "") + postfix;
};
- layoutFrame = layoutFrame.replace(/(>[^{]*)\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
+ layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
// 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) => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b861669f8..60fa462ad 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -13,7 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty
import { AudioField } from "../../../fields/URLField";
import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, OmitKeys, returnVal, Utils } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, OmitKeys, returnVal, Utils, returnTrue } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
@@ -137,7 +137,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
treeViewDoc?: Doc;
isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
- isContentActive: () => boolean | undefined; // whether a document should handle pointer events
+ isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
radialMenu?: String[];
LayoutTemplateString?: string;
@@ -846,6 +846,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
dontRegisterView={false}
+ fitWidth={returnTrue}
styleProvider={this.anchorStyleProvider}
removeDocument={this.hideLinkAnchor}
LayoutTemplate={undefined}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e2e08a0e6..d876ae818 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -7,10 +7,10 @@ import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { createSchema, makeInterface } from '../../../fields/Schema';
import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, NumCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, OmitKeys, returnOne, Utils } from '../../../Utils';
+import { emptyFunction, OmitKeys, returnOne, Utils, returnFalse } from '../../../Utils';
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
import { Networking } from '../../Network';
@@ -26,6 +26,10 @@ import { FaceRectangles } from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import React = require("react");
+import { InkTool } from '../../../fields/InkField';
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { AnchorMenu } from '../pdf/AnchorMenu';
+import { Docs } from '../../documents/Documents';
const path = require('path');
export const pageSchema = createSchema({
@@ -60,6 +64,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
} // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
+
+ getAnchor = () => {
+ const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations);
+ anchor && this.addDocument(anchor);
+ return anchor ?? this.rootDoc;
+ }
+
componentDidMount() {
this.props.setContentView?.(this); // bcz: do not remove this. without it, stepping into an image in the lightbox causes an infinite loop....
this._disposers.sizer = reaction(() => (
@@ -72,8 +83,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
{ fireImmediately: true, delay: 1000 });
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => !selected && setTimeout(() => {
- Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
- this._savedAnnotations.clear();
+ // Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
+ // this._savedAnnotations.clear();
}));
this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
({ nativeSize, width }) => {
@@ -321,12 +332,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@action
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true)) this._marqueeing = [e.clientX, e.clientY];
+ if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ this._marqueeing = [e.clientX, e.clientY];
+ e.stopPropagation();
+ }
}
@action
finishMarquee = () => {
this._marqueeing = undefined;
- this.props.select(true);
+ this.props.select(false)
}
render() {
@@ -342,16 +356,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}} >
<CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
renderDepth={this.props.renderDepth + 1}
+ isAnnotationOverlay={true}
fieldKey={this.annotationKey}
CollectionView={undefined}
- isAnnotationOverlay={true}
annotationLayerHostsContent={true}
PanelWidth={this.props.PanelWidth}
PanelHeight={this.props.PanelHeight}
ScreenToLocalTransform={this.screenToLocalTransform}
+ scaling={returnOne}
select={emptyFunction}
isContentActive={this.isContentActive}
- scaling={returnOne}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index feaeb9e21..8f61e252b 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -23,6 +23,7 @@ import { FieldView, FieldViewProps } from './FieldView';
import { pageSchema } from "./ImageBox";
import "./PDFBox.scss";
import React = require("react");
+import { AnchorMenu } from '../pdf/AnchorMenu';
type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -94,11 +95,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return this._pdfViewer?.scrollFocus(doc, smooth);
}
getAnchor = () => {
- const anchor = Docs.Create.TextanchorDocument({
- title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
- annotationOn: this.rootDoc,
- y: NumCast(this.layoutDoc._scrollTop),
- });
+ const anchor =
+ AnchorMenu.Instance?.GetAnchor() ??
+ Docs.Create.TextanchorDocument({
+ title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
+ annotationOn: this.rootDoc,
+ y: NumCast(this.layoutDoc._scrollTop),
+ });
this.addDocument(anchor);
return anchor;
}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 252c029e4..700f8a7d3 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -252,8 +252,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
this.dataDoc.mediaState = "recording";
DocUtils.ActiveRecordings.push(this);
} else {
- this._audioRec.stop();
- this._videoRec.stop();
+ this._audioRec?.stop();
+ this._videoRec?.stop();
this.dataDoc.mediaState = "paused";
const ind = DocUtils.ActiveRecordings.indexOf(this);
ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 263fd5a19..fc08a2302 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -543,7 +543,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
marqueeDown = action((e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true)) this._marqueeing = [e.clientX, e.clientY];
+ if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) this._marqueeing = [e.clientX, e.clientY];
});
finishMarquee = action(() => {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index cb7e58559..88e38712a 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -88,9 +88,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => !selected && setTimeout(() => {
- Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
- this._savedAnnotations.clear();
- }));
+ // Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
+ // this._savedAnnotations.clear();
+ })
+ );
if (this.webField?.href.indexOf("youtube") !== -1) {
const youtubeaspect = 400 / 315;
@@ -174,11 +175,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
getAnchor = () => {
- const anchor = Docs.Create.TextanchorDocument({
- title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
- annotationOn: this.rootDoc,
- y: NumCast(this.layoutDoc._scrollTop),
- });
+ const anchor =
+ AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
+ Docs.Create.TextanchorDocument({
+ title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
+ annotationOn: this.rootDoc,
+ y: NumCast(this.layoutDoc._scrollTop),
+ });
this.addDocument(anchor);
return anchor;
}
@@ -395,7 +398,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
onMarqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.isContentActive(true)) {
+ if (!e.altKey && e.button === 0 && this.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
this._marqueeing = [e.clientX, e.clientY];
this.props.select(false);
}
@@ -505,11 +508,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}}>
{this.content}
<CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ renderDepth={this.props.renderDepth + 1}
isAnnotationOverlay={true}
fieldKey={this.annotationKey}
+ CollectionView={undefined}
setPreviewCursor={this.setPreviewCursor}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
+ ScreenToLocalTransform={this.scrollXf}
+ scaling={returnOne}
dropAction={"alias"}
select={emptyFunction}
isContentActive={returnFalse}
@@ -519,10 +526,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.sidebarAddDocument}
- CollectionView={undefined}
- ScreenToLocalTransform={this.scrollXf}
- renderDepth={this.props.renderDepth + 1}
- scaling={returnOne}
childPointerEvents={true}
pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />
{this.annotationLayer}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index e16036000..e7dd286a5 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -1,6 +1,7 @@
.dashFieldView {
position: relative;
- display: inline-block;
+ display: inline-flex;
+ align-items: center;
.dashFieldView-enumerables {
width: 10px;
@@ -13,6 +14,8 @@
min-width: 12px;
position: relative;
display: inline-block;
+ margin: 0;
+ transform: scale(0.7);
background-color: rgba(155, 155, 155, 0.24);
}
.dashFieldView-labelSpan {
@@ -22,11 +25,11 @@
background: rgba(0,0,0,0.1);
}
.dashFieldView-fieldSpan {
- min-width: 20px;
+ min-width: 8px;
margin-left: 2px;
margin-right: 5px;
- position: relative;
- display: inline;
+ padding-left: 2px;
+ display: inline-block;
background-color: rgba(155, 155, 155, 0.24);
font-weight: bold;
span {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 911ec1560..42cb02782 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -71,7 +71,7 @@ export interface FormattedTextBoxProps {
xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yPadding?: number;
noSidebar?: boolean;
- dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded
+ dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)
}
export const GoogleRef = "googleDocId";
@@ -876,7 +876,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
var quickScroll: string | undefined = "";
this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
pos => {
- if (!this._ignoreScroll && this._scrollRef.current) {
+ if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) {
const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
@@ -1413,9 +1413,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
onScroll = (e: React.UIEvent) => {
if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
- this._ignoreScroll = true;
- this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
- this._ignoreScroll = false;
+ if (!this.props.dontSelectOnLoad) {
+ this._ignoreScroll = true;
+ this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
+ this._ignoreScroll = false;
+ }
}
}
tryUpdateScrollHeight() {
@@ -1517,8 +1519,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const selPad = Math.min(margins, 10);
const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0);
const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : "";
- const col = this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
- const back = this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
return (
<div className="formattedTextBox-cont"
onWheel={e => this.isContentActive() && e.stopPropagation()}
@@ -1554,7 +1554,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
>
<div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef}
style={{
- width: `calc(100% - ${this.sidebarWidthPercent})`,
+ width: this.props.dontSelectOnLoad ? "100%" : `calc(100% - ${this.sidebarWidthPercent})`,
pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined,
overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
}}
@@ -1566,8 +1566,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}}
/>
</div>
- {(this.props.noSidebar || this.Document._noSidebar) || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
- {(this.props.noSidebar || this.Document._noSidebar) || this.Document._singleLine ? (null) : this.sidebarHandle}
+ {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
+ {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle}
{!this.layoutDoc._showAudio ? (null) : this.audioHandle}
</div>
</div >
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 071491463..59b2d3753 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -352,7 +352,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
function onClick(e: React.PointerEvent) {
e.preventDefault();
e.stopPropagation();
- self.TextView.endUndoTypingBatch();
+ self.TextView?.endUndoTypingBatch();
UndoManager.RunInBatch(() => {
self.view && command && command(self.view.state, self.view.dispatch, self.view);
self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 1e2d72254..c24c4eaaf 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,7 +1,7 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
-import { action, computed, observable, IReactionDisposer, reaction } from "mobx";
+import { action, computed, observable, IReactionDisposer, reaction, ObservableMap } from "mobx";
import { observer } from "mobx-react";
import { ColorState } from "react-color";
import { Doc, Opt } from "../../../fields/Doc";
@@ -46,6 +46,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public OnClick: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined;
+ public GetAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
public Delete: () => void = unimplementedFunction;
public AddTag: (key: string, value: string) => boolean = returnFalse;
public PinToPres: () => void = unimplementedFunction;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 85cf5abd7..4a50dccf3 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -133,10 +133,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._disposers.selected = reaction(() => this.props.isSelected(),
selected => {
- if (!selected) {
- Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
- Array.from(this._savedAnnotations.keys()).forEach(k => this._savedAnnotations.set(k, []));
- }
+ // if (!selected) {
+ // Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
+ // Array.from(this._savedAnnotations.keys()).forEach(k => this._savedAnnotations.set(k, []));
+ // }
(SelectionManager.Views().length === 1) && this.setupPdfJsViewer();
},
{ fireImmediately: true });
@@ -372,7 +372,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
this._setPreviewCursor?.(e.clientX, e.clientY, true);
}
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true)) {
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
this.props.select(false);
this._marqueeing = [e.clientX, e.clientY];
if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 5c168d8a9..6a2325342 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -18,7 +18,7 @@ import { SetupDrag } from '../../util/DragManager';
import { SearchUtil } from '../../util/SearchUtil';
import { Transform } from '../../util/Transform';
import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionSchemaView, ColumnType } from "../collections/CollectionSchemaView";
+import { CollectionSchemaView, ColumnType } from "../collections/collectionSchema/CollectionSchemaView";
import { CollectionViewType } from '../collections/CollectionView';
import { ViewBoxBaseComponent } from "../DocComponent";
import { FieldView, FieldViewProps } from '../nodes/FieldView';
@@ -119,7 +119,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
getFinalQuery(query: string): string {
//alters the query so it looks in the correct fields
- //if this is true, th`en not all of the field boxes are checked
+ //if this is true, then not all of the field boxes are checked
//TODO: data
const initialfilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
@@ -522,7 +522,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
{`${Doc.CurrentUserEmail}`}
<div className="searchBox-logoff" onClick={() => window.location.assign(Utils.prepend("/logout"))}>
Logoff
- </div>
+ </div>
</div>
<div className="searchBox-lozenge" onClick={() => DocServer.UPDATE_SERVER_CACHE()}>
{`UI project`}
@@ -534,10 +534,10 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
</select>
<div className="searchBox-dashboards" onClick={undoBatch(() => CurrentUserUtils.createNewDashboard(Doc.UserDoc()))}>
New
- </div>
+ </div>
<div className="searchBox-dashboards" onClick={undoBatch(() => CurrentUserUtils.snapshotDashboard(Doc.UserDoc()))}>
Snapshot
- </div>
+ </div>
</div>
</div>
<div className="searchBox-query" >
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index 88de3a19f..a53fa542e 100644
--- a/src/fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
@@ -3,7 +3,7 @@ import { serializable, primitive } from "serializr";
import { ObjectField } from "./ObjectField";
import { Copy, ToScriptString, ToString, OnUpdate } from "./FieldSymbols";
import { scriptingGlobal } from "../client/util/Scripting";
-import { ColumnType } from "../client/views/collections/CollectionSchemaView";
+import { ColumnType } from "../client/views/collections/collectionSchema/CollectionSchemaView";
export const PastelSchemaPalette = new Map<string, string>([
// ["pink1", "#FFB4E8"],
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index e7cc89697..65f2bf80c 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -20,9 +20,7 @@ export namespace GestureUtils {
): GestureEventDisposer {
const handler = (e: Event) => func(e, (e as CustomEvent<GestureEvent>).detail);
element.addEventListener("dashOnGesture", handler);
- return () => {
- element.removeEventListener("dashOnGesture", handler);
- };
+ return () => element.removeEventListener("dashOnGesture", handler);
}
export enum Gestures {
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 555e3bf3b..5ce69999a 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -33,7 +33,7 @@ export function InjectSize(filename: string, size: SizeSuffix) {
}
function isLocal() {
- return /Dash-Web[\\\/]src[\\\/]server[\\\/]public[\\\/](.*)/;
+ return /Dash-Web[0-9]*[\\\/]src[\\\/]server[\\\/]public[\\\/](.*)/;
}
export namespace DashUploadUtils {