aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.idea/.gitignore3
-rw-r--r--.idea/Dash-Web.iml9
-rw-r--r--.idea/modules.xml8
-rw-r--r--.idea/vcs.xml6
-rw-r--r--src/client/documents/Documents.ts18
-rw-r--r--src/client/util/CurrentUserUtils.ts13
-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/DocumentDecorations.tsx4
-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/SidebarAnnos.tsx5
-rw-r--r--src/client/views/StyleProvider.tsx2
-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.scss5
-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.scss1
-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.tsx49
-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
44 files changed, 1057 insertions, 415 deletions
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 000000000..26d33521a
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/Dash-Web.iml b/.idea/Dash-Web.iml
new file mode 100644
index 000000000..d6ebd4805
--- /dev/null
+++ b/.idea/Dash-Web.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module> \ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 000000000..35c51c015
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/Dash-Web.iml" filepath="$PROJECT_DIR$/.idea/Dash-Web.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..35eb1ddfb
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0c4d266d4..c2cdf7f72 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/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 12733e815..5bab827d5 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -406,11 +406,18 @@ export class CurrentUserUtils {
storedMarks: []
};
const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { title: "header", version: headerViewVersion, target: doc, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, cloneFieldFilter: new List<string>(["system"]) }, "header"); // text needs to be a space to allow templateText to be created
+ const headerBtnHgt = 10;
headerTemplate[DataSym].layout =
- "<div style={'height:100%'}>" +
- " <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._headerPointerEvents||`none`}' fontSize='{this._headerFontSize}px' height='{this._headerHeight}px' background='{this._headerColor||this.target.mySharedDocs.userColor}' />" +
- " <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._headerHeight)*scale}px' height='calc({100/scale}% - {this._headerHeight}px)'/>" +
+ "<div style='height:100%'>" +
+ ` <FormattedTextBox {...props} fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._headerHeight}px)'/>` +
+ " <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize}px' height='{this._headerHeight||1}px' background={this._headerColor ||this.target.mySharedDocs.userColor||'lightGray'} />" +
+ ` <HTMLdiv fontSize='${headerBtnHgt - 1}' onClick={‘(this._headerHeight=Math.min(Math.max(1,this._height-30),this._headerHeight===1?50:1)) && (this._autoHeightMargins=this._headerHeight+${headerBtnHgt})’} height='${headerBtnHgt}px' background='yellow' >Metadata</HTMLdiv>` +
"</div>";
+
+ // "<div style={'height:100%'}>" +
+ // " <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._headerPointerEvents||`none`}' fontSize='{this._headerFontSize}px' height='{this._headerHeight}px' background='{this._headerColor||this.target.mySharedDocs.userColor}' />" +
+ // " <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._headerHeight)*scale}px' height='calc({100/scale}% - {this._headerHeight}px)'/>" +
+ // "</div>";
(headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView");
doc.emptyHeader = headerTemplate;
((doc.emptyHeader as Doc).proto as Doc)["dragFactory-count"] = 0;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index e0e6024d2..07b2b7dff 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/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index bf939d57c..65a97a49d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -235,7 +235,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
this._resizeUndo = UndoManager.StartBatch("DocDecs resize");
this._snapX = e.pageX;
this._snapY = e.pageY;
- DragManager.docsBeingDragged.forEach(doc => this._dragHeights.set(doc, { start: NumCast(doc._height), lowest: NumCast(doc._height) }));
+ SelectionManager.Views().forEach(docView => this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) }));
}
onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
@@ -382,7 +382,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
SnappingManager.clearSnapLines();
// detect autoHeight gesture and apply
- DragManager.docsBeingDragged.map(doc => ({ doc, hgts: this._dragHeights.get(doc) }))
+ SelectionManager.Views().map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) }))
.filter(pair => pair.hgts && pair.hgts.lowest < pair.hgts.start && pair.hgts.lowest <= 20)
.forEach(pair => pair.doc._autoHeight = true);
//need to change points for resize, or else rotation/control points will fail.
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 cbaa706e0..c4162a6bb 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/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 59ff1c340..9c5a54574 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -78,7 +78,9 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
sidebarStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
- if (property === StyleProp.ShowTitle) return StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
+ if (property === StyleProp.ShowTitle) {
+ return doc === this.props.rootDoc ? 0 : StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
+ }
return this.props.styleProvider?.(doc, props, property);
}
render() {
@@ -117,6 +119,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
styleProvider={this.sidebarStyleProvider}
docFilters={this.docFilters}
scaleField={this.sidebarKey() + "-scale"}
+ setHeight={(height) => this.props.setHeight(height + this.filtersHeight())}
isAnnotationOverlay={false}
select={emptyFunction}
scaling={returnOne}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 1bf47f6ac..55f6b8e3c 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -128,7 +128,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() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
case DocumentType.LINKANCHOR: docColor = isAnchor ? Colors.LIGHT_BLUE : "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/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 4f9a966f2..401bdcb02 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 ee6d446f8..90f64f163 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 '../global/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 '../../global/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 d529f097e..4cc34cd32 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,5 +1,4 @@
-@import "../global/globalCssVariables";
-
+@import "../../globalCssVariables";
.collectionSchemaView-container {
border-width: $COLLECTION_BORDER_WIDTH;
border-color: $medium-gray;
@@ -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: $white;
// }
@@ -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: $white;
// }
@@ -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: $medium-gray;
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-gray;
}
-
&.active {
font-weight: bold;
border: 2px solid $light-gray;
}
-
svg {
color: gray;
margin-right: 5px;
width: 10px;
}
}
-
.keys-dropdown {
position: relative;
//width: 100%;
background-color: white;
-
input {
border: 2px solid $light-gray;
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-gray;
}
}
}
}
-
.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-gray;
}
-
&.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 84560cf26..de08c327a 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/global/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 '../../global/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.scss b/src/client/views/nodes/PDFBox.scss
index 0f46da294..72dec6e4c 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -7,7 +7,7 @@
overflow: hidden;
cursor: auto;
transform-origin: top left;
- z-index: 0;
+ //z-index: 0;
.pdfBox-ui {
position: absolute;
@@ -30,6 +30,7 @@
justify-content: center;
border-radius: 3px;
pointer-events: all;
+ z-index: 1; // so it appears on top of the document's title, if shown
}
.pdfBox-pageNums {
@@ -223,7 +224,7 @@
.pdfBox {
width: 100%;
height: 100%;
- pointer-events: none;
+ //pointer-events: none;
.pdfViewerDash-text {
.textLayer {
display: none;
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.scss b/src/client/views/nodes/WebBox.scss
index e206508d3..19b69ff5a 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -17,6 +17,7 @@
justify-content: center;
border-radius: 3px;
pointer-events: all;
+ z-index: 1; // so it appears on top of the document's title, if shown
}
.pdfViewerDash-dragAnnotationBox {
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..95d8f555c 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";
@@ -124,8 +124,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; }
@computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); }
@computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); }
- @computed get sidebarHeight() { return NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
+ @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
@computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
+ @computed get autoHeightMargins() { return this.titleHeight + (this.layoutDoc._autoHeightMargins && !this.props.dontSelectOnLoad ? NumCast(this.layoutDoc._autoHeightMargins) : 0); }
@computed get _recording() { return this.dataDoc?.mediaState === "recording"; }
set _recording(value) {
!this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined);
@@ -780,7 +781,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// Since we also monitor all component height changes, this will update the document's height.
resetNativeHeight = (scrollHeight: number) => {
const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
- this.rootDoc[this.fieldKey + "-height"] = scrollHeight + this.titleHeight;
+ this.rootDoc[this.fieldKey + "-height"] = scrollHeight;
if (nh) this.layoutDoc._nativeHeight = scrollHeight;
}
@@ -793,8 +794,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
({ width, scrollHeight, autoHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight)
);
this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on
- () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight }),
- ({ sidebarHeight, textHeight, autoHeight }) => autoHeight && this.props.setHeight(Math.max(sidebarHeight, textHeight)));
+ () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
+ ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => autoHeight && this.props.setHeight(marginsHeight + Math.max(sidebarHeight, textHeight)));
this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
@@ -876,7 +877,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,23 +1414,24 @@ 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() {
if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
- setTimeout(() => { // bcz: don't know why this is needed, but without it, the size of the textbox is too big as it includes the size of the title header. after the timeout, the size seems to get computed correctly.
- const proseHeight = this.ProseRef?.scrollHeight || 0;
- const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
- if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
- const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
- if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
- setScrollHeight();
- } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
- }
- });
+ const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
+ const proseHeight = !this.ProseRef ? 0 : Array.from(this.ProseRef.children[0].children).reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+ const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+ if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
+ if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
+ setScrollHeight();
+ } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
+ }
}
}
fitToBox = () => this.props.Document._fitToBox;
@@ -1469,6 +1471,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
PanelWidth={this.sidebarWidth}
+ setHeight={this.setSidebarHeight}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
@@ -1517,8 +1520,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 +1555,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 +1567,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 {