aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin6148 -> 0 bytes
-rw-r--r--src/Utils.ts2
-rw-r--r--src/client/DocServer.ts7
-rw-r--r--src/client/documents/DocumentTypes.ts1
-rw-r--r--src/client/documents/Documents.ts106
-rw-r--r--src/client/util/CurrentUserUtils.ts116
-rw-r--r--src/client/util/DictationManager.ts2
-rw-r--r--src/client/util/DragManager.ts63
-rw-r--r--src/client/util/DropConverter.ts9
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx36
-rw-r--r--src/client/util/InteractionUtils.tsx112
-rw-r--r--src/client/util/LinkManager.ts3
-rw-r--r--src/client/util/ScriptManager.ts104
-rw-r--r--src/client/util/Scripting.ts66
-rw-r--r--src/client/util/SettingsManager.tsx7
-rw-r--r--src/client/util/UndoManager.ts22
-rw-r--r--src/client/views/ContextMenu.tsx2
-rw-r--r--src/client/views/DocComponent.tsx14
-rw-r--r--src/client/views/DocumentButtonBar.tsx1
-rw-r--r--src/client/views/DocumentDecorations.scss18
-rw-r--r--src/client/views/DocumentDecorations.tsx29
-rw-r--r--src/client/views/EditableView.tsx33
-rw-r--r--src/client/views/GestureOverlay.tsx20
-rw-r--r--src/client/views/GlobalKeyHandler.ts8
-rw-r--r--src/client/views/InkingStroke.tsx44
-rw-r--r--src/client/views/MainView.scss12
-rw-r--r--src/client/views/MainView.tsx18
-rw-r--r--src/client/views/OverlayView.tsx4
-rw-r--r--src/client/views/Palette.tsx3
-rw-r--r--src/client/views/RecommendationsBox.tsx5
-rw-r--r--src/client/views/TemplateMenu.tsx3
-rw-r--r--src/client/views/collections/CollectionDockingView.scss6
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx11
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx1
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx12
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx45
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx13
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx11
-rw-r--r--src/client/views/collections/CollectionStackingView.scss8
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx3
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx10
-rw-r--r--src/client/views/collections/CollectionSubView.tsx9
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx62
-rw-r--r--src/client/views/collections/CollectionView.tsx47
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss105
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx197
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx11
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx38
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx16
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss160
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx307
-rw-r--r--src/client/views/collections/collectionGrid/Grid.tsx53
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx1
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx1
-rw-r--r--src/client/views/nodes/ColorBox.tsx4
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx16
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx48
-rw-r--r--src/client/views/nodes/DocHolderBox.tsx11
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx229
-rw-r--r--src/client/views/nodes/FieldView.tsx1
-rw-r--r--src/client/views/nodes/ImageBox.tsx6
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx51
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx3
-rw-r--r--src/client/views/nodes/LabelBox.tsx2
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.tsx14
-rw-r--r--src/client/views/nodes/ScriptingBox.scss213
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx683
-rw-r--r--src/client/views/nodes/VideoBox.tsx1
-rw-r--r--src/client/views/nodes/WebBox.tsx1
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx129
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx3
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss2
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx26
-rw-r--r--src/client/views/nodes/formattedText/FootnoteView.tsx118
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss62
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx142
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx77
-rw-r--r--src/client/views/nodes/formattedText/ImageResizeView.tsx138
-rw-r--r--src/client/views/nodes/formattedText/OrderedListView.tsx8
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts177
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx8
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts315
-rw-r--r--src/client/views/nodes/formattedText/RichTextSchema.tsx398
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx116
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts28
-rw-r--r--src/client/views/pdf/PDFViewer.tsx27
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx1
-rw-r--r--src/client/views/search/SearchItem.tsx3
-rw-r--r--src/fields/Doc.ts27
-rw-r--r--src/fields/RichTextUtils.ts2
-rw-r--r--src/fields/ScriptField.ts2
-rw-r--r--src/fields/util.ts10
-rw-r--r--src/mobile/MobileInkOverlay.tsx3
-rw-r--r--src/mobile/MobileInterface.tsx5
-rw-r--r--src/server/Message.ts2
-rw-r--r--src/server/database.ts1
99 files changed, 3387 insertions, 1729 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
deleted file mode 100644
index 5b35884bd..000000000
--- a/src/.DS_Store
+++ /dev/null
Binary files differ
diff --git a/src/Utils.ts b/src/Utils.ts
index e527634fd..dba802f98 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -395,6 +395,8 @@ export function returnZero() { return 0; }
export function returnEmptyString() { return ""; }
+export function returnEmptyFilter() { return [] as string[]; }
+
export let emptyPath = [];
export function emptyFunction() { }
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index c6b3fa61f..c7dfb0b23 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,6 +1,6 @@
import * as io from 'socket.io-client';
import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc } from '../fields/Doc';
+import { Opt, Doc, fetchProto } from '../fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
import { RefField } from '../fields/RefField';
@@ -244,7 +244,10 @@ export namespace DocServer {
return cached;
} else {
// CACHED => great, let's just return the cached field we have
- return Promise.resolve(cached);
+ return Promise.resolve(cached).then(field => {
+ (field instanceof Doc) && fetchProto(field);
+ return field;
+ });
}
};
const _GetCachedRefFieldImpl = (id: string): Opt<RefField> => {
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 06d35038a..7ba21b2f6 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -34,5 +34,6 @@ export enum DocumentType {
COMPARISON = "comparison", // before/after view with slider (view of 2 images)
LINKDB = "linkdb", // database of links ??? why do we have this
+ SCRIPTDB = "scriptdb", // database of scripts
RECOMMENDATION = "recommendation", // view of a recommendation
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index cbf97e8e8..4e2c7f96c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,41 +1,32 @@
-import { CollectionView } from "../views/collections/CollectionView";
-import { CollectionViewType } from "../views/collections/CollectionView";
-import { AudioBox } from "../views/nodes/AudioBox";
-import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
-import { ImageBox } from "../views/nodes/ImageBox";
-import { KeyValueBox } from "../views/nodes/KeyValueBox";
-import { PDFBox } from "../views/nodes/PDFBox";
-import { ScriptingBox } from "../views/nodes/ScriptingBox";
-import { VideoBox } from "../views/nodes/VideoBox";
-import { WebBox } from "../views/nodes/WebBox";
-import { OmitKeys, JSONUtils, Utils } from "../../Utils";
-import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast, HeightSym, WidthSym } from "../../fields/Doc";
-import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../fields/URLField";
+import { runInAction } from "mobx";
+import { extname } from "path";
+import { DateField } from "../../fields/DateField";
+import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
import { HtmlField } from "../../fields/HtmlField";
+import { InkField } from "../../fields/InkField";
import { List } from "../../fields/List";
-import { Cast, NumCast, StrCast, FieldValue } from "../../fields/Types";
+import { ProxyField } from "../../fields/Proxy";
+import { RichTextField } from "../../fields/RichTextField";
+import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
+import { ComputedField, ScriptField } from "../../fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
+import { AudioField, ImageField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField";
+import { MessageStore } from "../../server/Message";
+import { OmitKeys, Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { dropActionType } from "../util/DragManager";
-import { DateField } from "../../fields/DateField";
-import { YoutubeBox } from "../apis/youtube/YoutubeBox";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { LinkManager } from "../util/LinkManager";
-import { DocumentManager } from "../util/DocumentManager";
-import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox";
import { Scripting } from "../util/Scripting";
-import { LabelBox } from "../views/nodes/LabelBox";
-import { SliderBox } from "../views/nodes/SliderBox";
-import { FontIconBox } from "../views/nodes/FontIconBox";
-import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
-import { PresBox } from "../views/nodes/PresBox";
-import { ComputedField, ScriptField } from "../../fields/ScriptField";
-import { ProxyField } from "../../fields/Proxy";
+import { UndoManager } from "../util/UndoManager";
import { DocumentType } from "./DocumentTypes";
-import { RecommendationsBox } from "../views/RecommendationsBox";
-import { PresElementBox } from "../views/presentationview/PresElementBox";
-import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
-import { QueryBox } from "../views/nodes/QueryBox";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { ContextMenu } from "../views/ContextMenu";
+import { ContextMenuProps } from "../views/ContextMenuItem";
+import { ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from "../views/InkingStroke";
+import { AudioBox } from "../views/nodes/AudioBox";
import { ColorBox } from "../views/nodes/ColorBox";
+import { ComparisonBox } from "../views/nodes/ComparisonBox";
import { DocHolderBox } from "../views/nodes/DocHolderBox";
import { InkingStroke, ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, ActiveDash } from "../views/InkingStroke";
import { InkField } from "../../fields/InkField";
@@ -44,11 +35,26 @@ import { extname } from "path";
import { MessageStore } from "../../server/Message";
import { ContextMenuProps } from "../views/ContextMenuItem";
import { ContextMenu } from "../views/ContextMenu";
+import { FontIconBox } from "../views/nodes/FontIconBox";
+import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
+import { ImageBox } from "../views/nodes/ImageBox";
+import { KeyValueBox } from "../views/nodes/KeyValueBox";
+import { LabelBox } from "../views/nodes/LabelBox";
import { LinkBox } from "../views/nodes/LinkBox";
+import { PDFBox } from "../views/nodes/PDFBox";
+import { PresBox } from "../views/nodes/PresBox";
+import { QueryBox } from "../views/nodes/QueryBox";
import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
-import { ComparisonBox } from "../views/nodes/ComparisonBox";
-import { runInAction } from "mobx";
-import { UndoManager } from "../util/UndoManager";
+import { ScriptingBox } from "../views/nodes/ScriptingBox";
+import { SliderBox } from "../views/nodes/SliderBox";
+import { VideoBox } from "../views/nodes/VideoBox";
+import { WebBox } from "../views/nodes/WebBox";
+import { PresElementBox } from "../views/presentationview/PresElementBox";
+import { RecommendationsBox } from "../views/RecommendationsBox";
+import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
+import { YoutubeBox } from "../apis/youtube/YoutubeBox";
+import { DocumentManager } from "../util/DocumentManager";
+import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox";
const path = require('path');
export interface DocumentOptions {
@@ -162,6 +168,7 @@ export interface DocumentOptions {
targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
searchFileTypes?: List<string>; // file types allowed in a search query
strokeWidth?: number;
+ stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere
treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view
treeViewHideTitle?: boolean; // whether to hide the title of a tree view
treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
@@ -262,6 +269,11 @@ export namespace Docs {
layout: { view: EmptyBox, dataField: defaultDataKey },
options: { childDropAction: "alias", title: "Global Link Database" }
}],
+ [DocumentType.SCRIPTDB, {
+ data: new List<Doc>(),
+ layout: { view: EmptyBox, dataField: defaultDataKey },
+ options: { childDropAction: "alias", title: "Global Script Database" }
+ }],
[DocumentType.SCRIPTING, {
layout: { view: ScriptingBox, dataField: defaultDataKey }
}],
@@ -361,6 +373,13 @@ export namespace Docs {
}
/**
+ * A collection of all scripts in the database
+ */
+ export function MainScriptDocument() {
+ return Prototypes.get(DocumentType.SCRIPTDB);
+ }
+
+ /**
* This is a convenience method that is used to initialize
* prototype documents for the first time.
*
@@ -478,7 +497,7 @@ export namespace Docs {
Scripting.addGlobal(Buxton);
- const delegateKeys = ["x", "y", "layoutKey", "dropAction", "childDropAction", "isLinkButton", "isBackground", "removeDropProperties", "treeViewOpen"];
+ const delegateKeys = ["x", "y", "layoutKey", "dropAction", "lockedPosiiton", "childDropAction", "isLinkButton", "isBackground", "removeDropProperties", "treeViewOpen"];
/**
* This function receives the relevant document prototype and uses
@@ -606,13 +625,15 @@ export namespace Docs {
selection: { type: "text", anchor: 1, head: 1 },
storedMarks: []
};
-
const field = text ? new RichTextField(JSON.stringify(rtf), text) : undefined;
return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
}
export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
- const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { isLinkButton: true, treeViewHideTitle: true, treeViewOpen: false, removeDropProperties: new List(["isBackground", "isLinkButton"]), ...options });
+ const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, {
+ isLinkButton: true, treeViewHideTitle: true, treeViewOpen: false,
+ removeDropProperties: new List(["isBackground", "isLinkButton"]), ...options
+ }, id);
const linkDocProto = Doc.GetProto(doc);
linkDocProto.anchor1 = source.doc;
linkDocProto.anchor2 = target.doc;
@@ -649,6 +670,7 @@ export namespace Docs {
I._backgroundColor = "transparent";
I._width = options._width;
I._height = options._height;
+ I.author = Doc.CurrentUserEmail;
I.data = new InkField(points);
return I;
// return I;
@@ -700,7 +722,7 @@ export namespace Docs {
}
export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List(schemaColumns), ...options, _viewType: CollectionViewType.Schema });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List(schemaColumns.length ? schemaColumns : [new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Schema });
}
export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -728,6 +750,11 @@ export namespace Docs {
}
export function ButtonDocument(options?: DocumentOptions) {
+ // const btn = InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), "onClick-rawScript": "-script-" });
+ // btn.layoutKey = "layout_onClick";
+ // btn.height = 250;
+ // btn.width = 200;
+ // btn.layout_onClick = ScriptingBox.LayoutString("onClick");
return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), "onClick-rawScript": "-script-" });
}
@@ -827,8 +854,9 @@ export namespace DocUtils {
if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
if (target.doc === Doc.UserDoc()) return undefined;
- const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship }, id);
- Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1.title +" (" + (self.linkRelationship||"to") +") " + self.anchor2.title');
+ const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView" }, id);
+ linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null);
+ Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") " + self.anchor2?.title');
Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 324b08603..7cc410a1c 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -21,6 +21,7 @@ import { MainView } from "../views/MainView";
import { DocumentType } from "../documents/DocumentTypes";
import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
+import { LabelBox } from "../views/nodes/LabelBox";
export class CurrentUserUtils {
private static curr_id: string;
@@ -86,6 +87,52 @@ export class CurrentUserUtils {
});
}
+ if (doc["template-button-link"] === undefined) {
+ const linkTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created
+ Doc.GetProto(linkTemplate).layout =
+ "<div>" +
+ " <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`lightBlue`}' fieldKey={'header'}/>" +
+ " <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
+ "</div>";
+ linkTemplate.isTemplateDoc = makeTemplate(linkTemplate, true, "linkView");
+
+ const rtf2 = {
+ doc: {
+ type: "doc", content: [
+ {
+ type: "paragraph",
+ content: [{
+ type: "dashField",
+ attrs: {
+ fieldKey: "src",
+ hideKey: false
+ }
+ }]
+ },
+ { type: "paragraph" },
+ {
+ type: "paragraph",
+ content: [{
+ type: "dashField",
+ attrs: {
+ fieldKey: "dst",
+ hideKey: false
+ }
+ }]
+ }]
+ },
+ selection: { type: "text", anchor: 1, head: 1 },
+ storedMarks: []
+ };
+ linkTemplate.header = new RichTextField(JSON.stringify(rtf2), "");
+
+ doc["template-button-link"] = CurrentUserUtils.ficon({
+ onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'),
+ dragFactory: new PrefetchProxy(linkTemplate) as any as Doc,
+ removeDropProperties: new List<string>(["dropAction"]), title: "link view", icon: "window-maximize"
+ });
+ }
+
if (doc["template-button-switch"] === undefined) {
const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create;
@@ -168,17 +215,21 @@ export class CurrentUserUtils {
});
}
+ const requiredTypes = [
+ doc["template-button-slides"] as Doc,
+ doc["template-button-description"] as Doc,
+ doc["template-button-query"] as Doc,
+ doc["template-button-detail"] as Doc,
+ doc["template-button-link"] as Doc,
+ doc["template-button-switch"] as Doc];
if (doc["template-buttons"] === undefined) {
- doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc,
- doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc], {
+ doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, {
title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title",
_autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
}));
} else {
const curButnTypes = Cast(doc["template-buttons"], Doc, null);
- const requiredTypes = [doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc,
- doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc];
DocListCastAsync(curButnTypes.data).then(async curBtns => {
await Promise.all(curBtns!);
requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype));
@@ -262,9 +313,17 @@ export class CurrentUserUtils {
iconView.isTemplateDoc = makeTemplate(iconView);
doc["template-icon-view"] = new PrefetchProxy(iconView);
}
+ if (doc["template-icon-view-pdf"] === undefined) {
+ const iconPdfView = Docs.Create.LabelDocument({
+ title: "icon_" + DocumentType.PDF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimGray",
+ _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+ });
+ iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF);
+ doc["template-icon-view-pdf"] = new PrefetchProxy(iconPdfView);
+ }
if (doc["template-icon-view-rtf"] === undefined) {
const iconRtfView = Docs.Create.LabelDocument({
- title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset",
+ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"),
_width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
});
iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
@@ -284,11 +343,11 @@ export class CurrentUserUtils {
}
if (doc["template-icons"] === undefined) {
doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc], { title: "icon templates", _height: 75 }));
+ doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75 }));
} else {
const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
+ doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc];
DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
await Promise.all(curIcons!);
requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
@@ -481,12 +540,17 @@ export class CurrentUserUtils {
}
if (doc["tabs-button-tools"] === undefined) {
+ const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], {
+ _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true
+ })) as any as Doc;
doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({
_width: 35, _height: 25, title: "Tools", _fontSize: 10,
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
- sourcePanel: new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], {
- _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true
- })) as any as Doc,
+ sourcePanel: toolsStack,
+ onDragStart: ScriptField.MakeFunction('getAlias(this.dragFactory, true)'),
+ dragFactory: toolsStack,
+ removeDropProperties: new List<string>(["lockedPosition"]),
+ stayInCollection: true,
targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel"),
}));
@@ -510,8 +574,9 @@ export class CurrentUserUtils {
}
static setupCatalog(doc: Doc) {
if (doc.myCatalog === undefined) {
- doc.myCatalog = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "CATALOG", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true,
+ doc.myCatalog = new PrefetchProxy(Docs.Create.SchemaDocument([], [], {
+ title: "CATALOG", _height: 1000, _fitWidth: true, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false,
+ childDropAction: "alias", targetDropAction: "same", treeViewExpandedView: "layout", stayInCollection: true,
}));
}
return doc.myCatalog as Doc;
@@ -520,7 +585,7 @@ export class CurrentUserUtils {
// setup Recently Closed library item
if (doc.myRecentlyClosed === undefined) {
doc.myRecentlyClosed = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true,
+ title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, stayInCollection: true,
}));
}
// this is equivalent to using PrefetchProxies to make sure the recentlyClosed doc is ready
@@ -538,12 +603,18 @@ export class CurrentUserUtils {
const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc);
if (doc["tabs-button-library"] === undefined) {
+ const libraryStack = new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
+ title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
+ lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same"
+ })) as any as Doc;
doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 50, _height: 25, title: "Library", _fontSize: 10,
+ _width: 50, _height: 25, title: "Library", _fontSize: 10, targetDropAction: "same",
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
- sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
- title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true
- })) as any as Doc,
+ sourcePanel: libraryStack,
+ onDragStart: ScriptField.MakeFunction('getAlias(this.dragFactory, true)'),
+ dragFactory: libraryStack,
+ removeDropProperties: new List<string>(["lockedPosition"]),
+ stayInCollection: true,
targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
}));
@@ -686,6 +757,7 @@ export class CurrentUserUtils {
}
static async updateUserDocument(doc: Doc) {
+ doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
doc.title = Doc.CurrentUserEmail;
doc.activeInkPen = doc;
doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
@@ -707,6 +779,7 @@ export class CurrentUserUtils {
this.setupDefaultPresentation(doc); // presentation that's initially triggered
await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels
doc.globalLinkDatabase = Docs.Prototypes.MainLinkDocument();
+ doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
@@ -739,6 +812,9 @@ export class CurrentUserUtils {
}
}
-Scripting.addGlobal(function setupMobileInkingDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileInkingDoc(userDoc); });
-Scripting.addGlobal(function setupMobileUploadDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileUploadDoc(userDoc); });
-Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); }); \ No newline at end of file
+Scripting.addGlobal(function setupMobileInkingDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileInkingDoc(userDoc); },
+ "initializes the Mobile inking document", "(userDoc: Doc)");
+Scripting.addGlobal(function setupMobileUploadDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileUploadDoc(userDoc); },
+ "initializes the Mobile upload document", "(userDoc: Doc)");
+Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); },
+ "creates a new workspace when called"); \ No newline at end of file
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index e46225b4a..d8a5657c3 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -144,7 +144,7 @@ export namespace DictationManager {
recognizer.start();
return new Promise<string>((resolve, reject) => {
- recognizer.onerror = (e: SpeechRecognitionError) => {
+ recognizer.onerror = (e: any) => { // e is SpeechRecognitionError but where is that defined?
if (!(indefinite && e.error === "no-speech")) {
recognizer.stop();
reject(e);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index f1afaf734..26e7250f4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -13,7 +13,7 @@ import * as globalCssVariables from "../views/globalCssVariables.scss";
import { UndoManager } from "./UndoManager";
import { SnappingManager } from "./SnappingManager";
-export type dropActionType = "alias" | "copy" | "move" | undefined; // undefined = move
+export type dropActionType = "alias" | "copy" | "move" | "same" | undefined; // undefined = move
export function SetupDrag(
_reference: React.RefObject<HTMLElement>,
docFunc: () => Doc | Promise<Doc> | undefined,
@@ -37,7 +37,7 @@ export function SetupDrag(
dragData.treeViewId = treeViewId;
dragData.dontHideOnDrop = dontHideOnDrop;
DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
- dragStarted && dragStarted();
+ dragStarted?.();
}
};
const onRowUp = (): void => {
@@ -122,7 +122,7 @@ export namespace DragManager {
export class DocumentDragData {
constructor(dragDoc: Doc[]) {
this.draggedDocuments = dragDoc;
- this.droppedDocuments = dragDoc;
+ this.droppedDocuments = [];
this.offset = [0, 0];
}
draggedDocuments: Doc[];
@@ -134,7 +134,6 @@ export namespace DragManager {
dropAction: dropActionType;
removeDropProperties?: string[];
userDropAction: dropActionType;
- embedDoc?: boolean;
moveDocument?: MoveFunction;
removeDocument?: RemoveFunction;
isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts
@@ -209,15 +208,19 @@ export namespace DragManager {
};
const batch = UndoManager.StartBatch("dragging");
const finishDrag = (e: DragCompleteEvent) => {
- e.docDragData && (e.docDragData.droppedDocuments =
- dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
- dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) :
- dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeDelegate(d) : d)
- );
- e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) =>
- (dragData?.removeDropProperties || []).concat(Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), [])).map(prop => drop[prop] = undefined)
- );
- batch.end();
+ const docDragData = e.docDragData;
+ if (docDragData && !docDragData.droppedDocuments.length) {
+ docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
+ docDragData.droppedDocuments =
+ dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
+ docDragData.dropAction === "alias" ? Doc.MakeAlias(d) :
+ docDragData.dropAction === "copy" ? Doc.MakeDelegate(d) : d);
+ docDragData.dropAction !== "same" && docDragData.droppedDocuments.forEach((drop: Doc, i: number) =>
+ (dragData?.removeDropProperties || []).concat(Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), [])).map(prop => drop[prop] = undefined)
+ );
+ batch.end();
+ }
+ return e;
};
dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded
StartDrag(eles, dragData, downX, downY, options, finishDrag);
@@ -231,6 +234,7 @@ export namespace DragManager {
initialize?.(bd);
Doc.GetProto(bd)["onClick-paramFieldKeys"] = new List<string>(params);
e.docDragData && (e.docDragData.droppedDocuments = [bd]);
+ return e;
};
StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
}
@@ -319,12 +323,12 @@ export namespace DragManager {
dragDiv = document.createElement("div");
dragDiv.className = "dragManager-dragDiv";
dragDiv.style.pointerEvents = "none";
- dragLabel = document.createElement("div") as HTMLDivElement;
+ dragLabel = document.createElement("div");
dragLabel.className = "dragManager-dragLabel";
dragLabel.style.zIndex = "100001";
dragLabel.style.fontSize = "10";
dragLabel.style.position = "absolute";
- dragLabel.innerText = "press 'a' to embed on drop";
+ // dragLabel.innerText = "press 'a' to embed on drop"; // bcz: need to move this to a status bar
dragDiv.appendChild(dragLabel);
DragManager.Root().appendChild(dragDiv);
}
@@ -406,31 +410,28 @@ export namespace DragManager {
const yFromTop = downY - elesCont.top;
const xFromRight = elesCont.right - downX;
const yFromBottom = elesCont.bottom - downY;
- let alias = "alias";
const moveHandler = (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (dragData instanceof DocumentDragData) {
dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined;
}
- if (e)
- if (e.shiftKey && dragData.droppedDocuments.length === 1) {
- !dragData.dropAction && (dragData.dropAction = alias);
- if (dragData.dropAction === "move") {
- dragData.removeDocument?.(dragData.draggedDocuments[0]);
- }
- AbortDrag();
- finishDrag?.(new DragCompleteEvent(true, dragData));
- DragManager.StartWindowDrag?.({
- pageX: e.pageX,
- pageY: e.pageY,
- preventDefault: emptyFunction,
- button: 0
- }, dragData.droppedDocuments);
+ if (e?.shiftKey && dragData.draggedDocuments.length === 1) {
+ dragData.dropAction = dragData.userDropAction || "same";
+ if (dragData.dropAction === "move") {
+ dragData.removeDocument?.(dragData.draggedDocuments[0]);
}
+ AbortDrag();
+ finishDrag?.(new DragCompleteEvent(true, dragData));
+ DragManager.StartWindowDrag?.({
+ pageX: e.pageX,
+ pageY: e.pageY,
+ preventDefault: emptyFunction,
+ button: 0
+ }, dragData.droppedDocuments);
+ }
const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
- alias = "move";
const moveX = thisX - lastX;
const moveY = thisY - lastY;
lastX = thisX;
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 752c1cfc5..f9837298d 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -54,10 +54,12 @@ export function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string
return any;
}
export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
- data && data.draggedDocuments.map((doc, i) => {
+ data?.draggedDocuments.map((doc, i) => {
let dbox = doc;
// bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
- if (!doc.onDragStart && !doc.isButtonBar) {
+ if (doc.type === DocumentType.FONTICON) {
+ dbox = Doc.MakeAlias(doc);
+ } else if (!doc.onDragStart && !doc.isButtonBar) {
const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
if (layoutDoc.type !== DocumentType.FONTICON) {
!layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
@@ -76,4 +78,5 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
data.droppedDocuments[i] = dbox;
});
}
-Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); }); \ No newline at end of file
+Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); },
+ "converts the dropped data to buttons", "(dragData: any)"); \ No newline at end of file
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 25c556697..af6c57e68 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -1,33 +1,33 @@
-import "fs";
-import React = require("react");
-import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc";
-import { action, observable, runInAction, computed, reaction, IReactionDisposer } from "mobx";
-import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
-import Measure, { ContentRect } from "react-measure";
import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCloudUploadAlt, faPlus, faTag } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faTag, faPlus, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
-import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
+import { BatchedArray } from "array-batcher";
+import "fs";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry";
-import { Utils } from "../../../Utils";
-import { DocumentManager } from "../DocumentManager";
+import * as path from 'path';
+import Measure, { ContentRect } from "react-measure";
+import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
-import { Cast, BoolCast, NumCast } from "../../../fields/Types";
import { listSpec } from "../../../fields/Schema";
-import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import "./DirectoryImportBox.scss";
-import { Networking } from "../../Network";
-import { BatchedArray } from "array-batcher";
-import * as path from 'path';
+import { BoolCast, Cast, NumCast } from "../../../fields/Types";
import { AcceptibleMedia, Upload } from "../../../server/SharedMediaTypes";
+import { Utils } from "../../../Utils";
+import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
+import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
+import { Networking } from "../../Network";
+import { FieldView, FieldViewProps } from "../../views/nodes/FieldView";
+import { DocumentManager } from "../DocumentManager";
+import "./DirectoryImportBox.scss";
+import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry";
+import React = require("react");
const unsupported = ["text/html", "text/plain"];
@observer
-export default class DirectoryImportBox extends React.Component<FieldViewProps> {
+export class DirectoryImportBox extends React.Component<FieldViewProps> {
private selector = React.createRef<HTMLInputElement>();
@observable private top = 0;
@observable private left = 0;
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 68dcbebe3..9a9bb3d42 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -91,86 +91,66 @@ export namespace InteractionUtils {
return myTouches;
}
-
- export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: string, bezier: string, fill: string, arrowStart: string, arrowEnd: string, dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean) {
- var pts = "";
- if (shape) {
- //if any of the shape are true
- const shapePts = makePolygon(shape, points);
- pts = shapePts.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X * scalex - left * scalex},${pt.Y * scaley - top * scaley} `, "");
+ export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
+ color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
+ dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean) {
+ let pts: { X: number; Y: number; }[] = [];
+ if (shape) { //if any of the shape are true
+ pts = makePolygon(shape, points);
}
else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
//pointer is up (first and last points are the same)
- const newPoints: number[][] = [];
- const newPts: { X: number; Y: number; }[] = [];
- //convert to [][] for fitcurve module
- for (var i = 0; i < points.length - 2; i++) {
- newPoints.push([points[i].X, points[i].Y]);
- }
+ points.pop();
+ const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
+
const bezierCurves = fitCurve(newPoints, parseInt(bezier));
- for (var i = 0; i < bezierCurves.length; i++) {
+ for (const curve of bezierCurves) {
for (var t = 0; t < 1; t += 0.01) {
- const point = beziercurve(t, bezierCurves[i]);
- newPts.push({ X: point[0], Y: point[1] });
+ const point = beziercurve(t, curve);
+ pts.push({ X: point[0], Y: point[1] });
}
}
- pts = newPts.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X * scalex - left * scalex},${pt.Y * scaley - top * scaley} `, "");
} else {
- //in the middle of drawing
- pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X * scalex - left * scalex},${pt.Y * scaley - top * scaley} `, "");
+ pts = points;
}
+ const strpts = pts.reduce((acc: string, pt: { X: number, Y: number }) => acc +
+ `${(pt.X - left - width / 2) * scalex + width / 2},
+ ${(pt.Y - top - width / 2) * scaley + width / 2} `, "");
const dashArray = String(Number(width) * Number(dash));
- return (
- <svg>
- <defs>
- <marker id="dot" orient="auto" overflow="visible">
- <circle r={0.5} fill="context-stroke" />
- </marker>
- <marker id="arrowHead" orient="auto" overflow="visible" refX="3" refY="1" markerWidth="10" markerHeight="7">
- {/* <rect width={strokeWidth} height={strokeWidth} transform='rotate(45)' fill="dodgerblue" /> */}
- <polygon points="3 0, 3 2, 0 1" fill="black" />
- </marker>
- <marker id="arrowEnd" orient="auto" overflow="visible" refX="0" refY="1" markerWidth="10" markerHeight="7">
- {/* <rect width={strokeWidth} height={strokeWidth} transform='rotate(45)' fill="dodgerblue" /> */}
- <polygon points="0 0, 3 1, 0 2" fill="black" />
- </marker>
-
- </defs>
- {/* <polyline
- points={pts}
- style={{
- fill: fill,
- pointerEvents: pevents as any,
- stroke: drawHalo ? "grey" : "transparent",
- strokeWidth: parseInt(width) * 4,
- strokeLinejoin: "round",
- strokeLinecap: "round",
- strokeDasharray: dashArray
- }}
- markerStart={arrowStart}
- markerEnd={arrowEnd}
- /> */}
+ return (<svg>
+ <defs>
+ <marker id="dot" orient="auto" overflow="visible">
+ <circle r={0.5} fill="context-stroke" />
+ </marker>
+ <marker id="arrowHead" orient="auto" overflow="visible" refX="3" refY="1" markerWidth="10" markerHeight="7">
+ {/* <rect width={strokeWidth} height={strokeWidth} transform='rotate(45)' fill="dodgerblue" /> */}
+ <polygon points="3 0, 3 2, 0 1" fill={fill} />
+ </marker>
+ <marker id="arrowEnd" orient="auto" overflow="visible" refX="0" refY="1" markerWidth="10" markerHeight="7">
+ {/* <rect width={strokeWidth} height={strokeWidth} transform='rotate(45)' fill="dodgerblue" /> */}
+ <polygon points="0 0, 3 1, 0 2" fill={fill} />
+ </marker>
- <polyline
- points={pts}
- style={{
- // filter: drawHalo ? "url(#dangerShine)" : undefined,
- fill: fill,
- pointerEvents: pevents as any,
- stroke: color ?? "rgb(0, 0, 0)",
- strokeWidth: parseInt(width),
- strokeLinejoin: "round",
- strokeLinecap: "round",
- strokeDasharray: dashArray
- }}
- markerStart={arrowStart}
- markerEnd={arrowEnd}
- />
+ </defs>
- </svg>
+ <polyline
+ points={strpts}
+ style={{
+ filter: drawHalo ? "url(#dangerShine)" : undefined,
+ fill: "none",
+ opacity: strokeWidth !== width ? 0.5 : undefined,
+ pointerEvents: pevents as any,
+ stroke: color ?? "rgb(0, 0, 0)",
+ strokeWidth: strokeWidth,
+ strokeLinejoin: "round",
+ strokeLinecap: "round"
+ }}
+ markerStart={arrowStart}
+ markerEnd={arrowEnd}
+ />
- );
+ </svg>);
}
// export function makeArrow() {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 95528e25a..47b2541bd 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -205,4 +205,5 @@ export class LinkManager {
}
}
-Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }); \ No newline at end of file
+Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
+ "creates a link to inputted document", "(doc: any)"); \ No newline at end of file
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
new file mode 100644
index 000000000..785e63d9a
--- /dev/null
+++ b/src/client/util/ScriptManager.ts
@@ -0,0 +1,104 @@
+import { Doc, DocListCast } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { Scripting } from "./Scripting";
+import { StrCast, Cast } from "../../fields/Types";
+import { listSpec } from "../../fields/Schema";
+import { Docs } from "../documents/Documents";
+
+export class ScriptManager {
+
+ static _initialized = false;
+ private static _instance: ScriptManager;
+ public static get Instance(): ScriptManager {
+ return this._instance || (this._instance = new this());
+ }
+ private constructor() {
+ if (!ScriptManager._initialized) {
+ ScriptManager._initialized = true;
+ this.getAllScripts().forEach(scriptDoc => ScriptManager.addScriptToGlobals(scriptDoc));
+ }
+ }
+
+ public get ScriptManagerDoc(): Doc | undefined {
+ return Docs.Prototypes.MainScriptDocument();
+ }
+ public getAllScripts(): Doc[] {
+ const sdoc = ScriptManager.Instance.ScriptManagerDoc;
+ if (sdoc) {
+ const docs = DocListCast(sdoc.data);
+ return docs;
+ }
+ return [];
+ }
+
+ public addScript(scriptDoc: Doc): boolean {
+
+ console.log("in add script method");
+
+ const scriptList = this.getAllScripts();
+ scriptList.push(scriptDoc);
+ if (ScriptManager.Instance.ScriptManagerDoc) {
+ ScriptManager.Instance.ScriptManagerDoc.data = new List<Doc>(scriptList);
+ ScriptManager.addScriptToGlobals(scriptDoc);
+ console.log("script added");
+ return true;
+ }
+ return false;
+ }
+
+ public deleteScript(scriptDoc: Doc): boolean {
+
+ console.log("in delete script method");
+
+ if (scriptDoc.name) {
+ Scripting.removeGlobal(StrCast(scriptDoc.name));
+ }
+ const scriptList = this.getAllScripts();
+ const index = scriptList.indexOf(scriptDoc);
+ if (index > -1) {
+ scriptList.splice(index, 1);
+ if (ScriptManager.Instance.ScriptManagerDoc) {
+ ScriptManager.Instance.ScriptManagerDoc.data = new List<Doc>(scriptList);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static addScriptToGlobals(scriptDoc: Doc): void {
+
+ Scripting.removeGlobal(StrCast(scriptDoc.name));
+
+ const params = Cast(scriptDoc["data-params"], listSpec("string"), []);
+ console.log(params);
+ const paramNames = params.reduce((o: string, p: string) => {
+ if (params.indexOf(p) === params.length - 1) {
+ o = o + p.split(":")[0].trim();
+ } else {
+ o = o + p.split(":")[0].trim() + ",";
+ }
+ return o;
+ }, "" as string);
+
+ const f = new Function(paramNames, StrCast(scriptDoc.script));
+
+ console.log(scriptDoc.script);
+
+ Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
+
+ let parameters = "(";
+ params.forEach((element: string, i: number) => {
+ if (i === params.length - 1) {
+ parameters = parameters + element + ")";
+ } else {
+ parameters = parameters + element + ", ";
+ }
+ });
+
+ if (parameters === "(") {
+ Scripting.addGlobal(f, StrCast(scriptDoc.description));
+ } else {
+ Scripting.addGlobal(f, StrCast(scriptDoc.description), parameters);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index ab577315c..e6cf50de3 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -10,6 +10,8 @@ export { ts };
// @ts-ignore
import * as typescriptlib from '!!raw-loader!./type_decls.d';
import { Doc, Field } from '../../fields/Doc';
+import { Cast } from "../../fields/Types";
+import { listSpec } from "../../fields/Schema";
export interface ScriptSucccess {
success: true;
@@ -49,19 +51,34 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is
export namespace Scripting {
export function addGlobal(global: { name: string }): void;
export function addGlobal(name: string, global: any): void;
- export function addGlobal(nameOrGlobal: any, global?: any) {
- let n: string;
+
+ export function addGlobal(global: { name: string }, decription?: string, params?: string): void;
+
+ export function addGlobal(first: any, second?: any, third?: string) {
+ let n: any;
let obj: any;
- if (global !== undefined && typeof nameOrGlobal === "string") {
- n = nameOrGlobal;
- obj = global;
- } else if (nameOrGlobal && typeof nameOrGlobal.name === "string") {
- n = nameOrGlobal.name;
- obj = nameOrGlobal;
+
+ if (second !== undefined) {
+ if (typeof first === "string") {
+ n = first;
+ obj = second;
+ } else {
+ obj = first;
+ n = first.name;
+ _scriptingDescriptions[n] = second;
+ if (third !== undefined) {
+ _scriptingParams[n] = third;
+ }
+ }
+ } else if (first && typeof first.name === "string") {
+ n = first.name;
+ obj = first;
} else {
throw new Error("Must either register an object with a name, or give a name and an object");
}
- if (_scriptingGlobals.hasOwnProperty(n)) {
+ if (n === undefined || n === "undefined") {
+ return false;
+ } else if (_scriptingGlobals.hasOwnProperty(n)) {
throw new Error(`Global with name ${n} is already registered, choose another name`);
}
_scriptingGlobals[n] = obj;
@@ -75,6 +92,20 @@ export namespace Scripting {
scriptingGlobals = globals;
}
+ export function removeGlobal(name: string) {
+ if (getGlobals().includes(name)) {
+ delete _scriptingGlobals[name];
+ if (_scriptingDescriptions[name]){
+ delete _scriptingDescriptions[name];
+ }
+ if (_scriptingParams[name]){
+ delete _scriptingParams[name];
+ }
+ return true;
+ }
+ return false;
+ }
+
export function resetScriptingGlobals() {
scriptingGlobals = _scriptingGlobals;
}
@@ -85,7 +116,19 @@ export namespace Scripting {
}
export function getGlobals() {
- return Object.keys(scriptingGlobals);
+ return Object.keys(_scriptingGlobals);
+ }
+
+ export function getGlobalObj() {
+ return _scriptingGlobals;
+ }
+
+ export function getDescriptions(){
+ return _scriptingDescriptions;
+ }
+
+ export function getParameters(){
+ return _scriptingParams;
}
}
@@ -95,6 +138,8 @@ export function scriptingGlobal(constructor: { new(...args: any[]): any }) {
const _scriptingGlobals: { [name: string]: any } = {};
let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
+const _scriptingDescriptions: { [name: string]: any } = {};
+const _scriptingParams: { [name: string]: any } = {};
function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error);
@@ -133,6 +178,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
}
return { success: true, result };
} catch (error) {
+
if (batch) {
batch.end();
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 0e15197c4..9888cce48 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -10,6 +10,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Networking } from "../Network";
import { CurrentUserUtils } from "./CurrentUserUtils";
import { Utils } from "../../Utils";
+import { Doc } from "../../fields/Doc";
library.add(fa.faWindowClose);
@@ -78,6 +79,10 @@ export default class SettingsManager extends React.Component<{}> {
this.errorText = "";
this.successText = "";
}
+ @action
+ noviceToggle = (event: any) => {
+ Doc.UserDoc().noviceMode = !Doc.UserDoc().noviceMode;
+ }
private get settingsInterface() {
return (
@@ -91,7 +96,7 @@ export default class SettingsManager extends React.Component<{}> {
<div className="settings-body">
<div className="settings-type">
<button onClick={this.onClick} value="password">reset password</button>
- <button onClick={this.onClick} value="data">reset data</button>
+ <button onClick={this.noviceToggle} value="data">{`toggle ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button>
<button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
</button>
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 314b52bf3..c7b7bb215 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -78,10 +78,12 @@ export namespace UndoManager {
let currentBatch: UndoBatch | undefined;
let batchCounter = 0;
let undoing = false;
+ let tempEvents: UndoEvent[] | undefined = undefined;
export function AddEvent(event: UndoEvent): void {
if (currentBatch && batchCounter && !undoing) {
currentBatch.push(event);
+ tempEvents?.push(event);
}
}
@@ -135,7 +137,7 @@ export namespace UndoManager {
const EndBatch = action((cancel: boolean = false) => {
batchCounter--;
- if (batchCounter === 0 && currentBatch && currentBatch.length) {
+ if (batchCounter === 0 && currentBatch?.length) {
if (!cancel) {
undoStack.push(currentBatch);
}
@@ -144,6 +146,13 @@ export namespace UndoManager {
}
});
+ export function ClearTempBatch() {
+ tempEvents = undefined;
+ }
+ export function RunInTempBatch<T>(fn: () => T) {
+ tempEvents = [];
+ return runInAction(fn);
+ }
//TODO Make this return the return value
export function RunInBatch<T>(fn: () => T, batchName: string) {
const batch = StartBatch(batchName);
@@ -153,7 +162,16 @@ export namespace UndoManager {
batch.end();
}
}
-
+ export const UndoTempBatch = action(() => {
+ if (tempEvents) {
+ undoing = true;
+ for (let i = tempEvents.length - 1; i >= 0; i--) {
+ tempEvents[i].undo();
+ }
+ undoing = false;
+ }
+ tempEvents = undefined;
+ });
export const Undo = action(() => {
if (undoStack.length === 0) {
return;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 5b66b63ed..941d7b44a 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -236,7 +236,7 @@ export class ContextMenu extends React.Component {
<span className="icon-background">
<FontAwesomeIcon icon="search" size="lg" />
</span>
- <input className="contextMenu-item contextMenu-description search" type="text" placeholder="Search . . ." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
+ <input className="contextMenu-item contextMenu-description search" type="text" placeholder="Search Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
</span>
{this.menuItems}
</>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 3af570f1e..9b9a28f0f 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, DocListCast } from '../../fields/Doc';
+import { Doc, Opt, DataSym, DocListCast, AclSym, AclReadonly, AclAddonly } from '../../fields/Doc';
import { Touchable } from './Touchable';
import { computed, action, observable } from 'mobx';
import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -138,9 +138,15 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
const docList = DocListCast(targetDataDoc[this.annotationKey]);
const added = docs.filter(d => !docList.includes(d));
if (added.length) {
- added.map(doc => doc.context = this.props.Document);
- targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
- targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ if (this.dataDoc[AclSym] === AclReadonly) {
+ return false;
+ } else if (this.dataDoc[AclSym] === AclAddonly) {
+ added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
+ } else {
+ added.map(doc => doc.context = this.props.Document);
+ targetDataDoc[this.annotationKey] = new List<Doc>([...docList, ...added]);
+ targetDataDoc[this.annotationKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
}
return true;
}
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a35a8869c..62a95116f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -281,7 +281,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const dragDocView = this.view0!;
const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- dragData.embedDoc = true;
dragData.dropAction = "alias";
DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
offsetX: dragData.offset[0],
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index cf5871bda..c8347165d 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -190,6 +190,20 @@ $linkGap : 3px;
}
+.documentDecorations-iconifyButton {
+ opacity: 1;
+ grid-column-start: 4;
+ grid-column-end: 6;
+ pointer-events: all;
+ text-align: center;
+ left: -20px;
+ top: -2px;
+ cursor: pointer;
+ position: absolute;
+ background: transparent;
+ width: 20px;
+}
+
.documentDecorations-closeButton {
opacity: 1;
grid-column-start: 4;
@@ -241,13 +255,15 @@ $linkGap : 3px;
}
.link-button-container {
- margin-top: $linkGap;
+ padding: $linkGap;
+ border-radius: 10px;
width: max-content;
height: auto;
display: flex;
flex-direction: row;
z-index: 998;
position: absolute;
+ background: $alt-accent;
}
.linkButtonWrapper {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8cb0c83ed..5a718d33f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -76,6 +76,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
get Bounds(): { x: number, y: number, b: number, r: number } {
return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
if (documentView.props.renderDepth === 0 ||
+ documentView.props.treeViewId ||
Doc.AreProtosEqual(documentView.props.Document, Doc.UserDoc())) {
return bounds;
}
@@ -180,7 +181,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
@undoBatch
@action
- onCloseClick = async (e: PointerEvent | undefined) => {
+ onCloseClick = async (e: React.MouseEvent|undefined) => {
if (!e?.button) {
const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc;
const selected = SelectionManager.SelectedDocuments().slice();
@@ -536,7 +537,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div className="documentDecorations-contextMenu" title="Show context menu" onPointerDown={this.onSettingsDown}>
<FontAwesomeIcon size="lg" icon="cog" />
</div>) : (
- <div className="documentDecorations-minimizeButton" title="Iconify" onPointerDown={this.onIconifyDown}>
+ <div className="documentDecorations-minimizeButton" title="Iconify" onClick={this.onCloseClick}>
{/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
<FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
</div>);
@@ -545,16 +546,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<>
<input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
onBlur={e => this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} />
- {minimal ? (null) : <div className="publishBox" title="make document referenceable by its title"
- onPointerDown={action(e => {
- if (!seldoc.props.Document.customTitle) {
- seldoc.props.Document.customTitle = true;
- StrCast(Doc.GetProto(seldoc.props.Document).title).startsWith("-") && (Doc.GetProto(seldoc.props.Document).title = StrCast(seldoc.props.Document.title).substring(1));
- this._accumulatedTitle = StrCast(seldoc.props.Document.title);
- }
- DocUtils.Publish(seldoc.props.Document, this._accumulatedTitle, seldoc.props.addDocument, seldoc.props.removeDocument);
- })}>
- <FontAwesomeIcon size="lg" color={SelectionManager.SelectedDocuments()[0].props.Document.title === SelectionManager.SelectedDocuments()[0].props.Document[Id] ? "green" : undefined} icon="sticky-note"></FontAwesomeIcon>
+ {minimal ? (null) : <div className="publishBox" // title="make document referenceable by its title"
+ // onPointerDown={action(e => {
+ // if (!seldoc.props.Document.customTitle) {
+ // seldoc.props.Document.customTitle = true;
+ // StrCast(Doc.GetProto(seldoc.props.Document).title).startsWith("-") && (Doc.GetProto(seldoc.props.Document).title = StrCast(seldoc.props.Document.title).substring(1));
+ // this._accumulatedTitle = StrCast(seldoc.props.Document.title);
+ // }
+ // DocUtils.Publish(seldoc.props.Document, this._accumulatedTitle, seldoc.props.addDocument, seldoc.props.removeDocument);
+ // })}
+ >
+ {/* <FontAwesomeIcon size="lg" color={SelectionManager.SelectedDocuments()[0].props.Document.title === SelectionManager.SelectedDocuments()[0].props.Document[Id] ? "green" : undefined} icon="sticky-note"></FontAwesomeIcon> */}
</div>}
</> :
<>
@@ -595,6 +597,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}}>
{maximizeIcon}
{titleArea}
+ <div className="documentDecorations-iconifyButton" title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} onPointerDown={this.onIconifyDown}>
+ {"_"}
+ </div>
<div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
{SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
</div>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index e0e205df9..ee3ce1cf3 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -5,6 +5,7 @@ import * as Autosuggest from 'react-autosuggest';
import { ObjectField } from '../../fields/ObjectField';
import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
import "./EditableView.scss";
+import { DragManager } from '../util/DragManager';
export interface EditableProps {
/**
@@ -48,6 +49,8 @@ export interface EditableProps {
HeadingObject?: SchemaHeaderField | undefined;
toggle?: () => void;
color?: string | undefined;
+ onDrop?: any;
+ placeholder?: string;
}
/**
@@ -66,14 +69,22 @@ export class EditableView extends React.Component<EditableProps> {
EditableView.loadId = "";
}
+ // @action
+ // componentDidUpdate(nextProps: EditableProps) {
+ // // this is done because when autosuggest is turned on, the suggestions are passed in as a prop,
+ // // so when the suggestions are passed in, and no editing prop is passed in, it used to set it
+ // // to false. this will no longer do so -syip
+ // console.log("props editing = " + nextProps.editing);
+ // if (nextProps.editing && nextProps.editing !== this._editing) {
+ // this._editing = nextProps.editing;
+ // EditableView.loadId = "";
+ // }
+ // }
+
@action
- componentDidUpdate(nextProps: EditableProps) {
- // this is done because when autosuggest is turned on, the suggestions are passed in as a prop,
- // so when the suggestions are passed in, and no editing prop is passed in, it used to set it
- // to false. this will no longer do so -syip
- if (nextProps.editing && nextProps.editing !== this._editing) {
- this._editing = nextProps.editing;
- EditableView.loadId = "";
+ componentDidMount() {
+ if (this._ref.current && this.props.onDrop) {
+ DragManager.MakeDropTarget(this._ref.current, this.props.onDrop.bind(this));
}
}
@@ -109,7 +120,7 @@ export class EditableView extends React.Component<EditableProps> {
if (this._ref.current && this.props.showMenuOnLoad) {
this.props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
} else {
- if (!this.props.onClick || !this.props.onClick(e)) {
+ if (!this.props.onClick?.(e)) {
this._editing = true;
this.props.isEditingCallback?.(true);
}
@@ -168,6 +179,7 @@ export class EditableView extends React.Component<EditableProps> {
onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true)}
onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
style={{ display: this.props.display, fontSize: this.props.fontSize }}
+ placeholder={this.props.placeholder}
/>;
} else {
this.props.autosuggestProps?.resetValue();
@@ -175,8 +187,9 @@ export class EditableView extends React.Component<EditableProps> {
<div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
ref={this._ref}
style={{ display: this.props.display, minHeight: "20px", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
- onClick={this.onClick}>
- <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents}</span>
+ onClick={this.onClick} placeholder={this.props.placeholder}>
+
+ <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
</div>
);
}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 01ef0b664..95dc2c7d4 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -8,7 +8,7 @@ import MobileInkOverlay from "../../mobile/MobileInkOverlay";
import MobileInterface from "../../mobile/MobileInterface";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { MobileInkOverlayContent } from "../../server/Message";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { DocServer } from "../DocServer";
import { DocUtils } from "../documents/Documents";
@@ -327,7 +327,7 @@ export default class GestureOverlay extends Touchable {
this._thumbY = thumb.clientY;
this._menuX = thumb.clientX + 50;
this._menuY = thumb.clientY;
- this._palette = <HorizontalPalette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
+ this._palette = <HorizontalPalette key="palette" x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
});
}
@@ -814,17 +814,18 @@ export default class GestureOverlay extends Touchable {
@computed get elements() {
const B = this.svgBounds;
+ const width = Number(ActiveInkWidth());
return [
this.props.children,
this._palette,
- [this._strokes.map(l => {
+ [this._strokes.map((l, i) => {
const b = this.getBounds(l);
- return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false)}
+ return <svg key={i} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
+ {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false)}
</svg>;
}),
- this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false)}
+ this._points.length <= 1 ? (null) : <svg key="svg" width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
+ {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false)}
</svg>]
];
}
@@ -855,6 +856,7 @@ export default class GestureOverlay extends Touchable {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
/>;
@@ -933,7 +935,7 @@ Scripting.addGlobal(function resetPen() {
SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)");
SetActiveInkWidth(GestureOverlay.Instance.SavedWidth ?? "2");
});
-});
+}, "resets the pen tool");
Scripting.addGlobal(function createText(text: any, x: any, y: any) {
GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text);
-}); \ No newline at end of file
+}, "creates a text document with inputted text and coordinates", "(text: any, x: any, y: any)"); \ No newline at end of file
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index a3b144055..c696625db 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -17,7 +17,6 @@ import { undoBatch, UndoManager } from "../util/UndoManager";
import { CollectionDockingView } from "./collections/CollectionDockingView";
import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView";
import { DocumentDecorations } from "./DocumentDecorations";
-import { InkingStroke } from "./InkingStroke";
import { MainView } from "./MainView";
import { DocumentView } from "./nodes/DocumentView";
@@ -75,7 +74,7 @@ export default class KeyManager {
case "a": DragManager.CanEmbed = true;
break;
case " ":
- MarqueeView.DragMarquee = !MarqueeView.DragMarquee;
+ // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
break;
case "escape":
const main = MainView.Instance;
@@ -104,6 +103,7 @@ export default class KeyManager {
}
UndoManager.RunInBatch(() =>
SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument?.(dv.props.Document)), "delete");
+ SelectionManager.DeselectAll();
break;
case "arrowleft":
UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-1, 0)), "nudge left");
@@ -262,14 +262,14 @@ export default class KeyManager {
}
break;
case "c":
- if (SelectionManager.SelectedDocuments().length) {
+ if (DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) {
const bds = DocumentDecorations.Instance.Bounds;
const pt = SelectionManager.SelectedDocuments()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":");
SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText(text);
stopPropagation = false;
- preventDefault = false;
}
+ preventDefault = false;
break;
}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 324665aea..dc5d387d0 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,6 +1,5 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faPaintBrush } from "@fortawesome/free-solid-svg-icons";
-import { observable, runInAction, action } from "mobx";
import { observer } from "mobx-react";
import { documentSchema } from "../../fields/documentSchemas";
import { InkData, InkField, InkTool } from "../../fields/InkField";
@@ -16,7 +15,6 @@ import { FieldView, FieldViewProps } from "./nodes/FieldView";
import React = require("react");
import { Scripting } from "../util/Scripting";
import { Doc } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
library.add(faPaintBrush);
@@ -43,28 +41,26 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
render() {
TraceMobx();
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
+ const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
const xs = data.map(p => p.X);
const ys = data.map(p => p.Y);
- const left = Math.min(...xs);
- const top = Math.min(...ys);
- const right = Math.max(...xs);
- const bottom = Math.max(...ys);
+ const left = Math.min(...xs) - strokeWidth / 2;
+ const top = Math.min(...ys) - strokeWidth / 2;
+ const right = Math.max(...xs) + strokeWidth / 2;
+ const bottom = Math.max(...ys) + strokeWidth / 2;
const width = right - left;
const height = bottom - top;
- const scaleX = this.props.PanelWidth() / width;
- const scaleY = this.props.PanelHeight() / height;
- const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
+ const scaleX = (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
+ const scaleY = (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
const strokeColor = StrCast(this.layoutDoc.color, ActiveInkColor());
- const points = InteractionUtils.CreatePolyline(data, left, top,
- strokeColor,
- strokeWidth.toString(),
- StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()), StrCast(this.layoutDoc.arrowStart, ActiveArrowStart()), StrCast(this.layoutDoc.arrowEnd, ActiveArrowEnd()), StrCast(this.layoutDoc.dash, ActiveDash()), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5);
+ const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
+ StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()),
+ StrCast(this.layoutDoc.arrowStart, ActiveArrowStart()), StrCast(this.layoutDoc.arrowEnd, ActiveArrowEnd()),
+ StrCast(this.layoutDoc.dash, ActiveDash()), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5);
const hpoints = InteractionUtils.CreatePolyline(data, left, top,
- this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent",
- // strokeColor,
- (strokeWidth + 15).toString(),
- StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()), StrCast(this.layoutDoc.arrowStart, ActiveArrowStart()), StrCast(this.layoutDoc.arrowEnd, ActiveArrowEnd()), StrCast(this.layoutDoc.dash, ActiveDash()), scaleX, scaleY, "", this.props.active() ? "visiblestroke" : "none", false);
- console.log("#" + strokeColor);
+ this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, (strokeWidth + 15),
+ StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()),
+ "none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblestroke" : "none", false);
return (
<svg className="inkingStroke"
width={width}
@@ -96,18 +92,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
- {/* <marker id="arrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
- <path d="M0,0 L0,6 L9,3 z" fill="black" />
- </marker> */}
- {/* <marker id="arrowHead" orient="auto" overflow="visible" refX="10" refY="3.5" markerWidth="10" markerHeight="7">
- <polygon points="10 0, 10 7, 0 3.5" fill="dodgerblue" />
- </marker>
- <marker id="arrowEnd" orient="auto" overflow="visible" refX="0" refY="3.5" markerWidth="10" markerHeight="7">
- <polygon points="0 0, 10 3.5, 0 7" fill="dodgerblue" />
- </marker> */}
- {/* <marker id="dot" orient="auto" overflow="visible">
- <circle r={strokeWidth} fill={"#" + strokeColor} />
- </marker> */}
</defs>
{hpoints}
{points}
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index dca2a1e3e..e84969565 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -32,9 +32,6 @@
}
.mainView-container, .mainView-container-dark {
- input {
- color: unset !important;
- }
width: 100%;
height: 100%;
position: absolute;
@@ -50,6 +47,10 @@
.mainView-container {
color:dimgray;
+ .lm_title {
+ background: #cacaca;
+ color:black;
+ }
}
.mainView-container-dark {
@@ -57,6 +58,10 @@
.lm_goldenlayout {
background: dimgray;
}
+ .lm_title {
+ background: black;
+ color:unset;
+ }
.marquee {
border-color: white;
}
@@ -79,6 +84,7 @@
height: 100%;
position: absolute;
display: flex;
+ user-select: none;
}
.mainView-flyoutContainer {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 15a6239be..a785a0d8b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -20,7 +20,7 @@ import { listSpec } from '../../fields/Schema';
import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils, returnEmptyFilter } from '../../Utils';
import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs, DocumentOptions } from '../documents/Documents';
@@ -58,7 +58,7 @@ import { DocumentManager } from '../util/DocumentManager';
@observer
export class MainView extends React.Component {
public static Instance: MainView;
- private _buttonBarHeight = 26;
+ private _buttonBarHeight = 36;
private _flyoutSizeOnDown = 0;
private _urlState: HistoryUtil.DocUrl;
private _docBtnRef = React.createRef<HTMLDivElement>();
@@ -201,9 +201,9 @@ export class MainView extends React.Component {
const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
- const cloneWorkspace = ScriptField.MakeScript(`cloneWorkspace()`);
- workspaceDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneWorkspace!]);
- workspaceDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Workspace Layout"]);
+ const copyWorkspace = ScriptField.MakeScript(`copyWorkspace()`);
+ workspaceDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, copyWorkspace!]);
+ workspaceDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Workspace"]);
Doc.AddDocToList(workspaces, "data", workspaceDoc);
// bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
@@ -310,6 +310,7 @@ export class MainView extends React.Component {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
/>;
@@ -389,7 +390,7 @@ export class MainView extends React.Component {
return (null);
}
return <div className="mainView-flyoutContainer" >
- <div className="mainView-tabButtons" style={{ height: `${this._buttonBarHeight}px`, backgroundColor: StrCast(this.sidebarButtonsDoc.backgroundColor) }}>
+ <div className="mainView-tabButtons" style={{ height: `${this._buttonBarHeight - 10/*margin-top*/}px`, backgroundColor: StrCast(this.sidebarButtonsDoc.backgroundColor) }}>
<DocumentView
Document={this.sidebarButtonsDoc}
DataDoc={undefined}
@@ -412,6 +413,7 @@ export class MainView extends React.Component {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
</div>
@@ -438,6 +440,7 @@ export class MainView extends React.Component {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
<button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
@@ -529,6 +532,7 @@ export class MainView extends React.Component {
renderDepth={0}
focus={emptyFunction}
whenActiveChanged={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
</div>;
@@ -578,7 +582,7 @@ export class MainView extends React.Component {
}
Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().fontFamily = "Comic Sans MS"; Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; });
-Scripting.addGlobal(function cloneWorkspace() {
+Scripting.addGlobal(function copyWorkspace() {
const copiedWorkspace = Doc.MakeCopy(Cast(Doc.UserDoc().activeWorkspace, Doc, null), true);
const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc, null);
Doc.AddDocToList(workspaces, "data", copiedWorkspace);
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index cfa869fb2..37d8dd23b 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -4,7 +4,7 @@ import * as React from "react";
import { Doc, DocListCast, Opt } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { NumCast, Cast } from "../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils, setupMoveUpEvents } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils, setupMoveUpEvents, returnEmptyFilter } from "../../Utils";
import { Transform } from "../util/Transform";
import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
import { DocumentView } from "./nodes/DocumentView";
@@ -12,7 +12,6 @@ import './OverlayView.scss';
import { Scripting } from "../util/Scripting";
import { ScriptingRepl } from './ScriptingRepl';
import { DragManager } from "../util/DragManager";
-import { listSpec } from "../../fields/Schema";
import { List } from "../../fields/List";
export type OverlayDisposer = () => void;
@@ -203,6 +202,7 @@ export class OverlayView extends React.Component {
backgroundColor={returnEmptyString}
addDocTab={returnFalse}
pinToPres={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
</div>;
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 108eb83d6..0a4334302 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { Doc } from "../../fields/Doc";
import { NumCast } from "../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue, returnEmptyFilter } from "../../Utils";
import { Transform } from "../util/Transform";
import { DocumentView } from "./nodes/DocumentView";
import "./Palette.scss";
@@ -59,6 +59,7 @@ export default class Palette extends React.Component<PaletteProps> {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
<div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index 8ca81c070..cdde32c21 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -6,7 +6,7 @@ import "./RecommendationsBox.scss";
import { Doc, DocListCast, WidthSym, HeightSym } from "../../fields/Doc";
import { DocumentIcon } from "./nodes/DocumentIcon";
import { StrCast, NumCast } from "../../fields/Types";
-import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero } from "../../Utils";
+import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero, returnEmptyFilter } from "../../Utils";
import { Transform } from "../util/Transform";
import { ObjectField } from "../../fields/ObjectField";
import { DocumentView } from "./nodes/DocumentView";
@@ -56,7 +56,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
}
const returnXDimension = () => 150;
const returnYDimension = () => 150;
- const scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
+ const scale = () => returnXDimension() / NumCast(renderDoc._nativeWidth, returnXDimension());
//let scale = () => 1;
const newRenderDoc = Doc.MakeAlias(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
newRenderDoc.height = NumCast(this.props.Document.documentIconHeight);
@@ -82,6 +82,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
parentActive={returnFalse}
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
ContentScaling={scale}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index f135823a8..916e631d0 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -10,7 +10,7 @@ import { Doc, DocListCast } from "../../fields/Doc";
import { Docs, DocUtils, } from "../documents/Documents";
import { StrCast, Cast } from "../../fields/Types";
import { CollectionTreeView } from "./collections/CollectionTreeView";
-import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero } from "../../Utils";
+import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero, returnEmptyFilter } from "../../Utils";
import { Transform } from "../util/Transform";
import { ScriptField, ComputedField } from "../../fields/ScriptField";
import { Scripting } from "../util/Scripting";
@@ -140,6 +140,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
CollectionView={undefined}
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
+ docFilters={returnEmptyFilter}
rootSelected={returnFalse}
onCheckedClick={this.scriptField!}
onChildClick={this.scriptField!}
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 2fafcecb2..18d642510 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -2,13 +2,17 @@
.lm_title {
margin-top: 3px;
- background: black;
border-radius: 5px;
border: solid 1px dimgray;
border-width: 2px 2px 0px;
height: 20px;
transform: translate(0px, -3px);
+ cursor: grab;
}
+.lm_title.focus-visible {
+ cursor: text;
+}
+
.lm_title_wrap {
overflow: hidden;
height: 19px;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6f5a3dfe4..a969e302d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -8,11 +8,10 @@ import * as GoldenLayout from "../../../client/goldenLayout";
import { DateField } from '../../../fields/DateField';
import { Doc, DocListCast, Field, Opt, DataSym } from "../../../fields/Doc";
import { Id } from '../../../fields/FieldSymbols';
-import { List } from '../../../fields/List';
import { FieldId } from "../../../fields/RefField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, returnTrue, Utils, returnZero } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
@@ -44,7 +43,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
props: {
documentId: document[Id],
libraryPath: libraryPath?.map(d => d[Id])
- //collectionDockingView: CollectionDockingView.Instance
}
};
}
@@ -465,7 +463,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (docids) {
const docs = (await Promise.all(docids.map(id => DocServer.GetRefField(id)))).filter(f => f).map(f => f as Doc);
- Doc.GetProto(this.props.Document)[this.props.fieldKey] = new List<Doc>(docs);
+ docs.map(doc => Doc.AddDocToList(Doc.GetProto(this.props.Document), this.props.fieldKey, doc));
+ // Doc.GetProto(this.props.Document)[this.props.fieldKey] = new List<Doc>(docs);
}
}
@@ -843,6 +842,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
addDocTab={this.addDocTab}
pinToPres={DockedFrameRenderer.PinDoc}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />;
}
@@ -859,5 +859,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
</div >);
}
}
-Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); });
+Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc); },
+ "opens up the inputted document on the right side of the screen", "(doc: any)");
Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.UseRightSplit(doc, undefined, shiftKey); });
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index f1002044a..f38eeaf41 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -124,6 +124,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
</div>;
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index cc7a9f5ac..e0b53e762 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -43,6 +43,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@observable private heading: string = "";
@observable private color: string = "#f1efeb";
@observable private collapsed: boolean = false;
+ @observable private _paletteOn = false;
private set _heading(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.heading = this.heading = value)); }
private set _color(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.color = this.color = value)); }
private set _collapsed(value: boolean) { runInAction(() => this.props.headingObject && (this.props.headingObject.collapsed = this.collapsed = value)); }
@@ -293,11 +294,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
{noChrome ? evContents : <EditableView {...headerEditableViewProps} />}
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionColor">
- <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}>
- <button className="collectionStackingView-sectionColorButton">
- <FontAwesomeIcon icon="palette" size="lg" />
- </button>
- </ Flyout >
+ <button className="collectionStackingView-sectionColorButton" onClick={action(e => this._paletteOn = !this._paletteOn)}>
+ <FontAwesomeIcon icon="palette" size="lg" />
+ </button>
+ {this._paletteOn ? this.renderColorPicker() : (null)}
</div>
}
{noChrome ? (null) : <button className="collectionStackingView-sectionDelete" onClick={noChrome ? undefined : this.collapseSection}>
@@ -305,7 +305,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
</button>}
{noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionOptions">
- <Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}>
+ <Flyout anchorPoint={anchorPoints.TOP_CENTER} content={this.renderMenu()}>
<button className="collectionStackingView-sectionOptionButton">
<FontAwesomeIcon icon="ellipsis-v" size="lg" />
</button>
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 62aed67ed..2b8110e27 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -1,9 +1,9 @@
import React = require("react");
-import { action, observable } from "mobx";
+import { action, observable, trace } from "mobx";
import { observer } from "mobx-react";
import { CellInfo } from "react-table";
import "react-table/react-table.css";
-import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils";
+import { emptyFunction, returnFalse, returnZero, returnOne, returnEmptyFilter } from "../../../Utils";
import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { KeyCodes } from "../../util/KeyCodes";
@@ -23,6 +23,7 @@ import { faExpand } from '@fortawesome/free-solid-svg-icons';
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { undoBatch } from "../../util/UndoManager";
import { SnappingManager } from "../../util/SnappingManager";
+import { ComputedField } from "../../../fields/ScriptField";
library.add(faExpand);
@@ -57,7 +58,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
componentDidMount() {
document.addEventListener("keydown", this.onKeyDown);
-
}
componentWillUnmount() {
@@ -70,7 +70,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
document.removeEventListener("keydown", this.onKeyDown);
this._isEditing = true;
this.props.setIsEditing(true);
-
}
}
@@ -160,6 +159,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
bringToFront: emptyFunction,
rootSelected: returnFalse,
fieldKey: this.props.rowProps.column.id as string,
+ docFilters: returnEmptyFilter,
ContainingCollectionView: this.props.CollectionView,
ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
isSelected: returnFalse,
@@ -217,7 +217,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
// <div className="collectionSchemaView-cellContents-docExpander" onPointerDown={this.expandDoc} >
// <FontAwesomeIcon icon="expand" size="sm" />
// </div>
- // );
+ // );
+ trace();
return (
<div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
@@ -231,23 +232,29 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
height={"auto"}
maxHeight={Number(MAX_ROW_HEIGHT)}
GetValue={() => {
- const field = props.Document[props.fieldKey];
- if (Field.IsField(field)) {
- return Field.toScriptString(field);
- }
- return "";
- }
- }
- SetValue={(value: string) => {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
+ const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
+ const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1];
+ const val = cscript !== undefined ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` :
+ Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
+ return val;
+ }}
+ SetValue={action((value: string) => {
+ let retVal = false;
if (value.startsWith(":=")) {
- return this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
+ retVal = this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
+ } else {
+ const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ if (script.compiled) {
+ retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
+ }
}
- const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- if (!script.compiled) {
- return false;
+ 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'
+ this.props.setIsEditing(false);
}
- return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
- }}
+ return retVal;
+ })}
OnFillDown={async (value: string) => {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (script.compiled) {
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 6f1e8ac1f..b206765e8 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -66,8 +66,9 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
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];
- if (de.complete.columnDragData) {
- this.props.reorderColumns(de.complete.columnDragData.colKey, this.props.columnValue, before, this.props.allColumns);
+ const colDragData = de.complete.columnDragData;
+ if (colDragData) {
+ this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns);
return true;
}
return false;
@@ -164,13 +165,14 @@ export class MovableRow extends React.Component<MovableRowProps> {
}
createRowDropTarget = (ele: HTMLDivElement) => {
- this._rowDropDisposer && this._rowDropDisposer();
+ this._rowDropDisposer?.();
if (ele) {
this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
}
rowDrop = (e: Event, de: DragManager.DropEvent) => {
+ this.onPointerLeave(e as any);
const rowDoc = FieldValue(Cast(this.props.rowInfo.original, Doc));
if (!rowDoc) return false;
@@ -203,10 +205,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
@action
move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => {
const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
- if (targetView && targetView.props.ContainingCollectionDoc) {
- return doc !== targetCollection && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
- }
- return doc !== targetCollection && this.props.removeDoc(doc) && addDoc(doc);
+ return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
}
render() {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 35f892d65..56a2a517c 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -28,7 +28,7 @@ import { CollectionSubView } from "./CollectionSubView";
import { CollectionView } from "./CollectionView";
import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
import { setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils";
-import { DocumentView } from "../nodes/DocumentView";
+import { SnappingManager } from "../../util/SnappingManager";
library.add(faCog, faPlus, faSortUp, faSortDown);
library.add(faTable);
@@ -133,6 +133,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
PanelWidth={this.previewWidth}
PanelHeight={this.previewHeight}
ScreenToLocalTransform={this.getPreviewTransform}
+ docFilters={this.docFilters}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
moveDocument={this.props.moveDocument}
@@ -186,7 +187,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
render() {
- return <div className="collectionSchemaView-container">
+ return <div className="collectionSchemaView-container"
+ style={{
+ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined,
+ width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%"
+ }} >
<div className="collectionSchemaView-tableContainer" style={{ width: `calc(100% - ${this.previewWidth()}px)` }} onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
{this.schemaTable}
</div>
@@ -651,7 +656,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
resized={this.resized}
onResizedChange={this.onResizedChange}
SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== "collection") ? (null) :
- <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} childDocs={undefined} /></div>}
+ <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
/>;
}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 203c51163..3d8ec2fd5 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -240,11 +240,15 @@
}
.collectionStackingView-sectionColorButton {
- height: 35px;
+ height: 30px;
+ display: inherit;
}
.collectionStackingView-colorPicker {
width: 78px;
+ z-index: 10;
+ position: relative;
+ background: white;
.colorOptions {
display: flex;
@@ -278,7 +282,7 @@
}
.collectionStackingView-sectionOptionButton {
- height: 35px;
+ height: 30px;
}
.collectionStackingView-optionPicker {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0a1b03522..4aab43b08 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -217,6 +217,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
ScreenToLocalTransform={dxf}
opacity={opacity}
focus={this.focusDocument}
+ docFilters={this.docFilters}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
@@ -476,7 +477,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
transformOrigin: "top left",
}}
onScroll={action(e => {
- if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
+ if (!this.props.isSelected() && this.props.renderDepth) e.currentTarget.scrollTop = this._scroll;
else this._scroll = e.currentTarget.scrollTop;
})}
onDrop={this.onExternalDrop.bind(this)}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index bcd55f0fe..b60ed853b 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -50,6 +50,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
private dropDisposer?: DragManager.DragDropDisposer;
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
+ @observable _paletteOn = false;
@observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
@observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
_ele: HTMLElement | null = null;
@@ -326,11 +327,10 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
<EditableView {...headerEditableViewProps} />
{evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
<div className="collectionStackingView-sectionColor">
- <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}>
- <button className="collectionStackingView-sectionColorButton">
- <FontAwesomeIcon icon="palette" size="lg" />
- </button>
- </ Flyout >
+ <button className="collectionStackingView-sectionColorButton" onClick={action(e => this._paletteOn = !this._paletteOn)}>
+ <FontAwesomeIcon icon="palette" size="lg" />
+ </button>
+ {this._paletteOn ? this.renderColorPicker() : (null)}
</div>
}
{evContents === `NO ${key.toUpperCase()} VALUE` ?
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 93d20c475..00d6d59c8 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -10,7 +10,7 @@ import { WebField } from "../../../fields/URLField";
import { Cast, ScriptCast, NumCast } from "../../../fields/Types";
import { GestureUtils } from "../../../pen-gestures/GestureUtils";
import { Upload } from "../../../server/SharedMediaTypes";
-import { Utils, returnFalse } from "../../../Utils";
+import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Networking } from "../../Network";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
@@ -101,8 +101,13 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
get childDocList() {
return Cast(this.dataField, listSpec(Doc));
}
+ docFilters = () => {
+ return this.props.ignoreFields?.includes("_docFilters") ? [] :
+ this.props.docFilters !== returnEmptyFilter ? this.props.docFilters() :
+ Cast(this.props.Document._docFilters, listSpec("string"), []);
+ }
@computed get childDocs() {
- const docFilters = this.props.ignoreFields?.includes("_docFilters") ? [] : Cast(this.props.Document._docFilters, listSpec("string"), []);
+ const docFilters = this.docFilters();
const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
for (let i = 0; i < docFilters.length; i += 3) {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e891c4274..747eb36a1 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,7 +8,7 @@ import { PrefetchProxy } from '../../../fields/Proxy';
import { Document, listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils, returnEmptyFilter } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
@@ -83,8 +83,9 @@ class TreeView extends React.Component<TreeViewProps> {
private _tref = React.createRef<HTMLDivElement>();
private _docRef = React.createRef<DocumentView>();
+ get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); }
get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive
- get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, "fields"); }
+ get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, this.noviceMode ? "layout" : "fields"); }
@observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
@computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.props.document.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
@@ -123,7 +124,7 @@ class TreeView extends React.Component<TreeViewProps> {
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer?.();
- ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)), this.props.document);
+ ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.props.document);
}
onPointerEnter = (e: React.PointerEvent): void => {
@@ -187,33 +188,36 @@ class TreeView extends React.Component<TreeViewProps> {
})}
/>)
+ preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+ const dragData = de.complete.docDragData;
+ dragData && (dragData.dropAction = this.props.treeViewId[Id] === dragData.treeViewId ? "same" : dragData.dropAction);
+ }
+
@undoBatch
treeDrop = (e: Event, de: DragManager.DropEvent) => {
const pt = [de.x, de.y];
const rect = this._header!.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && DocListCast(this.dataDoc[this.fieldKey]).length);
- if (de.complete.linkDragData) {
- const sourceDoc = de.complete.linkDragData.linkSourceDocument;
+ const complete = de.complete;
+ if (complete.linkDragData) {
+ const sourceDoc = complete.linkDragData.linkSourceDocument;
const destDoc = this.props.document;
DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link");
e.stopPropagation();
}
- if (de.complete.docDragData) {
+ const docDragData = complete.docDragData;
+ if (docDragData) {
e.stopPropagation();
- if (de.complete.docDragData.draggedDocuments[0] === this.props.document) return true;
- let addDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
+ if (docDragData.draggedDocuments[0] === this.props.document) return true;
+ const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before);
+ let addDoc = parentAddDoc;
if (inside) {
addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce(
- ((flg: boolean, doc) => flg && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc)), true) || addDoc(doc);
+ (flg: boolean, doc) => flg && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc), true) || parentAddDoc(doc);
}
- const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId[Id] ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
- const move = de.complete.docDragData.dropAction === "move" || de.complete.docDragData.dropAction;
- return ((!move && (de.complete.docDragData.treeViewId !== this.props.treeViewId[Id])) || de.complete.docDragData.userDropAction) ?
- de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
- : de.complete.docDragData.moveDocument ?
- movedDocs.reduce((added, d) => de.complete.docDragData?.moveDocument?.(d, undefined, addDoc) || added, false)
- : de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d), false);
+ const move = (!docDragData.dropAction || docDragData.dropAction === "move" || docDragData.dropAction === "same") && docDragData.moveDocument;
+ return docDragData.droppedDocuments.reduce((added, d) => (move ? docDragData.moveDocument?.(d, undefined, addDoc) : addDoc(d)) || added, false);
}
return false;
}
@@ -246,7 +250,7 @@ class TreeView extends React.Component<TreeViewProps> {
const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
if (aspect) return this.docWidth() * aspect;
if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
- return layoutDoc._fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection._height) :
+ return layoutDoc._fitWidth ? (!this.props.document._nativeHeight ? NumCast(this.props.containingCollection._height) :
Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
NumCast(this.props.containingCollection._height)))) :
NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
@@ -341,6 +345,7 @@ class TreeView extends React.Component<TreeViewProps> {
LibraryPath={emptyPath}
renderDepth={this.props.renderDepth + 1}
rootSelected={returnTrue}
+ treeViewId={this.props.treeViewId[Id]}
backgroundColor={this.props.backgroundColor}
fitToBox={this.boundsOfCollectionDocument !== undefined}
FreezeDimensions={true}
@@ -350,6 +355,7 @@ class TreeView extends React.Component<TreeViewProps> {
PanelHeight={panelHeight}
focus={returnFalse}
ScreenToLocalTransform={this.docTransform}
+ docFilters={returnEmptyFilter}
ContainingCollectionDoc={this.props.containingCollection}
ContainingCollectionView={undefined}
addDocument={returnFalse}
@@ -417,10 +423,10 @@ class TreeView extends React.Component<TreeViewProps> {
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
if (this.treeViewOpen) {
- this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" :
+ this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") :
this.treeViewExpandedView === "fields" && Doc.Layout(this.props.document) ? "layout" :
this.treeViewExpandedView === "layout" && this.props.document.links ? "links" :
- this.childDocs ? this.fieldKey : "fields";
+ this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields");
}
this.treeViewOpen = true;
})}>
@@ -467,6 +473,7 @@ class TreeView extends React.Component<TreeViewProps> {
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildViews)}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={this.props.containingCollection}
/>}
@@ -480,12 +487,13 @@ class TreeView extends React.Component<TreeViewProps> {
TraceMobx();
const sorting = this.props.document[`${this.fieldKey}-sortAscending`];
//setTimeout(() => runInAction(() => untracked(() => this._overrideTreeViewOpen = this.treeViewOpen)), 0);
- return <div className="treeViewItem-container" ref={this.createTreeDropTarget}>
+ return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active() && SelectionManager.DeselectAll()}>
<li className="collection-child">
<div className="treeViewItem-header" ref={this._header} onClick={e => {
if (this.props.active(true)) {
e.stopPropagation();
e.preventDefault();
+ SelectionManager.DeselectAll();
}
}}
onPointerDown={e => {
@@ -662,7 +670,16 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this.treedropDisposer?.();
if (this._mainEle = ele) {
- this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.props.Document);
+ this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.props.Document, this.onInternalPreDrop.bind(this));
+ }
+ }
+
+ protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+ const dragData = de.complete.docDragData;
+ if (dragData) {
+ if (targetAction && !dragData.draggedDocuments.some(d => d.context === this.props.Document && this.childDocs.includes(d))) {
+ dragData.dropAction = targetAction;
+ } else dragData.dropAction = this.props.Document[Id] === dragData?.treeViewId ? "same" : dragData.dropAction;
}
}
@@ -788,7 +805,8 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
background: this.props.backgroundColor?.(this.props.Document),
paddingLeft: `${NumCast(this.props.Document._xPadding, 10)}px`,
paddingRight: `${NumCast(this.props.Document._xPadding, 10)}px`,
- paddingTop: `${NumCast(this.props.Document._yPadding, 20)}px`
+ paddingTop: `${NumCast(this.props.Document._yPadding, 20)}px`,
+ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined
}}
onKeyPress={this.onKeyPress}
onContextMenu={this.onContextMenu}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a25a864af..508b9e5e7 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,7 +17,7 @@ import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnEmptyFilter } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
@@ -42,8 +42,10 @@ import { CollectionStaffView } from './CollectionStaffView';
import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
+import { CollectionGridView } from './collectionGrid/CollectionGridView';
import './CollectionView.scss';
import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import { UndoManager } from '../../util/UndoManager';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -67,6 +69,7 @@ export enum CollectionViewType {
Linear = "linear",
Staff = "staff",
Map = "map",
+ Grid = "grid",
Pile = "pileup"
}
export interface CollectionViewCustomProps {
@@ -91,7 +94,7 @@ export interface CollectionRenderProps {
export class CollectionView extends Touchable<FieldViewProps & CollectionViewCustomProps> {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
- private _isChildActive = false; //TODO should this be observable?
+ _isChildActive = false; //TODO should this be observable?
get _isLightboxOpen() { return BoolCast(this.props.Document.isLightboxOpen); }
set _isLightboxOpen(value) { this.props.Document.isLightboxOpen = value; }
@observable private _curLightboxImg = 0;
@@ -164,7 +167,17 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
return true;
}
const first = doc instanceof Doc ? doc : doc[0];
- return !first?.stayInCollection && addDocument !== returnFalse && this.removeDocument(doc) ? addDocument(doc) : false;
+ if (!first?.stayInCollection && addDocument !== returnFalse) {
+ if (UndoManager.RunInTempBatch(() => this.removeDocument(doc))) {
+ const added = addDocument(doc);
+ if (!added) UndoManager.UndoTempBatch();
+ else UndoManager.ClearTempBatch();
+
+ return added;
+ }
+ UndoManager.ClearTempBatch();
+ }
+ return false;
}
showIsTagged = () => {
@@ -178,8 +191,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
// return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
}
+ screenToLocalTransform = () => this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth());
private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
- const props: SubCollectionViewProps = { ...this.props, ...renderProps, CollectionView: this, annotationsKey: "" };
+ const props: SubCollectionViewProps = { ...this.props, ...renderProps, ScreenToLocalTransform: this.screenToLocalTransform, CollectionView: this, annotationsKey: "" };
switch (type) {
case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />);
case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
@@ -194,6 +208,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Time: { return (<CollectionTimeView key="collview" {...props} />); }
case CollectionViewType.Map: return (<CollectionMapView key="collview" {...props} />);
+ case CollectionViewType.Grid: return (<CollectionGridView key="gridview" {...props} />);
case CollectionViewType.Freeform:
default: { this.props.Document._freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); }
}
@@ -231,6 +246,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" });
subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
+ subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
if (addExtras && this.props.Document._viewType === CollectionViewType.Freeform) {
subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
}
@@ -240,7 +256,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
-
this.setupViewTypes("Add a Perspective...", vtype => {
const newRendition = Doc.MakeAlias(this.props.Document);
newRendition._viewType = vtype;
@@ -279,10 +294,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}));
!existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
- const more = ContextMenu.Instance.findByDescription("More...");
- const moreItems = more && "subitems" in more ? more.subitems : [];
- moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
- !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+ if (!Doc.UserDoc().noviceMode) {
+ const more = ContextMenu.Instance.findByDescription("More...");
+ const moreItems = more && "subitems" in more ? more.subitems : [];
+ moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
+ !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+ }
}
}
@@ -450,6 +467,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
DataDoc={facetCollection}
fieldKey={`${this.props.fieldKey}-filter`}
CollectionView={this}
+ docFilters={returnEmptyFilter}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
ContainingCollectionView={this.props.ContainingCollectionView}
PanelWidth={this.facetWidth}
@@ -498,13 +516,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
ChildLayoutTemplate: this.childLayoutTemplate,
ChildLayoutString: this.childLayoutString,
};
- return (<div className={"collectionView"}
- style={{
- pointerEvents: this.props.Document.isBackground ? "none" : undefined,
- boxShadow: Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
- `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31)" : "#9c9396"} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
- }}
- onContextMenu={this.onContextMenu}>
+ const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
+ `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
+ return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
+ style={{ pointerEvents: this.props.Document.isBackground ? "none" : undefined, boxShadow }}>
{this.showIsTagged()}
<div className="collectionView-facetCont" style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
{this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 03bd9a01a..77a12ed37 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -3,7 +3,7 @@
.collectionViewChrome-cont {
position: absolute;
- width:100%;
+ width: 100%;
opacity: 0.9;
z-index: 9001;
transition: top .5s;
@@ -13,9 +13,9 @@
.collectionViewChrome {
display: flex;
padding-bottom: 1px;
- height:32px;
+ height: 32px;
border-bottom: .5px solid rgb(180, 180, 180);
- overflow: hidden;
+ overflow: visible;
.collectionViewBaseChrome {
display: flex;
@@ -35,7 +35,7 @@
outline-color: black;
}
- .collectionViewBaseChrome-button{
+ .collectionViewBaseChrome-button {
font-size: 75%;
text-transform: uppercase;
letter-spacing: 2px;
@@ -46,6 +46,7 @@
padding: 12px 10px 11px 10px;
margin-left: 10px;
}
+
.collectionViewBaseChrome-cmdPicker {
margin-left: 3px;
margin-right: 0px;
@@ -54,15 +55,17 @@
border: none;
color: grey;
}
+
.commandEntry-outerDiv {
pointer-events: all;
background-color: gray;
display: flex;
flex-direction: row;
- height:30px;
+ height: 30px;
+
.commandEntry-drop {
- color:white;
- width:25px;
+ color: white;
+ width: 25px;
margin-top: auto;
margin-bottom: auto;
}
@@ -76,15 +79,17 @@
pointer-events: all;
// margin-top: 10px;
}
+
.collectionViewBaseChrome-template,
.collectionViewBaseChrome-viewModes {
display: grid;
background: rgb(238, 238, 238);
- color:grey;
- margin-top:auto;
- margin-bottom:auto;
+ color: grey;
+ margin-top: auto;
+ margin-bottom: auto;
margin-left: 5px;
}
+
.collectionViewBaseChrome-viewModes {
margin-left: 25px;
}
@@ -92,7 +97,7 @@
.collectionViewBaseChrome-viewSpecs {
margin-left: 5px;
display: grid;
-
+
.collectionViewBaseChrome-filterIcon {
position: relative;
display: flex;
@@ -163,13 +168,53 @@
}
}
-
.collectionStackingViewChrome-cont,
.collectionTreeViewChrome-cont {
display: flex;
justify-content: space-between;
}
+ .collectionGridViewChrome-cont {
+ display: flex;
+ margin-left: 10;
+
+ .collectionGridViewChrome-viewPicker {
+ font-size: 75%;
+ //text-transform: uppercase;
+ //letter-spacing: 2px;
+ background: rgb(238, 238, 238);
+ color: grey;
+ outline-color: black;
+ border: none;
+ //padding: 12px 10px 11px 10px;
+ }
+
+ .collectionGridViewChrome-viewPicker:active {
+ outline-color: black;
+ }
+
+ .grid-control {
+ align-self: center;
+ display: flex;
+ flex-direction: row;
+ margin-right: 5px;
+
+ .grid-icon {
+ margin-right: 5px;
+ align-self: center;
+ }
+
+ .flexLabel {
+ margin-bottom: 0;
+ }
+ }
+
+ .collectionGridViewChrome-entryBox {
+ width: 50%;
+ }
+ }
+
+
.collectionStackingViewChrome-sort,
.collectionTreeViewChrome-sort {
display: flex;
@@ -199,13 +244,13 @@
.collectionTreeViewChrome-pivotField-label {
vertical-align: center;
padding-left: 10px;
- margin:auto;
+ margin: auto;
}
.collectionStackingViewChrome-pivotField,
.collectionTreeViewChrome-pivotField {
color: white;
- width:100%;
+ width: 100%;
min-width: 100px;
display: flex;
align-items: center;
@@ -215,7 +260,7 @@
input,
.editableView-container-editing-oneLine,
.editableView-container-editing {
- margin:auto;
+ margin: auto;
border: 0px;
color: grey;
text-align: center;
@@ -236,6 +281,7 @@
.collectionTreeViewChrome-pivotField:hover {
cursor: text;
}
+
}
}
@@ -244,7 +290,10 @@
display: flex;
position: relative;
align-items: center;
- .fwdKeyframe, .numKeyframe, .backKeyframe {
+
+ .fwdKeyframe,
+ .numKeyframe,
+ .backKeyframe {
cursor: pointer;
position: absolute;
width: 20;
@@ -253,26 +302,31 @@
background: gray;
display: flex;
align-items: center;
- color:white;
+ color: white;
}
+
.backKeyframe {
- left:0;
+ left: 0;
+
svg {
- display:block;
- margin:auto;
+ display: block;
+ margin: auto;
}
}
+
.numKeyframe {
- left:20;
+ left: 20;
display: flex;
flex-direction: column;
padding: 5px;
}
+
.fwdKeyframe {
- left:40;
+ left: 40;
+
svg {
- display:block;
- margin:auto;
+ display: block;
+ margin: auto;
}
}
}
@@ -334,8 +388,9 @@
flex-direction: column;
height: 40px;
}
+
.commandEntry-inputArea {
- display:flex;
+ display: flex;
flex-direction: row;
width: 150px;
margin: auto auto auto auto;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 3dc740c25..52fb63386 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -1,8 +1,8 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed, observable, runInAction, Lambda } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
-import { Doc, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
@@ -16,7 +16,6 @@ import { CollectionViewType } from "./CollectionView";
import { CollectionView } from "./CollectionView";
import "./CollectionViewChromes.scss";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-const datepicker = require('js-datepicker');
interface CollectionViewChromeProps {
CollectionView: CollectionView;
@@ -201,6 +200,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
default: return null;
}
}
@@ -220,8 +220,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- if (de.complete.docDragData && de.complete.docDragData.draggedDocuments.length) {
- this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData?.draggedDocuments || []));
+ const docDragData = de.complete.docDragData;
+ if (docDragData?.draggedDocuments.length) {
+ this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
e.stopPropagation();
}
return true;
@@ -258,10 +259,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<FontAwesomeIcon icon="bullseye" size="2x" />
</div>
<select
- className="collectionViewBaseChrome-cmdPicker"
- onPointerDown={stopPropagation}
- onChange={this.commandChanged}
- value={this._currentKey}>
+ className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}
+ style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} />
{this._buttonizableCommands.map(cmd =>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
@@ -279,7 +278,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<FontAwesomeIcon icon="bullseye" size="2x" />
</div>
<select
- className="collectionViewBaseChrome-viewPicker"
+ className="collectionViewBaseChrome-viewPicker" style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}
onPointerDown={stopPropagation}
onChange={this.viewChanged}
value={StrCast(this.props.CollectionView.props.Document._viewType)}>
@@ -562,3 +561,181 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
}
}
+/**
+ * Chrome for grid view.
+ */
+@observer
+export class CollectionGridViewChrome extends React.Component<CollectionViewChromeProps> {
+
+ private clicked: boolean = false;
+ private entered: boolean = false;
+ private decrementLimitReached: boolean = false;
+ @observable private resize = false;
+ private resizeListenerDisposer: Opt<Lambda>;
+
+ componentDidMount() {
+
+ runInAction(() => this.resize = this.props.CollectionView.props.PanelWidth() < 700);
+
+ // listener to reduce text on chrome resize (panel resize)
+ this.resizeListenerDisposer = computed(() => this.props.CollectionView.props.PanelWidth()).observe(({ newValue }) => {
+ runInAction(() => this.resize = newValue < 700);
+ });
+ }
+
+ componentWillUnmount() {
+ this.resizeListenerDisposer?.();
+ }
+
+ get numCols() { return NumCast(this.props.CollectionView.props.Document.gridNumCols, 10); }
+
+ /**
+ * Sets the value of `numCols` on the grid's Document to the value entered.
+ */
+ @undoBatch
+ onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ if (e.key === "Enter" || e.key === "Tab") {
+ if (e.currentTarget.valueAsNumber > 0) {
+ this.props.CollectionView.props.Document.gridNumCols = e.currentTarget.valueAsNumber;
+ }
+
+ }
+ }
+
+ /**
+ * Sets the value of `rowHeight` on the grid's Document to the value entered.
+ */
+ // @undoBatch
+ // onRowHeightEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ // if (e.key === "Enter" || e.key === "Tab") {
+ // if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.rowHeight as number !== e.currentTarget.valueAsNumber) {
+ // this.props.CollectionView.props.Document.rowHeight = e.currentTarget.valueAsNumber;
+ // }
+ // }
+ // }
+
+ /**
+ * Sets whether the grid is flexible or not on the grid's Document.
+ */
+ @undoBatch
+ toggleFlex = () => {
+ this.props.CollectionView.props.Document.gridFlex = !BoolCast(this.props.CollectionView.props.Document.gridFlex, true);
+ }
+
+ /**
+ * Increments the value of numCols on button click
+ */
+ onIncrementButtonClick = () => {
+ this.clicked = true;
+ this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)--;
+ undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1)();
+ this.entered = false;
+ }
+
+ /**
+ * Decrements the value of numCols on button click
+ */
+ onDecrementButtonClick = () => {
+ this.clicked = true;
+ if (!this.decrementLimitReached) {
+ this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)++;
+ undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1)();
+ }
+ this.entered = false;
+ }
+
+ /**
+ * Increments the value of numCols on button hover
+ */
+ incrementValue = () => {
+ this.entered = true;
+ if (!this.clicked && !this.decrementLimitReached) {
+ this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1;
+ }
+ this.decrementLimitReached = false;
+ this.clicked = false;
+ }
+
+ /**
+ * Decrements the value of numCols on button hover
+ */
+ decrementValue = () => {
+ this.entered = true;
+ if (!this.clicked) {
+ if (this.numCols !== 1) {
+ this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1;
+ }
+ else {
+ this.decrementLimitReached = true;
+ }
+ }
+
+ this.clicked = false;
+ }
+
+ /**
+ * Toggles the value of preventCollision
+ */
+ toggleCollisions = () => {
+ this.props.CollectionView.props.Document.gridPreventCollision = !this.props.CollectionView.props.Document.gridPreventCollision;
+ }
+
+ /**
+ * Changes the value of the compactType
+ */
+ changeCompactType = (e: React.ChangeEvent<HTMLSelectElement>) => {
+ // need to change startCompaction so that this operation will be undoable.
+ this.props.CollectionView.props.Document.gridStartCompaction = e.target.selectedOptions[0].value;
+ }
+
+ render() {
+ return (
+ <div className="collectionGridViewChrome-cont" >
+ <span className="grid-control" style={{ width: this.resize ? "25%" : "30%" }}>
+ <span className="grid-icon">
+ <FontAwesomeIcon icon="columns" size="1x" />
+ </span>
+ <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.numCols.toString()} onKeyDown={this.onNumColsEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ <input className="columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
+ <input className="columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
+ </span>
+ {/* <span className="grid-control">
+ <span className="grid-icon">
+ <FontAwesomeIcon icon="text-height" size="1x" />
+ </span>
+ <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ </span> */}
+ <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
+ <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.props.CollectionView.props.Document.gridPreventCollision} />
+ <label className="flexLabel">{this.resize ? "Coll" : "Collisions"}</label>
+ </span>
+
+ <select className="collectionGridViewChrome-viewPicker"
+ style={{ marginRight: 5, width: this.props.PanelWidth() < 300 ? 25 : undefined }}
+ onPointerDown={stopPropagation}
+ onChange={this.changeCompactType}
+ value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
+ {["vertical", "horizontal", "none"].map(type =>
+ <option className="collectionGridViewChrome-viewOption"
+ onPointerDown={stopPropagation}
+ value={type}>
+ {this.resize ? type[0].toUpperCase() + type.substring(1) : "Compact: " + type}
+ </option>
+ )}
+ </select>
+
+ <span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
+ <input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex}
+ checked={BoolCast(this.props.CollectionView.props.Document.gridFlex, true)} />
+ <label className="flexLabel">{this.resize ? "Flex" : "Flexible"}</label>
+ </span>
+
+ <button onClick={() => this.props.CollectionView.props.Document.gridResetLayout = true}>
+ {!this.resize ? "Reset" :
+ <FontAwesomeIcon icon="redo-alt" size="1x" />}
+ </button>
+
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f3fc04752..6cac39f77 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -46,10 +46,15 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
// really hacky stuff to make the LinkAnchorBox display where we want it to:
- // if there's an element in the DOM with the id of the opposite anchor, then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+ // if there's an element in the DOM with a classname containing the link's id and a targetids attribute containing the other end of the link,
+ // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
- const targetAhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][afield] as Doc)[Id]);
- const targetBhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][bfield] as Doc)[Id]);
+ const linkId = this.props.LinkDocs[0][Id]; // this link's Id
+ const AanchorId = (this.props.LinkDocs[0][afield] as Doc)[Id]; // anchor a's id
+ const BanchorId = (this.props.LinkDocs[0][bfield] as Doc)[Id]; // anchor b's id
+ const linkEles = Array.from(window.document.getElementsByClassName(linkId));
+ const targetAhyperlink = linkEles.find((ele: any) => ele.getAttribute("targetids")?.includes(AanchorId));
+ const targetBhyperlink = linkEles.find((ele: any) => ele.getAttribute("targetids")?.includes(BanchorId));
if (!targetBhyperlink) {
this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b97ac286c..742ec82e6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -52,7 +52,6 @@ library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressAr
export const panZoomSchema = createSchema({
_panX: "number",
_panY: "number",
- scale: "number",
currentTimecode: "number",
displayTimecode: "number",
currentFrame: "number",
@@ -76,6 +75,7 @@ export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: boolean;
+ scaleField?: string;
};
@observer
@@ -108,6 +108,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
@computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
+ private get scaleFieldKey() { return this.props.scaleField || "scale"; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
private easing = () => this.props.Document.panTransformType === "Ease";
private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
@@ -115,14 +116,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private zoomScaling = () => (this.fitToContentScaling / this.parentScaling) * (this.fitToContent ?
Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
- this.Document.scale || 1)
+ NumCast(this.Document[this.scaleFieldKey], 1))
@computed get cachedCenteringShiftX(): number {
- const scaling = this.fitToContent ? 1 : this.contentScaling;
+ const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
return !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / scaling : 0; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
- const scaling = this.fitToContent ? 1 : this.contentScaling;
+ const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
return !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / scaling : 0;// shift so pan position is at center of window for non-overlay collections
}
@computed get cachedGetLocalTransform(): Transform {
@@ -157,8 +158,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
if (retVal) {
const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox;
- for (let i = 0; i < newBoxes.length; i++) {
- const newBox = newBoxes[i];
+ for (const newBox of newBoxes) {
if (newBox.activeFrame !== undefined) {
const x = newBox.x;
const y = newBox.y;
@@ -239,7 +239,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@undoBatch
@action
internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
- if (linkDragData.linkSourceDocument === this.props.Document) return false;
+ if (linkDragData.linkSourceDocument === this.props.Document || this.props.Document.annotationOn) return false;
const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
this.props.addDocument(source);
linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
@@ -257,8 +257,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp);
} else if (de.complete.docDragData?.droppedDocuments.length && this.internalDocDrop(e, de, de.complete.docDragData, xp, yp)) {
return true;
- } else {
- UndoManager.Undo();
}
return false;
}
@@ -456,7 +454,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height });
+ const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), Doc.GetSelectedTool(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
+ { title: "ink stroke", x: B.x - Number(ActiveInkWidth()) / 2, y: B.y - Number(ActiveInkWidth()) / 2, _width: B.width + Number(ActiveInkWidth()), _height: B.height + Number(ActiveInkWidth()) });
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -779,7 +778,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
- this.props.Document.scale = Math.abs(safeScale);
+ this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
}
}
@@ -795,7 +794,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
else this.zoom(e.clientX, e.clientY, e.deltaY);
}
- this.props.Document.targetScale = NumCast(this.props.Document.scale);
+ this.props.Document.targetScale = NumCast(this.props.Document[this.scaleFieldKey]);
}
@action
@@ -852,7 +851,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
scaleAtPt(docpt: number[], scale: number) {
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
this.Document.panTransformType = "Ease";
- this.layoutDoc.scale = scale;
+ this.layoutDoc[this.scaleFieldKey] = scale;
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
@@ -896,7 +895,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
HistoryUtil.pushState(newState);
- const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
+ const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document[this.scaleFieldKey], pt: this.Document.panTransformType };
// if (!willZoom && DocumentView._focusHack.length) {
// Doc.BrushDoc(this.props.Document);
@@ -915,7 +914,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (afterFocus?.()) {
this.Document._panX = savedState.px;
this.Document._panY = savedState.py;
- this.Document.scale = savedState.s;
+ this.Document[this.scaleFieldKey] = savedState.s;
this.Document.panTransformType = savedState.pt;
}
}, 500);
@@ -924,7 +923,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
setScaleToZoom = (doc: Doc, scale: number = 0.75) => {
- this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
+ this.Document[this.scaleFieldKey] = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
}
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
@@ -959,6 +958,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
ContentScaling: returnOne,
ContainingCollectionView: this.props.CollectionView,
ContainingCollectionDoc: this.props.Document,
+ docFilters: this.docFilters,
focus: this.focusDocument,
backgroundColor: this.getClusterColor,
backgroundHalo: this.backgroundHalo,
@@ -1125,7 +1125,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}));
if (this.props.isAnnotationOverlay) {
- this.props.Document.scale = Math.max(1, NumCast(this.props.Document.scale));
+ this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey]));
}
return elements;
@@ -1197,7 +1197,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onContextMenu = (e: React.MouseEvent) => {
if (this.props.annotationsKey) return;
- ContextMenu.Instance.addItem({
+ !this.props.isAnnotationOverlay && ContextMenu.Instance.addItem({
description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
this._timelineVisible = !this._timelineVisible;
}), icon: this._timelineVisible ? faEyeSlash : faEye
@@ -1206,7 +1206,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+ optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
optionItems.push({ description: "toggle snap line display", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index 894227d84..f39f8f784 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -232,21 +232,6 @@ export default class InkOptionsMenu extends AntimodeMenu {
return fillPicker;
}
-
-
- @computed get shapeButtons() {
- return <>
- {this._buttons.map((btn, i) => <button
- className="antimodeMenu-button"
- title={`Draw ${btn}`}
- key={btn}
- onPointerDown={action(e => GestureOverlay.Instance.InkShape = btn)}
- style={{ backgroundColor: btn === GestureOverlay.Instance.InkShape ? "121212" : "" }}>
- {this._icons[i]}
- </button>)}
- </>;
- }
-
@computed get shapePicker() {
var currIcon;
if (GestureOverlay.Instance.InkShape === "") {
@@ -313,7 +298,6 @@ export default class InkOptionsMenu extends AntimodeMenu {
<FontAwesomeIcon icon="arrows-alt" size="lg" />
</button>,
this.shapePicker,
- // this.shapeButtons,
this.bezierButton,
this.widthPicker,
this.colorPicker,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 085625e69..5f09fa0ee 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -122,7 +122,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
const tbox = Docs.Create.TextDocument("", {
_width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
- _fontFamily: StrCast(Doc.UserDoc().fontFamily), _backgroundColor: StrCast(Doc.UserDoc().backgroundColor),
+ _fontFamily: StrCast(Doc.UserDoc().fontFamily),
title: "-typed text-"
});
const template = FormattedTextBox.DefaultLayout;
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
new file mode 100644
index 000000000..9c2d5cbff
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -0,0 +1,160 @@
+.collectionGridView-contents {
+ display: flex;
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ flex-direction: column;
+
+ .collectionGridView-gridContainer {
+ height: 100%;
+ overflow-y: auto;
+ background-color: white;
+ overflow-x: hidden;
+
+ display: flex;
+ flex-direction: row;
+
+ .imageBox-cont img {
+ height: auto;
+ width: auto;
+ max-height: 100%;
+ max-width: 100%;
+ }
+
+ .react-grid-layout {
+ width : 100%;
+ }
+
+ .react-grid-item>.react-resizable-handle {
+ z-index: 4; // doesn't work on prezi otherwise
+ }
+
+ .react-grid-item>.react-resizable-handle::after {
+ // grey so it can be seen on the audiobox
+ border-right: 2px solid slategrey;
+ border-bottom: 2px solid slategrey;
+ }
+
+ .rowHeightSlider {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ height: 15px;
+ background: #d3d3d3;
+
+ position: absolute;
+ height: 3;
+ left: 5;
+ top: 30;
+ transform-origin: left;
+ transform: rotate(90deg);
+ outline: none;
+ opacity: 0.7;
+ }
+
+ .rowHeightSlider:hover {
+ opacity: 1;
+ }
+
+ .rowHeightSlider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: darkgrey;
+ opacity: 1;
+ }
+
+ .rowHeightSlider::-moz-range-thumb {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: darkgrey;
+ opacity: 1;
+ }
+ }
+
+ .collectionGridView-addDocumentButton {
+ display: flex;
+ overflow: hidden;
+ margin: auto;
+ width: 90%;
+ cursor: text;
+ min-height: 30px;
+ max-height: 30px;
+ font-size: 75%;
+ letter-spacing: 2px;
+
+ .editableView-input {
+ outline-color: black;
+ letter-spacing: 2px;
+ color: grey;
+ border: 0px;
+ padding: 12px 10px 11px 10px;
+ }
+
+ .editableView-container-editing,
+ .editableView-container-editing-oneLine {
+ display: flex;
+ align-items: center;
+ flex-direction: row;
+ height: 20px;
+
+ width: 100%;
+ color: grey;
+ padding: 10px;
+
+ span::before,
+ span::after {
+ content: "";
+ width: 50%;
+ position: relative;
+ display: inline-block;
+ }
+
+ span::before {
+ margin-right: 10px;
+ }
+
+ span::after {
+ margin-left: 10px;
+ }
+
+ span {
+ position: relative;
+ text-align: center;
+ white-space: nowrap;
+ overflow: visible;
+ display: flex;
+ color: gray;
+ align-items: center;
+ }
+ }
+ }
+
+}
+
+// .documentDecorations-container .documentDecorations-resizer {
+// pointer-events: none;
+// }
+
+// #documentDecorations-bottomRightResizer,
+// #documentDecorations-bottomLeftResizer,
+// #documentDecorations-topRightResizer,
+// #documentDecorations-topLeftResizer {
+// visibility: collapse;
+// }
+
+
+/* Chrome, Safari, Edge, Opera */
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+/* Firefox */
+input[type=number] {
+ -moz-appearance: textfield;
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
new file mode 100644
index 000000000..2015ca930
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -0,0 +1,307 @@
+import { action, computed, Lambda, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from "react";
+import { Doc, Opt } from '../../../../fields/Doc';
+import { documentSchema } from '../../../../fields/documentSchemas';
+import { Id } from '../../../../fields/FieldSymbols';
+import { makeInterface } from '../../../../fields/Schema';
+import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
+import { ContextMenu } from '../../ContextMenu';
+import { ContextMenuProps } from '../../ContextMenuItem';
+import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { CollectionSubView } from '../CollectionSubView';
+import "./CollectionGridView.scss";
+import Grid, { Layout } from "./Grid";
+
+type GridSchema = makeInterface<[typeof documentSchema]>;
+const GridSchema = makeInterface(documentSchema);
+
+@observer
+export class CollectionGridView extends CollectionSubView(GridSchema) {
+ private _containerRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _changeListenerDisposer: Opt<Lambda>; // listens for changes in this.childLayoutPairs
+ private _resetListenerDisposer: Opt<Lambda>; // listens for when the reset button is clicked
+ @observable private _rowHeight: Opt<number>; // temporary store of row height to make change undoable
+ @observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll
+
+ @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+
+ @computed get numCols() { return NumCast(this.props.Document.gridNumCols, 10); }
+ @computed get rowHeight() { return this._rowHeight === undefined ? NumCast(this.props.Document.gridRowHeight, 100) : this._rowHeight; }
+ // sets the default width and height of the grid nodes
+ @computed get defaultW() { return NumCast(this.props.Document.gridDefaultW, 2); }
+ @computed get defaultH() { return NumCast(this.props.Document.gridDefaultH, 2); }
+
+ @computed get colWidthPlusGap() { return (this.props.PanelWidth() - this.margin) / this.numCols; }
+ @computed get rowHeightPlusGap() { return this.rowHeight + this.margin; }
+
+ @computed get margin() { return NumCast(this.props.Document.margin, 10); } // sets the margin between grid nodes
+
+ @computed get flexGrid() { return BoolCast(this.props.Document.gridFlex, true); } // is grid static/flexible i.e. whether nodes be moved around and resized
+ @computed get compaction() { return StrCast(this.props.Document.gridStartCompaction, StrCast(this.props.Document.gridCompaction, "vertical")); } // is grid static/flexible i.e. whether nodes be moved around and resized
+
+ componentDidMount() {
+ this._changeListenerDisposer = reaction(() => this.childLayoutPairs, (pairs) => {
+ const newLayouts: Layout[] = [];
+ const oldLayouts = this.savedLayoutList;
+ pairs.forEach((pair, i) => {
+ const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
+ if (existing) newLayouts.push(existing);
+ else this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ });
+ pairs?.length && this.setLayoutList(newLayouts);
+ }, { fireImmediately: true });
+
+ // updates the layouts if the reset button has been clicked
+ this._resetListenerDisposer = reaction(() => this.props.Document.gridResetLayout, (reset) => {
+ if (reset && this.flexGrid) {
+ this.setLayout(this.childLayoutPairs.map((pair, index) => this.makeLayoutItem(pair.layout, this.unflexedPosition(index))));
+ }
+ this.props.Document.gridResetLayout = false;
+ });
+ }
+
+ componentWillUnmount() {
+ this._changeListenerDisposer?.();
+ this._resetListenerDisposer?.();
+ }
+
+ unflexedPosition(index: number): Omit<Layout, "i"> {
+ return {
+ x: (index % Math.floor(this.numCols / this.defaultW)) * this.defaultW,
+ y: Math.floor(index / Math.floor(this.numCols / this.defaultH)) * this.defaultH,
+ w: this.defaultW,
+ h: this.defaultH,
+ static: true
+ };
+ }
+
+ screenToCell(sx: number, sy: number) {
+ const pt = this.props.ScreenToLocalTransform().transformPoint(sx, sy);
+ const x = Math.floor(pt[0] / this.colWidthPlusGap);
+ const y = Math.floor((pt[1] + this._scroll) / this.rowHeight);
+ return { x, y };
+ }
+
+ makeLayoutItem = (doc: Doc, pos: { x: number, y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
+ return ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
+ }
+
+ addLayoutItem = (layouts: Layout[], layout: Layout) => {
+ const f = layouts.findIndex(l => l.i === layout.i);
+ f !== -1 && layouts.splice(f, 1);
+ layouts.push(layout);
+ return layouts;
+ }
+ /**
+ * @returns the transform that will correctly place the document decorations box.
+ */
+ private lookupIndividualTransform = (layout: Layout) => {
+ const xypos = this.flexGrid ? layout : this.unflexedPosition(this.renderedLayoutList.findIndex(l => l.i === layout.i));
+ const pos = { x: xypos.x * this.colWidthPlusGap + this.margin, y: xypos.y * this.rowHeightPlusGap + this.margin - this._scroll };
+
+ return this.props.ScreenToLocalTransform().translate(-pos.x, -pos.y);
+ }
+
+ /**
+ * @returns the layout list converted from JSON
+ */
+ get savedLayoutList() {
+ return (this.props.Document.gridLayoutString ? JSON.parse(StrCast(this.props.Document.gridLayoutString)) : []) as Layout[];
+ }
+
+ /**
+ * Stores the layout list on the Document as JSON
+ */
+ setLayoutList(layouts: Layout[]) {
+ this.props.Document.gridLayoutString = JSON.stringify(layouts);
+ }
+
+ /**
+ *
+ * @param layout
+ * @param dxf the x- and y-translations of the decorations box as a transform i.e. this.lookupIndividualTransform
+ * @param width
+ * @param height
+ * @returns the `ContentFittingDocumentView` of the node
+ */
+ getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
+ return <ContentFittingDocumentView
+ {...this.props}
+ Document={layout}
+ DataDoc={layout.resolvedDataDoc as Doc}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ backgroundColor={this.props.backgroundColor}
+ ContainingCollectionDoc={this.props.Document}
+ PanelWidth={width}
+ PanelHeight={height}
+ ScreenToLocalTransform={dxf}
+ onClick={this.onChildClickHandler}
+ renderDepth={this.props.renderDepth + 1}
+ parentActive={this.props.active}
+ display={StrCast(this.props.Document.display, "contents")} // sets the css display type of the ContentFittingDocumentView component
+ />;
+ }
+
+ /**
+ * Saves the layouts received from the Grid to the Document.
+ * @param layouts `Layout[]`
+ */
+ @action
+ setLayout = (layoutArray: Layout[]) => {
+ // for every child in the collection, check to see if there's a corresponding grid layout object and
+ // updated layout object. If both exist, which they should, update the grid layout object from the updated object
+ if (this.flexGrid) {
+ const savedLayouts = this.savedLayoutList;
+ this.childLayoutPairs.forEach(({ layout: doc }) => {
+ const gridLayout = savedLayouts.find(gridLayout => gridLayout.i === doc[Id]);
+ if (gridLayout) Object.assign(gridLayout, layoutArray.find(layout => layout.i === doc[Id]) || gridLayout);
+ });
+
+ if (this.props.Document.gridStartCompaction) {
+ undoBatch(() => {
+ this.props.Document.gridCompaction = this.props.Document.gridStartCompaction;
+ this.setLayoutList(savedLayouts);
+ })();
+ this.props.Document.gridStartCompaction = undefined;
+ } else {
+ undoBatch(() => this.setLayoutList(savedLayouts))();
+ }
+ }
+ }
+
+ /**
+ * @returns a list of `ContentFittingDocumentView`s inside wrapper divs.
+ * The key of the wrapper div must be the same as the `i` value of the corresponding layout.
+ */
+ @computed
+ private get contents(): JSX.Element[] {
+ const collector: JSX.Element[] = [];
+ if (this.renderedLayoutList.length === this.childLayoutPairs.length) {
+ this.renderedLayoutList.forEach(l => {
+ const child = this.childLayoutPairs.find(c => c.layout[Id] === l.i);
+ const dxf = () => this.lookupIndividualTransform(l);
+ const width = () => (this.flexGrid ? l.w : this.defaultW) * this.colWidthPlusGap - this.margin;
+ const height = () => (this.flexGrid ? l.h : this.defaultH) * this.rowHeightPlusGap - this.margin;
+ child && collector.push(
+ <div key={child.layout[Id]} className={"document-wrapper" + (this.flexGrid && this.props.isSelected() ? "" : " static")} >
+ {this.getDisplayDoc(child.layout, dxf, width, height)}
+ </div >
+ );
+ });
+ }
+ return collector;
+ }
+
+ /**
+ * @returns a list of `Layout` objects with attributes depending on whether the grid is flexible or static
+ */
+ @computed get renderedLayoutList(): Layout[] {
+ return this.flexGrid ?
+ this.savedLayoutList.map(({ i, x, y, w, h }) => ({
+ i, y, h,
+ x: x + w > this.numCols ? 0 : x, // handles wrapping around of nodes when numCols decreases
+ w: Math.min(w, this.numCols), // reduces width if greater than numCols
+ static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout.lockedPosition, false) // checks if the lock position item has been selected in the context menu
+ })) :
+ this.savedLayoutList.map((layout, index) => Object.assign(layout, this.unflexedPosition(index)));
+ }
+
+ @action
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ const savedLayouts = this.savedLayoutList;
+ const dropped = de.complete.docDragData?.droppedDocuments;
+ if (dropped && super.onInternalDrop(e, de) && savedLayouts.length !== this.childDocs.length) {
+ dropped.forEach(doc => this.addLayoutItem(savedLayouts, this.makeLayoutItem(doc, this.screenToCell(de.x, de.y)))); // shouldn't place all docs in the same cell;
+ this.setLayoutList(savedLayouts);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handles the change in the value of the rowHeight slider.
+ */
+ @action
+ onSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ this._rowHeight = event.currentTarget.valueAsNumber;
+ }
+ @action
+ onSliderDown = (e: React.PointerEvent) => {
+ this._rowHeight = this.rowHeight; // uses _rowHeight during dragging and sets doc's rowHeight when finished so that operation is undoable
+ setupMoveUpEvents(this, e, returnFalse, action(() => {
+ undoBatch(() => this.props.Document.gridRowHeight = this._rowHeight)();
+ this._rowHeight = undefined;
+ }), emptyFunction, false, false);
+ e.stopPropagation();
+ }
+ /**
+ * Adds the display option to change the css display attribute of the `ContentFittingDocumentView`s
+ */
+ onContextMenu = () => {
+ const displayOptionsMenu: ContextMenuProps[] = [];
+ displayOptionsMenu.push({ description: "Contents", event: () => this.props.Document.display = "contents", icon: "copy" });
+ displayOptionsMenu.push({ description: "Undefined", event: () => this.props.Document.display = undefined, icon: "exclamation" });
+ ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" });
+ }
+
+ onPointerDown = (e: React.PointerEvent) => {
+ if (this.props.active(true)) {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse,
+ (e: PointerEvent, doubleTap?: boolean) => {
+ if (doubleTap) {
+ undoBatch(action(() => {
+ const text = Docs.Create.TextDocument("", { _width: 150, _height: 50 });
+ FormattedTextBox.SelectOnLoad = text[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ Doc.AddDocToList(this.props.Document, this.props.fieldKey, text);
+ this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
+ }))();
+ }
+ },
+ false);
+ if (this.props.isSelected(true)) e.stopPropagation();
+ }
+ }
+
+ render() {
+ return (
+ <div className="collectionGridView-contents" ref={this.createDashEventsTarget}
+ style={{ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined }}
+ onContextMenu={this.onContextMenu}
+ onPointerDown={e => this.onPointerDown(e)} >
+ <div className="collectionGridView-gridContainer" ref={this._containerRef}
+ onWheel={e => e.stopPropagation()}
+ onScroll={action(e => {
+ if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
+ else this._scroll = e.currentTarget.scrollTop;
+ })} >
+ <Grid
+ width={this.props.PanelWidth()}
+ nodeList={this.contents.length ? this.contents : null}
+ layout={this.contents.length ? this.renderedLayoutList : undefined}
+ childrenDraggable={this.props.isSelected() ? true : false}
+ numCols={this.numCols}
+ rowHeight={this.rowHeight}
+ setLayout={this.setLayout}
+ transformScale={this.props.ScreenToLocalTransform().Scale}
+ compactType={this.compaction} // determines whether nodes should remain in position, be bound to the top, or to the left
+ preventCollision={BoolCast(this.props.Document.gridPreventCollision)}// determines whether nodes should move out of the way (i.e. collide) when other nodes are dragged over them
+ margin={this.margin}
+ />
+ <input className="rowHeightSlider" type="range"
+ style={{ width: this.props.PanelHeight() - 30 }}
+ min={1} value={this.rowHeight} max={this.props.PanelHeight() - 30}
+ onPointerDown={this.onSliderDown} onChange={this.onSliderChange} />
+ </div>
+ </div >
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionGrid/Grid.tsx b/src/client/views/collections/collectionGrid/Grid.tsx
new file mode 100644
index 000000000..3d2ed0cf9
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/Grid.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react';
+import { observer } from "mobx-react";
+
+import "../../../../../node_modules/react-grid-layout/css/styles.css";
+import "../../../../../node_modules/react-resizable/css/styles.css";
+
+import * as GridLayout from 'react-grid-layout';
+import { Layout } from 'react-grid-layout';
+export { Layout } from 'react-grid-layout';
+
+
+interface GridProps {
+ width: number;
+ nodeList: JSX.Element[] | null;
+ layout: Layout[] | undefined;
+ numCols: number;
+ rowHeight: number;
+ setLayout: (layout: Layout[]) => void;
+ transformScale: number;
+ childrenDraggable: boolean;
+ preventCollision: boolean;
+ compactType: string;
+ margin: number;
+}
+
+/**
+ * Wrapper around the actual GridLayout of `react-grid-layout`.
+ */
+@observer
+export default class Grid extends React.Component<GridProps> {
+ render() {
+ const compactType = this.props.compactType === "vertical" || this.props.compactType === "horizontal" ? this.props.compactType : null;
+ return (
+ <GridLayout className="layout"
+ layout={this.props.layout}
+ cols={this.props.numCols}
+ rowHeight={this.props.rowHeight}
+ width={this.props.width}
+ compactType={compactType}
+ isDroppable={true}
+ isDraggable={this.props.childrenDraggable}
+ isResizable={this.props.childrenDraggable}
+ useCSSTransforms={true}
+ onLayoutChange={this.props.setLayout}
+ preventCollision={this.props.preventCollision}
+ transformScale={1 / this.props.transformScale} // still doesn't work :(
+ margin={[this.props.margin, this.props.margin]}
+ >
+ {this.props.nodeList}
+ </GridLayout>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index c0e1a0232..776266ce6 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -234,6 +234,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
focus={this.props.focus}
+ docFilters={this.docFilters}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 602246d07..1703ff4dc 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -233,6 +233,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
focus={this.props.focus}
+ docFilters={this.docFilters}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 0d6258cf3..d04da8f5b 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -60,9 +60,9 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} />
<div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}>
<div> {ActiveInkWidth() ?? 2}</div>
- <input type="range" value={ActiveInkWidth() ?? 2} defaultValue={2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveInkWidth(e.target.value)} />
+ <input type="range" defaultValue={ActiveInkWidth() ?? 2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveInkWidth(e.target.value)} />
<div> {ActiveInkBezierApprox() ?? 2}</div>
- <input type="range" value={ActiveInkBezierApprox() ?? 2} defaultValue={2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveBezierApprox(e.target.value)} />
+ <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveBezierApprox(e.target.value)} />
<br />
<br />
</div>
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index cce60628d..f140cc6e5 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -39,10 +39,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
@undoBatch
private dropHandler = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
- event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
- const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
- if (droppedDocs?.length) {
- this.dataDoc[fieldKey] = droppedDocs[0];
+ if (dropEvent.complete.docDragData) {
+ event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
+ const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
+ if (droppedDocs?.length) {
+ this.dataDoc[fieldKey] = droppedDocs[0];
+ }
}
}
@@ -76,12 +78,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
const clearButton = (which: string) => {
return <div className={`clear-button ${which}`}
onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
- onClick={e => this.clearDoc(e, `${which}Doc`)}>
+ onClick={e => this.clearDoc(e, `compareBox-${which}`)}>
<FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
</div>;
};
const displayDoc = (which: string) => {
- const whichDoc = Cast(this.dataDoc[`${which}Doc`], Doc, null);
+ const whichDoc = Cast(this.dataDoc[`compareBox-${which}`], Doc, null);
return whichDoc ? <>
<ContentFittingDocumentView {...childProps} Document={whichDoc} />
{clearButton(which)}
@@ -93,7 +95,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
const displayBox = (which: string, index: number, cover: number) => {
return <div className={`${which}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }}
onPointerDown={e => this.registerSliding(e, cover)}
- ref={ele => this.createDropTarget(ele, `${which}Doc`, index)} >
+ ref={ele => this.createDropTarget(ele, `compareBox-${which}`, index)} >
{displayDoc(which)}
</div>;
};
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index a90b4668e..ba075886b 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -1,15 +1,50 @@
import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
+import { Transform } from "nodemailer/lib/xoauth2";
import "react-table/react-table.css";
-import { Doc, Opt, WidthSym, HeightSym } from "../../../fields/Doc";
-import { NumCast, StrCast, Cast } from "../../../fields/Types";
+import { Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { ScriptField } from "../../../fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnOne } from "../../../Utils";
+import { emptyFunction } from "../../../Utils";
+import { dropActionType } from "../../util/DragManager";
+import { CollectionView } from "../collections/CollectionView";
import '../DocumentDecorations.scss';
import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import "./ContentFittingDocumentView.scss";
+interface ContentFittingDocumentViewProps {
+ Document: Doc;
+ DataDocument?: Doc;
+ LayoutDoc?: () => Opt<Doc>;
+ NativeWidth?: () => number;
+ NativeHeight?: () => number;
+ FreezeDimensions?: boolean;
+ LibraryPath: Doc[];
+ renderDepth: number;
+ fitToBox?: boolean;
+ layoutKey?: string;
+ dropAction?: dropActionType;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ focus?: (doc: Doc) => void;
+ CollectionView?: CollectionView;
+ CollectionDoc?: Doc;
+ onClick?: ScriptField;
+ backgroundColor?: (doc: Doc) => string | undefined;
+ getTransform: () => Transform;
+ addDocument?: (document: Doc) => boolean;
+ moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
+ removeDocument?: (document: Doc) => boolean;
+ active: (outsideReaction: boolean) => boolean;
+ whenActiveChanged: (isActive: boolean) => void;
+ addDocTab: (document: Doc, where: string) => boolean;
+ pinToPres: (document: Doc) => void;
+ dontRegisterView?: boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
+ Display?: string;
+}
@observer
export class ContentFittingDocumentView extends React.Component<DocumentViewProps>{
@@ -38,8 +73,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
@computed get panelHeight() { return this.nativeHeight && !this.props.Document._fitWidth ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
private getTransform = () => this.props.ScreenToLocalTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
- private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
- private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
+ private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth && this.props.display !== "contents" ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
+ private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 && this.props.display !== "contents" ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
@computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
@@ -47,7 +82,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
TraceMobx();
return (<div className="contentFittingDocumentView" style={{
width: Math.abs(this.centeringYOffset) > 0.001 ? "auto" : this.props.PanelWidth(),
- height: Math.abs(this.centeringOffset) > 0.0001 ? "auto" : this.props.PanelHeight()
+ height: Math.abs(this.centeringOffset) > 0.0001 ? "auto" : this.props.PanelHeight(),
+ display: this.props.display /* just added for grid */
}}>
{!this.props.Document || !this.props.PanelWidth ? (null) : (
<div className="contentFittingDocumentView-previewDoc"
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
index 0c5239d66..0cf5505cc 100644
--- a/src/client/views/nodes/DocHolderBox.tsx
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -119,6 +119,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
Document={containedDoc}
DataDoc={undefined}
LibraryPath={emptyPath}
+ docFilters={this.props.docFilters}
ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
ContainingCollectionDoc={undefined}
fitToBox={true}
@@ -147,6 +148,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
Document={containedDoc}
DataDoc={undefined}
LibraryPath={emptyPath}
+ docFilters={this.props.docFilters}
ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
ContainingCollectionDoc={undefined}
fitToBox={true}
@@ -198,11 +200,10 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.complete.docDragData) {
- if (de.complete.docDragData.draggedDocuments[0].type === DocumentType.FONTICON) {
- const doc = Cast(de.complete.docDragData.draggedDocuments[0].dragFactory, Doc, null);
- this.layoutDoc.childLayoutTemplate = doc;
- }
+ const docDragData = de.complete.docDragData;
+ if (docDragData?.draggedDocuments[0].type === DocumentType.FONTICON) {
+ const doc = Cast(docDragData.draggedDocuments[0].dragFactory, Doc, null);
+ this.layoutDoc.childLayoutTemplate = doc;
}
}
protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 126e9ac14..f1438fd54 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { Doc, Opt, Field, AclSym, AclPrivate } from "../../../fields/Doc";
import { Cast, StrCast, NumCast } from "../../../fields/Types";
import { OmitKeys, Without, emptyPath } from "../../../Utils";
-import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
+import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { CollectionSchemaView } from "../collections/CollectionSchemaView";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1d98a2628..3205e3050 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -17,10 +17,8 @@ import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { ClientRecommender } from '../../ClientRecommender';
-import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
-import { ClientUtils } from '../../util/ClientUtils';
import { DocumentManager } from "../../util/DocumentManager";
import { SnappingManager } from '../../util/SnappingManager';
import { DragManager, dropActionType } from "../../util/DragManager";
@@ -43,16 +41,17 @@ import "./DocumentView.scss";
import { LinkAnchorBox } from './LinkAnchorBox';
import { RadialMenu } from './RadialMenu';
import React = require("react");
-import { undo } from 'prosemirror-history';
library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
- fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone);
+ fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, fa.faQuestion);
export type DocFocusFunc = () => boolean;
+
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
+ docFilters: () => string[];
FreezeDimensions?: boolean;
NativeWidth: () => number;
NativeHeight: () => number;
@@ -96,6 +95,7 @@ export interface DocumentViewProps {
dontRegisterView?: boolean;
layoutKey?: string;
radialMenu?: String[];
+ display?: string;
}
@observer
@@ -136,8 +136,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.removeEndListeners();
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- console.log(SelectionManager.SelectedDocuments());
- console.log("START");
if (RadialMenu.Instance._display === false) {
this.addHoldMoveListeners();
this.addHoldEndListeners();
@@ -178,8 +176,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@action
onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
- // console.log(InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true));
- // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
@@ -188,12 +184,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "folder", selected: -1 });
- // if (SelectionManager.IsSelected(this, true)) {
- // SelectionManager.SelectDoc(this, false);
- // }
SelectionManager.DeselectAll();
-
-
}
@action
@@ -334,7 +325,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.addDocTab(alias, "onRight");
// UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
//ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
- } else if (this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+ } else if (this.props.Document.links && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
} else {
if ((this.props.Document.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
@@ -510,12 +501,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onPointerDown = (e: React.PointerEvent): void => {
- // console.log(e.button)
- // console.log(e.nativeEvent)
// continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
+ if (SelectionManager.IsSelected(this, true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
// TODO: check here for panning/inking
}
return;
@@ -529,8 +519,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
!e.ctrlKey &&
(e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
!this.Document.inOverlay) {
- e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
-
+ e.stopPropagation();
+ if (SelectionManager.IsSelected(this, true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
}
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -685,13 +675,30 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly") => {
- this.layoutDoc.ACL = this.dataDoc.ACL = acl;
+ this.dataDoc.ACL = this.props.Document.ACL = acl;
DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
const data = d[DataSym];
if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl;
});
}
+ @undoBatch
+ @action
+ testAcl = (acl: "readOnly" | "addOnly" | "ownerOnly") => {
+ this.dataDoc.author = this.props.Document.author = "ADMIN";
+ this.dataDoc.ACL = this.props.Document.ACL = acl;
+ DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
+ if (d.author === Doc.CurrentUserEmail) {
+ d.author = "ADMIN";
+ d.ACL = acl;
+ }
+ const data = d[DataSym];
+ if (data && data.author === Doc.CurrentUserEmail) {
+ data.author = "ADMIN";
+ data.ACL = acl;
+ }
+ });
+ }
@action
onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
@@ -720,11 +727,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.contextMenuItems?.().forEach(item =>
cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
-
let options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
- optionItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
if (!options) {
options = { description: "Options...", subitems: optionItems, icon: "compass" };
@@ -742,7 +747,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
!existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
-
const funcs: ContextMenuProps[] = [];
if (this.Document.onDragStart) {
funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
@@ -752,96 +756,106 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
const more = cm.findByDescription("More...");
- const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
- moreItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
- moreItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
- moreItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
- moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
- moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
- moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
-
- if (!ClientUtils.RELEASE) {
- // let copies: ContextMenuProps[] = [];
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
- // cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
- }
- if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
- moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
- moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
- moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
- }
- moreItems.push({
- description: "Download document", icon: "download", event: async () => {
- const response = await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
- qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' }
- });
- console.log(response ? JSON.parse(response) : undefined);
+ const moreItems = more && "subitems" in more ? more.subitems : [];
+ if (!Doc.UserDoc().noviceMode) {
+ moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
+ moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+
+ if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
+ moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
+ moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
+ moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
}
- // const a = document.createElement("a");
- // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
- // a.href = url;
- // a.download = `DocExport-${this.props.Document[Id]}.zip`;
- // a.click();
- });
-
- const recommender_subitems: ContextMenuProps[] = [];
-
- recommender_subitems.push({
- description: "Internal recommendations",
- event: () => this.recommender(),
- icon: "brain"
- });
-
- const ext_recommender_subitems: ContextMenuProps[] = [];
-
- ext_recommender_subitems.push({
- description: "arXiv",
- event: () => this.externalRecommendation("arxiv"),
- icon: "brain"
- });
- ext_recommender_subitems.push({
- description: "Bing",
- event: () => this.externalRecommendation("bing"),
- icon: "brain"
- });
-
- recommender_subitems.push({
- description: "External recommendations",
- subitems: ext_recommender_subitems,
- icon: "brain"
- });
-
+ moreItems.push({
+ description: "Download document", icon: "download", event: async () => {
+ const response = await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
+ qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' }
+ });
+ console.log(response ? JSON.parse(response) : undefined);
+ }
+ // const a = document.createElement("a");
+ // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+ // a.href = url;
+ // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+ // a.click();
+ });
+ }
+ moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
- moreItems.push({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" });
- moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
- moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
+ moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
!more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
-
cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
- runInAction(() => {
- const setWriteMode = (mode: DocServer.WriteMode) => {
- DocServer.AclsMode = mode;
- const mode1 = mode;
- const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
- DocServer.setFieldWriteMode("x", mode1);
- DocServer.setFieldWriteMode("y", mode1);
- DocServer.setFieldWriteMode("_width", mode1);
- DocServer.setFieldWriteMode("_height", mode1);
-
- DocServer.setFieldWriteMode("_panX", mode2);
- DocServer.setFieldWriteMode("_panY", mode2);
- DocServer.setFieldWriteMode("scale", mode2);
- DocServer.setFieldWriteMode("_viewType", mode2);
- };
- const aclsMenu: ContextMenuProps[] = [];
- aclsMenu.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
- aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
- aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
- aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
- aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
- cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
- });
+ const help = cm.findByDescription("Help...");
+ const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
+ helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
+ helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+ cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
+
+ const existingAcls = cm.findByDescription("Privacy...");
+ const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
+ aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
+ aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
+ aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
+ aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
+ aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
+ !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+
+ // const recommender_subitems: ContextMenuProps[] = [];
+
+ // recommender_subitems.push({
+ // description: "Internal recommendations",
+ // event: () => this.recommender(),
+ // icon: "brain"
+ // });
+
+ // const ext_recommender_subitems: ContextMenuProps[] = [];
+
+ // ext_recommender_subitems.push({
+ // description: "arXiv",
+ // event: () => this.externalRecommendation("arxiv"),
+ // icon: "brain"
+ // });
+ // ext_recommender_subitems.push({
+ // description: "Bing",
+ // event: () => this.externalRecommendation("bing"),
+ // icon: "brain"
+ // });
+
+ // recommender_subitems.push({
+ // description: "External recommendations",
+ // subitems: ext_recommender_subitems,
+ // icon: "brain"
+ // });
+
+
+ //moreItems.push({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" });
+ //moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
+ //moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
+
+ // runInAction(() => {
+ // const setWriteMode = (mode: DocServer.WriteMode) => {
+ // DocServer.AclsMode = mode;
+ // const mode1 = mode;
+ // const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
+ // DocServer.setFieldWriteMode("x", mode1);
+ // DocServer.setFieldWriteMode("y", mode1);
+ // DocServer.setFieldWriteMode("_width", mode1);
+ // DocServer.setFieldWriteMode("_height", mode1);
+
+ // DocServer.setFieldWriteMode("_panX", mode2);
+ // DocServer.setFieldWriteMode("_panY", mode2);
+ // DocServer.setFieldWriteMode("scale", mode2);
+ // DocServer.setFieldWriteMode("_viewType", mode2);
+ // };
+ // const aclsMenu: ContextMenuProps[] = [];
+ // aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
+ // aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
+ // aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
+ // aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
+ // cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
+ // });
runInAction(() => {
if (!this.topMost && !(e instanceof Touch)) {
// DocumentViews should stop propagation of this event
@@ -983,6 +997,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
TraceMobx();
return (<>
<DocumentContentsView key={1}
+ docFilters={this.props.docFilters}
ContainingCollectionView={this.props.ContainingCollectionView}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
NativeWidth={this.NativeWidth}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index e9dc43bd8..305c04a90 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -27,6 +27,7 @@ export interface FieldViewProps {
LibraryPath: Doc[];
onClick?: ScriptField;
dropAction: dropActionType;
+ docFilters: () => string[];
isSelected: (outsideReaction?: boolean) => boolean;
select: (isCtrlPressed: boolean) => void;
rootSelected: (outsideReaction?: boolean) => boolean;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index cabf30c13..84d49681c 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -182,11 +182,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
!existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
-
-
- const existingMore = ContextMenu.Instance.findByDescription("More...");
- const mores: ContextMenuProps[] = existingMore && "subitems" in existingMore ? existingMore.subitems : [];
- !existingMore && ContextMenu.Instance.addItem({ description: "More...", subitems: mores, icon: "hand-point-right" });
}
}
@@ -478,6 +473,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
CollectionView={undefined}
ScreenToLocalTransform={this.screenToLocalTransform}
renderDepth={this.props.renderDepth + 1}
+ docFilters={this.props.docFilters}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
{this.contentFunc}
</CollectionFreeFormView>
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index e983852ea..4442ee2eb 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -18,6 +18,7 @@ import { KeyValuePair } from "./KeyValuePair";
import React = require("react");
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
+import e = require("express");
export type KVPScript = {
script: CompiledScript;
@@ -31,10 +32,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
private _mainCont = React.createRef<HTMLDivElement>();
private _keyHeader = React.createRef<HTMLTableHeaderCellElement>();
+ private _keyInput = React.createRef<HTMLInputElement>();
+ private _valInput = React.createRef<HTMLInputElement>();
@observable private rows: KeyValuePair[] = [];
- @observable private _keyInput: string = "";
- @observable private _valueInput: string = "";
+
@computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
get fieldDocToLayout() { return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; }
@@ -42,10 +44,11 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
onEnterKey = (e: React.KeyboardEvent): void => {
if (e.key === 'Enter') {
e.stopPropagation();
- if (this._keyInput && this._valueInput && this.fieldDocToLayout) {
- if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput, this._valueInput)) {
- this._keyInput = "";
- this._valueInput = "";
+ if (this._keyInput.current?.value && this._valInput.current?.value && this.fieldDocToLayout) {
+ if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput.current.value, this._valInput.current.value)) {
+ this._keyInput.current.value = "";
+ this._valInput.current.value = "";
+ document.body.focus();
}
}
}
@@ -103,7 +106,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
rowHeight = () => 30;
- createTable = () => {
+ @computed get createTable() {
const doc = this.fieldDocToLayout;
if (!doc) {
return <tr><td>Loading...</td></tr>;
@@ -136,30 +139,18 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
return rows;
}
-
- @action
- keyChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._keyInput = e.currentTarget.value;
+ @computed get newKeyValue() {
+ return <tr className="keyValueBox-valueRow">
+ <td className="keyValueBox-td-key" onClick={(e) => { this._keyInput.current!.select(); e.stopPropagation(); }} style={{ width: `${100 - this.splitPercentage}%` }}>
+ <input style={{ width: "100%" }} ref={this._keyInput} type="text" placeholder="Key" />
+ </td>
+ <td className="keyValueBox-td-value" onClick={(e) => { this._valInput.current!.select(); e.stopPropagation(); }} style={{ width: `${this.splitPercentage}%` }}>
+ <input style={{ width: "100%" }} ref={this._valInput} type="text" placeholder="Value" onKeyDown={this.onEnterKey} />
+ </td>
+ </tr>
}
@action
- valueChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._valueInput = e.currentTarget.value;
- }
-
- newKeyValue = () =>
- (
- <tr className="keyValueBox-valueRow">
- <td className="keyValueBox-td-key" style={{ width: `${100 - this.splitPercentage}%` }}>
- <input style={{ width: "100%" }} type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} />
- </td>
- <td className="keyValueBox-td-value" style={{ width: `${this.splitPercentage}%` }}>
- <input style={{ width: "100%" }} type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyDown={this.onEnterKey} />
- </td>
- </tr>
- )
-
- @action
onDividerMove = (e: PointerEvent): void => {
const nativeWidth = this._mainCont.current!.getBoundingClientRect();
this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
@@ -260,8 +251,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
>Key</th>
<th className="keyValueBox-fields" style={{ width: `${this.splitPercentage}%` }}>Fields</th>
</tr>
- {this.createTable()}
- {this.newKeyValue()}
+ {this.createTable}
+ {this.newKeyValue}
</tbody>
</table>
{dividerDragger}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 3cbe3e494..4568a6b16 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,7 +1,7 @@
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc, Field, Opt } from '../../../fields/Doc';
-import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
@@ -56,6 +56,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
Document: this.props.doc,
DataDoc: this.props.doc,
LibraryPath: [],
+ docFilters:returnEmptyFilter,
ContainingCollectionView: undefined,
ContainingCollectionDoc: undefined,
fieldKey: this.props.keyName,
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index ad9e49369..360ead18e 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -77,7 +77,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
paddingBottom: NumCast(this.layoutDoc._yPadding),
whiteSpace: this.layoutDoc._singleLine ? "pre" : "pre-wrap"
}} >
- {StrCast(this.rootDoc.text, StrCast(this.rootDoc.title))}
+ {StrCast(this.rootDoc[this.fieldKey], StrCast(this.rootDoc.title))}
</div>
<div className="labelBox-fieldKeyParams" >
{!missingParams?.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 83245a89c..3c232eff8 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -117,7 +117,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
const y = this.props.PanelWidth() > 1 ? NumCast(this.layoutDoc[this.fieldKey + "_y"], 100) : 0;
const c = StrCast(this.layoutDoc.backgroundColor, "lightblue");
const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
- const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15;
+ const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25;
const timecode = this.dataDoc[anchor + "_timecode"];
const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title) + (timecode !== undefined ? ":" + timecode : "");
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fae216f17..985fb4363 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -146,11 +146,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
<FontAwesomeIcon style={{ color: "white" }} icon={"arrow-right"} size="sm" />
</button>
</>;
+ const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`;
return !this.active() ? (null) :
(<div className="pdfBox-ui" onKeyDown={e => e.keyCode === KeyCodes.BACKSPACE || e.keyCode === KeyCodes.DELETE ? e.stopPropagation() : true}
onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none" }}>
<div className="pdfBox-overlayCont" key="cont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
- <button className="pdfBox-overlayButton" title="Open Search Bar" />
+ <button className="pdfBox-overlayButton" title={searchTitle} />
<input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged} onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, !e.shiftKey)} />
<button title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" color="white" /></button>
@@ -161,14 +162,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
<FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="lg" />
</button>
</div>
- <button className="pdfBox-overlayButton" key="search" onClick={action(() => this._searching = !this._searching)} title="Open Search Bar" style={{ bottom: 0, right: 0 }}>
+ <button className="pdfBox-overlayButton" key="search" onClick={action(() => {
+ this._searching = !this._searching;
+ this.search("mxytzlaf", true);
+ })} title={searchTitle} style={{ bottom: 0, right: 0 }}>
<div className="pdfBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
<div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
<FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="lg" /></div>
</button>
<input value={`${(this.Document.curPage || 1)}`}
onChange={e => this.gotoPage(Number(e.currentTarget.value))}
- style={{ left: 5, top: 5, height: "20px", width: "20px", position: "absolute", pointerEvents: "all" }}
+ style={{ left: 5, top: 5, height: "20px", width: "3ch", position: "absolute", pointerEvents: "all" }}
onClick={action(() => this._pageControls = !this._pageControls)} />
{this._pageControls ? pageBtns : (null)}
<div className="pdfBox-settingsCont" key="settings" onPointerDown={(e) => e.stopPropagation()}>
@@ -234,7 +238,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
<PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
- addDocTab={this.props.addDocTab} focus={this.props.focus}
+ addDocTab={this.props.addDocTab} focus={this.props.focus} docFilters={this.props.docFilters}
pinToPres={this.props.pinToPres} addDocument={this.addDocument}
Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
@@ -248,7 +252,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
_pdfjsRequested = false;
render() {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
- if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document._scrollY !== undefined) this._everActive = true;
+ if (this.props.isSelected() || this.props.renderDepth === 0 || this.props.Document._scrollY !== undefined) this._everActive = true;
if (pdfUrl && (this._everActive || this.props.Document._scrollTop || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) {
if (pdfUrl instanceof PdfField && this._pdf) {
return this.renderPdfView;
diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss
index 43695f00d..a937364a8 100644
--- a/src/client/views/nodes/ScriptingBox.scss
+++ b/src/client/views/nodes/ScriptingBox.scss
@@ -5,31 +5,220 @@
flex-direction: column;
background-color: rgb(241, 239, 235);
padding: 10px;
+
+ .boxed {
+ border: 1px solid black;
+ background-color: rgb(212, 198, 179);
+ width: auto;
+ height: auto;
+ font-size: 12px;
+ position: absolute;
+ z-index: 100;
+ padding: 5px;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
.scriptingBox-inputDiv {
display: flex;
flex-direction: column;
- height: calc(100% - 30px);
+ height: 100%;
+ max-height: 100%;
+ overflow: hidden;
+ table-layout: fixed;
+
+ white-space: nowrap;
+
+ .scriptingBox-wrapper {
+ width: 100%;
+ height: 100%;
+ max-height: calc(100%-30px);
+ display: flex;
+ flex-direction: row;
+ overflow: auto;
+ justify-content: center;
+
+ .descriptor {
+ overflow: hidden;
+ }
+
+ .scriptingBox-textArea, .scriptingBox-textArea-inputs {
+ flex: 70;
+ height: 100%;
+ max-width: 95%;
+ min-width: none;
+ box-sizing: border-box;
+ resize: none;
+ padding: 7px;
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ body {
+ font-family: Arial, Helvetica, sans-serif;
+ border: 1px solid red;
+ }
+
+ .rta {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ margin-bottom: 60px !important;
+ overflow-y: auto;
+ overflow-x: hidden;
+ overflow: hidden;
+ }
+
+ .rta__textarea {
+ width: 100%;
+ height: 100%;
+ font-size: 10px;
+ }
+
+ .rta__autocomplete {
+ position: absolute;
+ display: block;
+ margin-top: 1em;
+ }
+
+ .rta__autocomplete--top {
+ margin-top: 0;
+ margin-bottom: 1em;
+ max-height: 100px;
+ }
+
+ .rta__list {
+ margin: 0;
+ padding: 0;
+ background: #fff;
+ border: 1px solid #dfe2e5;
+ border-radius: 3px;
+ box-shadow: 0 0 5px rgba(27, 31, 35, 0.1);
+ list-style: none;
+ overflow-y: auto;
+ overflow-x: hidden;
+ }
+
+ .rta__entity {
+ background: white;
+ width: 100%;
+ text-align: left;
+ outline: none;
+ overflow-y: auto;
+ }
+
+ .rta__entity:hover {
+ cursor: pointer;
+ }
+
+ .rta__entity>* {
+ padding-left: 4px;
+ padding-right: 4px;
+ }
+
+ .rta__entity--selected {
+ color: #fff;
+ text-decoration: none;
+ background: #0366d6;
+ }
+ }
+
+ .scriptingBox-textArea-inputs {
+ max-width: 100%;
+ height: 40%;
+ width: 100%;
+ resize: none;
+ }
+ .scriptingBox-textArea-script {
+ resize: none;
+ height: 100%;
+ }
+
+ .scriptingBox-plist {
+ flex: 30;
+ width: 30%;
+ height: 100%;
+ box-sizing: border-box;
+ resize: none;
+ padding: 2px;
+ overflow-y: auto;
+
+ .scriptingBox-pborder {
+ background-color: rgb(241, 239, 235);
+ }
+
+ .scriptingBox-viewBase {
+ display: flex;
+
+ .scriptingBox-viewPicker {
+ font-size: 75%;
+ //text-transform: uppercase;
+ letter-spacing: 2px;
+ background: rgb(238, 238, 238);
+ color: grey;
+ outline-color: black;
+ border: none;
+ padding: 12px 10px 11px 10px;
+ }
+
+ .scriptingBox-viewPicker:active {
+ outline-color: black;
+ }
+
+ .commandEntry-outerDiv {
+ pointer-events: all;
+ background-color: gray;
+ display: flex;
+ flex-direction: row;
+ }
+ }
+ }
+
+ .scriptingBox-paramNames {
+ flex: 60;
+ width: 60%;
+ box-sizing: border-box;
+ resize: none;
+ padding: 7px;
+ overflow-y: clip;
+ }
+
+ .scriptingBox-paramInputs {
+ flex: 40;
+ width: 40%;
+ box-sizing: border-box;
+ resize: none;
+ padding: 2px;
+ overflow-y: hidden;
+ }
+ }
+
.scriptingBox-errorMessage {
overflow: auto;
+ background: "red";
+ background-color: "red";
+ height: 45px;
}
+
.scripting-params {
- background: "beige";
- }
- .scriptingBox-textArea {
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- resize: none;
- padding: 7px;
+ background: rgb(241, 239, 235);
+ outline-style: solid;
+ outline-color: black;
}
}
.scriptingBox-toolbar {
width: 100%;
height: 30px;
+ overflow: hidden;
+
.scriptingBox-button {
- width: 50%
+ font-size: xx-small;
+ width: 50%;
+ resize: auto;
}
- }
-}
+ .scriptingBox-button-third {
+ width: 33%;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 0944edf60..8912b113c 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,19 +1,28 @@
-import { action, observable, computed } from "mobx";
+import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete";
+import "@webscopeio/react-textarea-autocomplete/style.css";
+import { action, computed, observable, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
+import { Doc } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
-import { createSchema, makeInterface, listSpec } from "../../../fields/Schema";
+import { List } from "../../../fields/List";
+import { createSchema, listSpec, makeInterface } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
-import { StrCast, ScriptCast, Cast } from "../../../fields/Types";
+import { Cast, NumCast, ScriptCast, StrCast, BoolCast } from "../../../fields/Types";
+import { returnEmptyString } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
import { InteractionUtils } from "../../util/InteractionUtils";
-import { CompileScript, isCompileError, ScriptParam } from "../../util/Scripting";
+import { CompileScript, Scripting, ScriptParam } from "../../util/Scripting";
+import { ScriptManager } from "../../util/ScriptManager";
+import { ContextMenu } from "../ContextMenu";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { EditableView } from "../EditableView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import "./ScriptingBox.scss";
import { OverlayView } from "../OverlayView";
import { DocumentIconContainer } from "./DocumentIcon";
-import { List } from "../../../fields/List";
+import "./ScriptingBox.scss";
+import { TraceMobx } from "../../../fields/util";
+const _global = (window /* browser */ || global /* node */) as any;
const ScriptingSchema = createSchema({});
type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentSchema]>;
@@ -21,78 +30,664 @@ const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema);
@observer
export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) {
+
+ private dropDisposer?: DragManager.DragDropDisposer;
protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); }
-
- _overlayDisposer?: () => void;
+ private _overlayDisposer?: () => void;
+ private _caretPos = 0;
@observable private _errorMessage: string = "";
+ @observable private _applied: boolean = false;
+ @observable private _function: boolean = false;
+ @observable private _spaced: boolean = false;
+
+ @observable private _scriptKeys: any = Scripting.getGlobals();
+ @observable private _scriptingDescriptions: any = Scripting.getDescriptions();
+ @observable private _scriptingParams: any = Scripting.getParameters();
+
+ @observable private _currWord: string = "";
+ @observable private _suggestions: string[] = [];
+
+ @observable private _suggestionBoxX: number = 0;
+ @observable private _suggestionBoxY: number = 0;
+ @observable private _lastChar: string = "";
+
+ @observable private _suggestionRef: any = React.createRef();
+ @observable private _scriptTextRef: any = React.createRef();
+
+ @observable private _selection: any = 0;
+
+ @observable private _paramSuggestion: boolean = false;
+ @observable private _scriptSuggestedParams: any = "";
+ @observable private _scriptParamsText: any = "";
+
+ // vars included in fields that store parameters types and names and the script itself
+ @computed({ keepAlive: true }) get paramsNames() { return this.compileParams.map(p => p.split(":")[0].trim()); }
+ @computed({ keepAlive: true }) get paramsTypes() { return this.compileParams.map(p => p.split(":")[1].trim()); }
+ @computed({ keepAlive: true }) get rawScript() { return StrCast(this.dataDoc[this.props.fieldKey + "-rawScript"], ""); }
+ @computed({ keepAlive: true }) get functionName() { return StrCast(this.dataDoc[this.props.fieldKey + "-functionName"], ""); }
+ @computed({ keepAlive: true }) get functionDescription() { return StrCast(this.dataDoc[this.props.fieldKey + "-functionDescription"], ""); }
+ @computed({ keepAlive: true }) get compileParams() { return Cast(this.dataDoc[this.props.fieldKey + "-params"], listSpec("string"), []); }
- @computed get rawScript() { return StrCast(this.dataDoc[this.props.fieldKey + "-rawScript"], StrCast(this.layoutDoc[this.props.fieldKey + "-rawScript"])); }
- @computed get compileParams() { return Cast(this.dataDoc[this.props.fieldKey + "-params"], listSpec("string"), Cast(this.layoutDoc[this.props.fieldKey + "-params"], listSpec("string"), [])); }
set rawScript(value) { this.dataDoc[this.props.fieldKey + "-rawScript"] = value; }
- set compileParams(value) { this.dataDoc[this.props.fieldKey + "-params"] = value; }
+ set functionName(value) { this.dataDoc[this.props.fieldKey + "-functionName"] = value; }
+ set functionDescription(value) { this.dataDoc[this.props.fieldKey + "-functionDescription"] = value; }
+
+ set compileParams(value) { this.dataDoc[this.props.fieldKey + "-params"] = new List<string>(value); }
+
+ getValue(result: any, descrip: boolean) {
+ if (typeof result === "object") {
+ const text = descrip ? result[1] : result[2];
+ return text !== undefined ? text : "";
+ } else {
+ return "";
+ }
+ }
@action
componentDidMount() {
- this.rawScript = ScriptCast(this.dataDoc[this.props.fieldKey])?.script?.originalScript || this.rawScript;
+ this.rawScript = ScriptCast(this.dataDoc[this.props.fieldKey])?.script?.originalScript ?? this.rawScript;
+
+ const observer = new _global.ResizeObserver(action((entries: any) => {
+ const area = document.querySelector('textarea');
+ if (area) {
+ for (const { } of entries) {
+ const getCaretCoordinates = require('textarea-caret');
+ const caret = getCaretCoordinates(area, this._selection);
+ this.resetSuggestionPos(caret);
+ }
+ }
+ }));
+ observer.observe(document.getElementsByClassName("scriptingBox")[0]);
}
- componentWillUnmount() { this._overlayDisposer?.(); }
+ @action
+ resetSuggestionPos(caret: any) {
+ if (!this._suggestionRef.current || !this._scriptTextRef.current) return;
+ console.log('(top, left, height) = (%s, %s, %s)', caret.top, caret.left, caret.height);
+ const suggestionWidth = this._suggestionRef.current.offsetWidth;
+ const scriptWidth = this._scriptTextRef.current.offsetWidth;
+ const top = caret.top;
+ const x = this.dataDoc.x;
+ let left = caret.left;
+ if ((left + suggestionWidth) > (x + scriptWidth)) {
+ const diff = (left + suggestionWidth) - (x + scriptWidth);
+ left = left - diff;
+ }
+
+ this._suggestionBoxX = left;
+ this._suggestionBoxY = top;
+ }
+ componentWillUnmount() {
+ this._overlayDisposer?.();
+ }
+
+ protected createDashEventsTarget = (ele: HTMLDivElement, dropFunc: (e: Event, de: DragManager.DropEvent) => void) => { //used for stacking and masonry view
+ if (ele) {
+ this.dropDisposer?.();
+ this.dropDisposer = DragManager.MakeDropTarget(ele, dropFunc, this.layoutDoc);
+ }
+ }
+
+ // only included in buttons, transforms scripting UI to a button
+ @action
+ onFinish = () => {
+ this.rootDoc.layoutKey = "layout";
+ this.rootDoc._height = 50;
+ this.rootDoc._width = 100;
+ this.dataDoc.documentText = this.rawScript;
+ }
+
+ // displays error message
+ @action
+ onError = (error: any) => {
+ this._errorMessage = error?.message ? error.message : error?.map((entry: any) => entry.messageText).join(" ") || "";
+ }
+
+ // checks if the script compiles using CompileScript method and inputting params
@action
onCompile = () => {
- const params = this.compileParams.reduce((o: ScriptParam, p: string) => { o[p] = "any"; return o; }, {} as ScriptParam);
+ const params: ScriptParam = {};
+ this.compileParams.forEach(p => params[p.split(":")[0].trim()] = p.split(":")[1].trim());
+
const result = CompileScript(this.rawScript, {
editable: true,
transformer: DocumentIconContainer.getTransformer(),
params,
typecheck: false
});
- this._errorMessage = isCompileError(result) ? result.errors.map(e => e.messageText).join("\n") : "";
- return this.dataDoc[this.props.fieldKey] = result.compiled ? new ScriptField(result) : undefined;
+ this.dataDoc.documentText = this.rawScript;
+ this.dataDoc.data = result.compiled ? new ScriptField(result) : undefined;
+ this.onError(result.compiled ? undefined : result.errors);
+ return result.compiled;
}
+ // checks if the script compiles and then runs the script
@action
onRun = () => {
- this.onCompile()?.script.run({}, err => this._errorMessage = err.map((e: any) => e.messageText).join("\n"));
+ if (this.onCompile()) {
+ const bindings: { [name: string]: any } = {};
+ this.paramsNames.forEach(key => bindings[key] = this.dataDoc[key]);
+ // binds vars so user doesnt have to refer to everything as self.<var>
+ ScriptCast(this.dataDoc.data, null)?.script.run({ self: this.rootDoc, this: this.layoutDoc, ...bindings }, this.onError);
+ }
+ }
+
+ // checks if the script compiles and switches to applied UI
+ @action
+ onApply = () => {
+ if (this.onCompile()) {
+ this._applied = true;
+ }
}
+ @action
+ onEdit = () => {
+ this._errorMessage = "";
+ this._applied = false;
+ this._function = false;
+ }
+
+ @action
+ onSave = () => {
+ if (this.onCompile()) {
+ this._function = true;
+ } else {
+ this._errorMessage = "Can not save script, does not compile";
+ }
+ }
+
+ @action
+ onCreate = () => {
+ this._errorMessage = "";
+
+ if (this.functionName.length === 0) {
+ this._errorMessage = "Must enter a function name";
+ return false;
+ }
+
+ if (this.functionName.indexOf(" ") > 0) {
+ this._errorMessage = "Name can not include spaces";
+ return false;
+ }
+
+ if (this.functionName.indexOf(".") > 0) {
+ this._errorMessage = "Name can not include '.'";
+ return false;
+ }
+
+ this.dataDoc.name = this.functionName;
+ this.dataDoc.description = this.functionDescription;
+ //this.dataDoc.parameters = this.compileParams;
+ this.dataDoc.script = this.rawScript;
+
+ ScriptManager.Instance.addScript(this.dataDoc);
+
+ this._scriptKeys = Scripting.getGlobals();
+ this._scriptingDescriptions = Scripting.getDescriptions();
+ this._scriptingParams = Scripting.getParameters();
+ }
+
+ // overlays document numbers (ex. d32) over all documents when clicked on
onFocus = () => {
this._overlayDisposer?.();
this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
}
+ // sets field of the corresponding field key (param name) to be dropped document
+ @action
+ onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
+ this.dataDoc[fieldKey] = de.complete.docDragData?.droppedDocuments[0];
+ e.stopPropagation();
+ }
+
+ // deletes a param from all areas in which it is stored
+ @action
+ onDelete = (num: number) => {
+ this.dataDoc[this.paramsNames[num]] = undefined;
+ this.compileParams.splice(num, 1);
+ return true;
+ }
+
+ // sets field of the param name to the selected value in drop down box
+ @action
+ viewChanged = (e: React.ChangeEvent, name: string) => {
+ //@ts-ignore
+ const val = e.target.selectedOptions[0].value;
+ this.dataDoc[name] = val[0] === "S" ? val.substring(1) : val[0] === "N" ? parseInt(val.substring(1)) : val.substring(1) === "true";
+ }
+
+ // creates a copy of the script document
+ onCopy = () => {
+ const copy = Doc.MakeCopy(this.rootDoc, true);
+ copy.x = NumCast(this.dataDoc.x) + NumCast(this.dataDoc._width);
+ this.props.addDocument?.(copy);
+ }
+
+ // adds option to create a copy to the context menu
+ specificContextMenu = (): void => {
+ const existingOptions = ContextMenu.Instance.findByDescription("Options...");
+ const options = existingOptions && "subitems" in existingOptions ? existingOptions.subitems : [];
+ options.push({ description: "Create a Copy", event: this.onCopy, icon: "copy" });
+ !existingOptions && ContextMenu.Instance.addItem({ description: "Options...", subitems: options, icon: "hand-point-right" });
+ }
+
+ renderFunctionInputs() {
+ const descriptionInput =
+ <textarea
+ className="scriptingBox-textarea-inputs"
+ onChange={e => this.functionDescription = e.target.value}
+ placeholder="enter description here"
+ value={this.functionDescription}
+ />;
+ const nameInput =
+ <textarea
+ className="scriptingBox-textarea-inputs"
+ onChange={e => this.functionName = e.target.value}
+ placeholder="enter name here"
+ value={this.functionName}
+ />;
+
+ return <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()} >
+ <div className="scriptingBox-wrapper" style={{ maxWidth: "100%" }}>
+ <div className="container" style={{ maxWidth: "100%" }}>
+ <div className="descriptor" style={{ textAlign: "center", display: "inline-block", maxWidth: "100%" }}> Enter a function name: </div>
+ <div style={{ maxWidth: "100%" }}> {nameInput}</div>
+ <div className="descriptor" style={{ textAlign: "center", display: "inline-block", maxWidth: "100%" }}> Enter a function description: </div>
+ <div style={{ maxWidth: "100%" }}>{descriptionInput}</div>
+ </div>
+ </div>
+ {this.renderErrorMessage()}
+ </div>;
+ }
+
+ renderErrorMessage() {
+ return !this._errorMessage ? (null) : <div className="scriptingBox-errorMessage"> {this._errorMessage} </div>;
+ }
+
+ // rendering when a doc's value can be set in applied UI
+ renderDoc(parameter: string) {
+ return <div className="scriptingBox-paramInputs" onFocus={this.onFocus} onBlur={() => this._overlayDisposer?.()}
+ ref={ele => ele && this.createDashEventsTarget(ele, (e, de) => this.onDrop(e, de, parameter))} >
+ <EditableView display={"block"} maxHeight={72} height={35} fontSize={14}
+ contents={this.dataDoc[parameter]?.title ?? "undefined"}
+ GetValue={() => this.dataDoc[parameter]?.title ?? "undefined"}
+ SetValue={action((value: string) => {
+ const script = CompileScript(value, {
+ addReturn: true,
+ typecheck: false,
+ transformer: DocumentIconContainer.getTransformer()
+ });
+ const results = script.compiled && script.run();
+ if (results && results.success) {
+ this._errorMessage = "";
+ this.dataDoc[parameter] = results.result;
+ return true;
+ }
+ this._errorMessage = "invalid document";
+ return false;
+ })}
+ />
+ </div>;
+ }
+
+ // rendering when a string's value can be set in applied UI
+ renderBasicType(parameter: string, isNum: boolean) {
+ const strVal = (isNum ? NumCast(this.dataDoc[parameter]).toString() : StrCast(this.dataDoc[parameter]));
+ return <div className="scriptingBox-paramInputs" style={{ overflowY: "hidden" }}>
+ <EditableView display={"block"} maxHeight={72} height={35} fontSize={14}
+ contents={strVal ?? "undefined"}
+ GetValue={() => strVal ?? "undefined"}
+ SetValue={action((value: string) => {
+ const setValue = isNum ? parseInt(value) : value;
+ if (setValue !== undefined && setValue !== " ") {
+ this._errorMessage = "";
+ this.dataDoc[parameter] = setValue;
+ return true;
+ }
+ this._errorMessage = "invalid input";
+ return false;
+ })}
+ />
+ </div>;
+ }
+
+ // rendering when an enum's value can be set in applied UI (drop down box)
+ renderEnum(parameter: string, types: (string | boolean | number)[]) {
+ return <div className="scriptingBox-paramInputs">
+ <div className="scriptingBox-viewBase">
+ <div className="commandEntry-outerDiv">
+ <select className="scriptingBox-viewPicker"
+ onPointerDown={e => e.stopPropagation()}
+ onChange={e => this.viewChanged(e, parameter)}
+ value={typeof (this.dataDoc[parameter]) === "string" ? "S" + StrCast(this.dataDoc[parameter]) :
+ typeof (this.dataDoc[parameter]) === "number" ? "N" + NumCast(this.dataDoc[parameter]) :
+ "B" + BoolCast(this.dataDoc[parameter])}>
+ {types.map(type =>
+ <option className="scriptingBox-viewOption" value={(typeof (type) === "string" ? "S" : typeof (type) === "number" ? "N" : "B") + type}> {type.toString()} </option>
+ )}
+ </select>
+ </div>
+ </div>
+ </div>;
+ }
+
+ // setting a parameter (checking type and name before it is added)
+ compileParam(value: string, whichParam?: number) {
+ if (value.includes(":")) {
+ const ptype = value.split(":")[1].trim();
+ const pname = value.split(":")[0].trim();
+ if (ptype === "Doc" || ptype === "string" || ptype === "number" || ptype === "boolean" || ptype.split("|")[1]) {
+ if ((whichParam !== undefined && pname === this.paramsNames[whichParam]) || !this.paramsNames.includes(pname)) {
+ this._errorMessage = "";
+ if (whichParam !== undefined) {
+ this.compileParams[whichParam] = value;
+ } else {
+ this.compileParams = [...value.split(";").filter(s => s), ...this.compileParams];
+ }
+ return true;
+ }
+ this._errorMessage = "this name has already been used";
+ } else {
+ this._errorMessage = "this type is not supported";
+ }
+ } else {
+ this._errorMessage = "must set type of parameter";
+ }
+ return false;
+ }
+
+ @action
+ handleToken(str: string) {
+ this._currWord = str;
+ this._suggestions = [];
+ this._scriptKeys.forEach((element: string) => {
+ if (element.toLowerCase().indexOf(this._currWord.toLowerCase()) >= 0) {
+ this._suggestions.push(StrCast(element));
+ }
+ });
+ return (this._suggestions);
+ }
+
+ @action
+ handleFunc(pos: number) {
+ const scriptString = this.rawScript.slice(0, pos - 2);
+ this._currWord = scriptString.split(" ")[scriptString.split(" ").length - 1];
+ this._suggestions = [StrCast(this._scriptingParams[this._currWord])];
+ return (this._suggestions);
+ }
+
+
+ getDescription(value: string) {
+ const descrip = this._scriptingDescriptions[value];
+ return descrip?.length > 0 ? descrip : "";
+ }
+
+ getParams(value: string) {
+ const params = this._scriptingParams[value];
+ return params?.length > 0 ? params : "";
+ }
+
+ returnParam(item: string) {
+ const params = item.split(",");
+ let value = "";
+ let first = true;
+ params.forEach((element) => {
+ if (first) {
+ value = element.split(":")[0].trim();
+ first = false;
+ } else {
+ value = value + ", " + element.split(":")[0].trim();
+ }
+ });
+ return value;
+ }
+
+ getSuggestedParams(pos: number) {
+ const firstScript = this.rawScript.slice(0, pos);
+ const indexP = firstScript.lastIndexOf(".");
+ const indexS = firstScript.lastIndexOf(" ");
+ const func = firstScript.slice((indexP > indexS ? indexP : indexS) + 1, firstScript.length + 1);
+ return this._scriptingParams[func];
+ }
+
+ @action
+ suggestionPos = () => {
+ const getCaretCoordinates = require('textarea-caret');
+ const This = this;
+ document.querySelector('textarea')?.addEventListener("input", function () {
+ const caret = getCaretCoordinates(this, this.selectionEnd);
+ This._selection = this;
+ This.resetSuggestionPos(caret);
+ });
+ }
+
+ @action
+ keyHandler(e: any, pos: number) {
+ if (this._lastChar === "Enter") {
+ this.rawScript = this.rawScript + " ";
+ }
+ if (e.key === "(") {
+ this.suggestionPos();
+
+ this._scriptParamsText = this.getSuggestedParams(pos);
+ this._scriptSuggestedParams = this.getSuggestedParams(pos);
+
+ if (this._scriptParamsText !== undefined && this._scriptParamsText.length > 0) {
+ if (this.rawScript[pos - 2] !== "(") {
+ this._paramSuggestion = true;
+ }
+ }
+ } else if (e.key === ")") {
+ this._paramSuggestion = false;
+ } else {
+ if (e.key === "Backspace") {
+ if (this._lastChar === "(") {
+ this._paramSuggestion = false;
+ } else if (this._lastChar === ")") {
+ if (this.rawScript.slice(0, this.rawScript.length - 1).split("(").length - 1 > this.rawScript.slice(0, this.rawScript.length - 1).split(")").length - 1) {
+ if (this._scriptParamsText.length > 0) {
+ this._paramSuggestion = true;
+ }
+ }
+ }
+ } else if (this.rawScript.split("(").length - 1 <= this.rawScript.split(")").length - 1) {
+ this._paramSuggestion = false;
+ }
+ }
+ this._lastChar = e.key === "Backspace" ? this.rawScript[this.rawScript.length - 2] : e.key;
+
+ if (this._paramSuggestion) {
+ const parameters = this._scriptParamsText.split(",");
+ const index = this.rawScript.lastIndexOf("(");
+ const enteredParams = this.rawScript.slice(index, this.rawScript.length);
+ const splitEntered = enteredParams.split(",");
+ const numEntered = splitEntered.length;
+
+ parameters.forEach((element: string, i: number) => {
+ if (i !== parameters.length - 1) {
+ parameters[i] = element + ",";
+ }
+ });
+
+ let first = "";
+ let last = "";
+
+ parameters.forEach((element: string, i: number) => {
+ if (i < numEntered - 1) {
+ first = first + element;
+ } else if (i > numEntered - 1) {
+ last = last + element;
+ }
+ });
+
+ this._scriptSuggestedParams = <div> {first} <b>{parameters[numEntered - 1]}</b> {last} </div>;
+ }
+ }
+
+ @action
+ handlePosChange(number: any) {
+ this._caretPos = number;
+ if (this._caretPos === 0) {
+ this.rawScript = " " + this.rawScript;
+ } else if (this._spaced) {
+ this._spaced = false;
+ if (this.rawScript[this._caretPos - 1] === " ") {
+ this.rawScript = this.rawScript.slice(0, this._caretPos - 1) +
+ this.rawScript.slice(this._caretPos, this.rawScript.length);
+ }
+ }
+ }
+
+ @computed({ keepAlive: true }) get renderScriptingBox() {
+ TraceMobx();
+ return <div style={{ width: this.compileParams.length > 0 ? "70%" : "100%" }} ref={this._scriptTextRef}>
+ <ReactTextareaAutocomplete className="ScriptingBox-textarea-script"
+ minChar={1}
+ placeholder="write your script here"
+ onFocus={this.onFocus}
+ onBlur={() => this._overlayDisposer?.()}
+ onChange={e => this.rawScript = e.target.value}
+ value={this.rawScript}
+ movePopupAsYouType={true}
+ loadingComponent={() => <span>Loading</span>}
+
+ trigger={{
+ " ": {
+ dataProvider: (token: any) => this.handleToken(token),
+ component: ({ entity: value }) => this.renderFuncListElement(value),
+ output: (item: any, trigger) => {
+ this._spaced = true;
+ return trigger + item.trim();
+ },
+ },
+ ".": {
+ dataProvider: (token: any) => this.handleToken(token),
+ component: ({ entity: value }) => this.renderFuncListElement(value),
+ output: (item: any, trigger) => {
+ this._spaced = true;
+ return trigger + item.trim();
+ },
+ }
+ }}
+ onKeyDown={(e) => this.keyHandler(e, this._caretPos)}
+ onCaretPositionChange={(number: any) => this.handlePosChange(number)}
+ />
+ </div>;
+ }
+
+ renderFuncListElement(value: string) {
+ return <div>
+ <div style={{ fontSize: "14px" }}>
+ {value}
+ </div>
+ <div key="desc" style={{ fontSize: "10px" }}>{this.getDescription(value)}</div>
+ <div key="params" style={{ fontSize: "10px" }}>{this.getParams(value)}</div>
+ </div>;
+ }
+
+ // inputs for scripting div (script box, params box, and params column)
+ @computed({ keepAlive: true }) get renderScriptingInputs() {
+
+ // should there be a border? style={{ borderStyle: "groove", borderBlockWidth: "1px" }}
+ // params box on bottom
+ const parameterInput = <div className="scriptingBox-params">
+ <EditableView display={"block"} maxHeight={72} height={35} fontSize={22}
+ contents={""}
+ GetValue={returnEmptyString}
+ SetValue={value => value && value !== " " ? this.compileParam(value) : false}
+ placeholder={"enter parameters here"}
+ />
+ </div>;
+
+ // params column on right side (list)
+ const definedParameters = !this.compileParams.length ? (null) :
+ <div className="scriptingBox-plist" style={{ width: "30%" }}>
+ {this.compileParams.map((parameter, i) =>
+ <div className="scriptingBox-pborder" onKeyPress={e => e.key === "Enter" && this._overlayDisposer?.()} >
+ <EditableView display={"block"} maxHeight={72} height={35} fontSize={12} background-color={"beige"}
+ contents={parameter}
+ GetValue={() => parameter}
+ SetValue={value => value && value !== " " ? this.compileParam(value, i) : this.onDelete(i)}
+ />
+ </div>
+ )}
+ </div>;
+
+ return <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()} >
+ <div className="scriptingBox-wrapper">
+ {this.renderScriptingBox}
+ {definedParameters}
+ </div>
+ {parameterInput}
+ {this.renderErrorMessage()}
+ </div>;
+ }
+
+ // toolbar (with compile and apply buttons) for scripting UI
+ renderScriptingTools() {
+ const buttonStyle = "scriptingBox-button" + (this.rootDoc.layoutKey === "layout_onClick" ? "third" : "");
+ return <div className="scriptingBox-toolbar">
+ <button className={buttonStyle} style={{ width: "33%" }} onPointerDown={e => { this.onCompile(); e.stopPropagation(); }}>Compile</button>
+ <button className={buttonStyle} style={{ width: "33%" }} onPointerDown={e => { this.onApply(); e.stopPropagation(); }}>Apply</button>
+ <button className={buttonStyle} style={{ width: "33%" }} onPointerDown={e => { this.onSave(); e.stopPropagation(); }}>Save</button>
+
+ {this.rootDoc.layoutKey !== "layout_onClick" ? (null) :
+ <button className={buttonStyle} onPointerDown={e => { this.onFinish(); e.stopPropagation(); }}>Finish</button>}
+ </div>;
+ }
+
+ // inputs UI for params which allows you to set values for each displayed in a list
+ renderParamsInputs() {
+ return <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected(true) && e.stopPropagation()} >
+ {!this.compileParams.length || !this.paramsNames ? (null) :
+ <div className="scriptingBox-plist">
+ {this.paramsNames.map((parameter: string, i: number) =>
+ <div className="scriptingBox-pborder" onKeyPress={e => e.key === "Enter" && this._overlayDisposer?.()} >
+ <div className="scriptingBox-wrapper" style={{ maxHeight: "40px" }}>
+ <div className="scriptingBox-paramNames" > {`${parameter}:${this.paramsTypes[i]} = `} </div>
+ {this.paramsTypes[i] === "boolean" ? this.renderEnum(parameter, [true, false]) : (null)}
+ {this.paramsTypes[i] === "string" ? this.renderBasicType(parameter, false) : (null)}
+ {this.paramsTypes[i] === "number" ? this.renderBasicType(parameter, true) : (null)}
+ {this.paramsTypes[i] === "Doc" ? this.renderDoc(parameter) : (null)}
+ {this.paramsTypes[i]?.split("|")[1] ? this.renderEnum(parameter, this.paramsTypes[i].split("|").map(s => !isNaN(parseInt(s.trim())) ? parseInt(s.trim()) : s.trim())) : (null)}
+ </div>
+ </div>)}
+ </div>}
+ {this.renderErrorMessage()}
+ </div>;
+ }
+
+ // toolbar (with edit and run buttons and error message) for params UI
+ renderTools(toolSet: string, func: () => void) {
+ const buttonStyle = "scriptingBox-button" + (this.rootDoc.layoutKey === "layout_onClick" ? "third" : "");
+ return <div className="scriptingBox-toolbar">
+ <button className={buttonStyle} onPointerDown={e => { this.onEdit(); e.stopPropagation(); }}>Edit</button>
+ <button className={buttonStyle} onPointerDown={e => { func(); e.stopPropagation(); }}>{toolSet}</button>
+ {this.rootDoc.layoutKey !== "layout_onClick" ? (null) :
+ <button className={buttonStyle} onPointerDown={e => { this.onFinish(); e.stopPropagation(); }}>Finish</button>}
+ </div>;
+ }
+
+ // renders script UI if _applied = false and params UI if _applied = true
render() {
- const params = <EditableView
- contents={this.compileParams.join(" ")}
- display={"block"}
- maxHeight={72}
- height={35}
- fontSize={28}
- GetValue={() => ""}
- SetValue={value => { this.compileParams = new List<string>(value.split(" ").filter(s => s !== " ")); return true; }}
- />;
return (
- <div className="scriptingBox-outerDiv"
- onWheel={e => this.props.isSelected(true) && e.stopPropagation()}>
- <div className="scriptingBox-inputDiv"
- onPointerDown={e => this.props.isSelected(true) && e.stopPropagation()} >
- <textarea className="scriptingBox-textarea"
- placeholder="write your script here"
- onChange={e => this.rawScript = e.target.value}
- value={this.rawScript}
- onFocus={this.onFocus}
- onBlur={e => this._overlayDisposer?.()} />
- <div className="scriptingBox-errorMessage" style={{ background: this._errorMessage ? "red" : "" }}>{this._errorMessage}</div>
- <div className="scriptingBox-params" >{params}</div>
- </div>
- {this.rootDoc.layout === "layout" ? <div></div> : (null)}
- <div className="scriptingBox-toolbar">
- <button className="scriptingBox-button" onPointerDown={e => { this.onCompile(); e.stopPropagation(); }}>Compile</button>
- <button className="scriptingBox-button" onPointerDown={e => { this.onRun(); e.stopPropagation(); }}>Run</button>
+ <div className={`scriptingBox`} onContextMenu={this.specificContextMenu}
+ onPointerUp={!this._function ? this.suggestionPos : undefined}>
+ <div className="scriptingBox-outerDiv"
+ onWheel={e => this.props.isSelected(true) && e.stopPropagation()}>
+ {this._paramSuggestion ? <div className="boxed" ref={this._suggestionRef} style={{ left: this._suggestionBoxX + 20, top: this._suggestionBoxY - 15, display: "inline" }}> {this._scriptSuggestedParams} </div> : null}
+ {!this._applied && !this._function ? this.renderScriptingInputs : null}
+ {this._applied && !this._function ? this.renderParamsInputs() : null}
+ {!this._applied && this._function ? this.renderFunctionInputs() : null}
+
+ {!this._applied && !this._function ? this.renderScriptingTools() : null}
+ {this._applied && !this._function ? this.renderTools("Run", () => this.onRun()) : null}
+ {!this._applied && this._function ? this.renderTools("Create Function", () => this.onCreate()) : null}
</div>
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e4dbceca6..71556bfd3 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -383,6 +383,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
CollectionView={undefined}
ScreenToLocalTransform={this.props.ScreenToLocalTransform}
renderDepth={this.props.renderDepth + 1}
+ docFilters={this.props.docFilters}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
{this.contentFunc}
</CollectionFreeFormView>
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index fb9e57b51..b726a6df9 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -488,6 +488,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
CollectionView={undefined}
ScreenToLocalTransform={this.scrollXf}
renderDepth={this.props.renderDepth + 1}
+ docFilters={this.props.docFilters}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
</CollectionFreeFormView>
</div>
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index d56b87ae5..5c75a589a 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,95 +1,112 @@
-import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
-import { StepMap } from "prosemirror-transform";
-import { EditorView } from "prosemirror-view";
+import { TextSelection } from "prosemirror-state";
import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { List } from "../../../../fields/List";
-import { ObjectField } from "../../../../fields/ObjectField";
-import { listSpec } from "../../../../fields/Schema";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
+import { Doc } from "../../../../fields/Doc";
import { DocServer } from "../../../DocServer";
-
import React = require("react");
-import { schema } from "./schema_rts";
-interface IDashDocCommentView {
- node: any;
+// creates an inline comment in a note when '>>' is typed.
+// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
+// the comment can be toggled on/off with the '<-' text anchor.
+export class DashDocCommentView {
+ _fieldWrapper: HTMLDivElement; // container for label and value
+
+ constructor(node: any, view: any, getPos: any) {
+ this._fieldWrapper = document.createElement("div");
+ this._fieldWrapper.style.width = node.attrs.width;
+ this._fieldWrapper.style.height = node.attrs.height;
+ this._fieldWrapper.style.fontWeight = "bold";
+ this._fieldWrapper.style.position = "relative";
+ this._fieldWrapper.style.display = "inline-block";
+ this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+
+ ReactDOM.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />, this._fieldWrapper);
+ (this as any).dom = this._fieldWrapper;
+ }
+
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this._fieldWrapper);
+ }
+
+ selectNode() { }
+}
+
+interface IDashDocCommentViewInternal {
+ docid: string;
view: any;
getPos: any;
}
-export class DashDocCommentView extends React.Component<IDashDocCommentView>{
- constructor(props: IDashDocCommentView) {
+export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal>{
+
+ constructor(props: IDashDocCommentViewInternal) {
super(props);
+ this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
+ this.onPointerEnterCollapsed = this.onPointerEnterCollapsed.bind(this);
+ this.onPointerUpCollapsed = this.onPointerUpCollapsed.bind(this);
+ this.onPointerDownCollapsed = this.onPointerDownCollapsed.bind(this);
}
- targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
- for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) {
- const m = this.props.view.state.doc.nodeAt(i);
- if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) {
- return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
- }
- }
- const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" });
- this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc));
- setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
- return undefined;
+ onPointerLeaveCollapsed(e: any) {
+ DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+ e.preventDefault();
+ e.stopPropagation();
}
- onPointerDownCollapse = (e: any) => e.stopPropagation();
+ onPointerEnterCollapsed(e: any) {
+ DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+ e.preventDefault();
+ e.stopPropagation();
+ }
- onPointerUpCollapse = (e: any) => {
+ onPointerUpCollapsed(e: any) {
const target = this.targetNode();
+
if (target) {
const expand = target.hidden;
const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
- expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+ expand && DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { }
}, 0);
}
e.stopPropagation();
}
- onPointerEnterCollapse = (e: any) => {
- DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
- e.preventDefault();
+ onPointerDownCollapsed(e: any) {
e.stopPropagation();
}
- onPointerLeaveCollapse = (e: any) => {
- DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
- e.preventDefault();
- e.stopPropagation();
+ targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+ const state = this.props.view.state;
+ for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
+ const m = state.doc.nodeAt(i);
+ if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docid === this.props.docid) {
+ return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+ }
+ }
+
+ const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.docid, float: "right" });
+ this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc));
+ setTimeout(() => { try { this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
+ return undefined;
}
render() {
-
- const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid;
-
return (
<span
className="formattedTextBox-inlineComment"
- id={collapsedId}
- onPointerDown={this.onPointerDownCollapse}
- onPointerUp={this.onPointerUpCollapse}
- onPointerEnter={this.onPointerEnterCollapse}
- onPointerLeave={this.onPointerLeaveCollapse}
+ id={"DashDocCommentView-" + this.props.docid}
+ onPointerLeave={this.onPointerLeaveCollapsed}
+ onPointerEnter={this.onPointerEnterCollapsed}
+ onPointerUp={this.onPointerUpCollapsed}
+ onPointerDown={this.onPointerDownCollapsed}
>
-
- </span >
+ </span>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 55b3f6f1e..5c3f3dcc9 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -5,7 +5,7 @@ import { Id } from "../../../../fields/FieldSymbols";
import { ObjectField } from "../../../../fields/ObjectField";
import { ComputedField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero, returnEmptyFilter } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from "../../../documents/Documents";
import { DocumentView } from "../DocumentView";
@@ -254,6 +254,7 @@ export class DashDocView extends React.Component<IDashDocView> {
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
dontRegisterView={false}
+ docFilters={this.props.tbox?.props.docFilters||returnEmptyFilter}
ContainingCollectionView={this._textBox.props.ContainingCollectionView}
ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
ContentScaling={this.contentScaling}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 35ff9c1e6..23cf1e79b 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -25,7 +25,7 @@
margin-left: 2px;
margin-right: 5px;
position: relative;
- display: inline-block;
+ display: inline;
background-color: rgba(155, 155, 155, 0.24);
span {
min-width: 100%;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 9a1b909c1..8c16f4a1a 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -14,7 +14,6 @@ import "./DashFieldView.scss";
import { observer } from "mobx-react";
import { DocUtils } from "../../../documents/Documents";
-
export class DashFieldView {
_fieldWrapper: HTMLDivElement; // container for label and value
@@ -40,12 +39,10 @@ export class DashFieldView {
/>, this._fieldWrapper);
(this as any).dom = this._fieldWrapper;
}
- destroy() {
- ReactDOM.unmountComponentAtNode(this._fieldWrapper);
- }
+ destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
selectNode() { }
-
}
+
interface IDashFieldViewInternal {
fieldKey: string;
docid: string;
@@ -103,11 +100,14 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
// bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
// use React events. Essentially, React events occur after native events have been processed, so corresponding React events
// will never fire because Prosemirror has handled the native events. So we add listeners for native events here.
- return <span contentEditable={true} suppressContentEditableWarning={true} defaultValue={strVal} ref={r => {
- r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
- r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
- r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
- }} >
+ return <span className="dashFieldView-fieldSpan" contentEditable={true}
+ style={{ display: strVal.length < 2 ? "inline-block" : undefined }}
+ suppressContentEditableWarning={true} defaultValue={strVal}
+ ref={r => {
+ r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
+ r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
+ r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
+ }} >
{strVal}
</span>;
}
@@ -205,9 +205,9 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
{this._fieldKey}
</span>}
- <div className="dashFieldView-fieldSpan">
- {this.fieldValueContent}
- </div>
+ {/* <div className="dashFieldView-fieldSpan"> */}
+ {this.fieldValueContent}
+ {/* </div> */}
{!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index ee21fb765..1683cc972 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -6,54 +6,50 @@ import { schema } from "./schema_rts";
import { redo, undo } from "prosemirror-history";
import { StepMap } from "prosemirror-transform";
-import React = require("react");
-
-interface IFootnoteView {
+export class FootnoteView {
innerView: any;
outerView: any;
node: any;
dom: any;
getPos: any;
-}
-export class FootnoteView extends React.Component<IFootnoteView> {
- _innerView: any;
- _node: any;
+ constructor(node: any, view: any, getPos: any) {
+ // We'll need these later
+ this.node = node;
+ this.outerView = view;
+ this.getPos = getPos;
+
+ // The node's representation in the editor (empty, for now)
+ this.dom = document.createElement("footnote");
- constructor(props: IFootnoteView) {
- super(props);
- const node = this.props.node;
- const outerView = this.props.outerView;
- const _innerView = this.props.innerView;
- const getPos = this.props.getPos;
+ this.dom.addEventListener("pointerup", this.toggle, true);
+ // These are used when the footnote is selected
+ this.innerView = null;
}
selectNode() {
- const attrs = { ...this.props.node.attrs };
- attrs.visibility = true;
this.dom.classList.add("ProseMirror-selectednode");
- if (!this.props.innerView) this.open();
+ if (!this.innerView) this.open();
}
deselectNode() {
- const attrs = { ...this.props.node.attrs };
- attrs.visibility = false;
this.dom.classList.remove("ProseMirror-selectednode");
- if (this.props.innerView) this.close();
+ if (this.innerView) this.close();
}
+
open() {
// Append a tooltip to the outer node
const tooltip = this.dom.appendChild(document.createElement("div"));
tooltip.className = "footnote-tooltip";
// And put a sub-ProseMirror into that
- this.props.innerView.defineProperty(new EditorView(tooltip, {
+ this.innerView = new EditorView(tooltip, {
// You can use any node as an editor document
state: EditorState.create({
- doc: this.props.node,
+ doc: this.node,
plugins: [keymap(baseKeymap),
keymap({
- "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch),
- "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch),
+ "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
+ "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
"Mod-b": toggleMark(schema.marks.strong)
}),
// new Plugin({
@@ -74,11 +70,11 @@ export class FootnoteView extends React.Component<IFootnoteView> {
// the parent editor is focused.
e.stopPropagation();
document.addEventListener("pointerup", this.ignore, true);
- if (this.props.outerView.hasFocus()) this.props.innerView.focus();
+ if (this.outerView.hasFocus()) this.innerView.focus();
}) as any
}
- }));
- setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0);
+ });
+ setTimeout(() => this.innerView?.docView.setSelection(0, 0, this.innerView.root, true), 0);
}
ignore = (e: PointerEvent) => {
@@ -86,32 +82,43 @@ export class FootnoteView extends React.Component<IFootnoteView> {
document.removeEventListener("pointerup", this.ignore, true);
}
+ toggle = () => {
+ if (this.innerView) this.close();
+ else this.open();
+ }
+
+ close() {
+ this.innerView?.destroy();
+ this.innerView = null;
+ this.dom.textContent = "";
+ }
+
dispatchInner(tr: any) {
- const { state, transactions } = this.props.innerView.state.applyTransaction(tr);
- this.props.innerView.updateState(state);
+ const { state, transactions } = this.innerView.state.applyTransaction(tr);
+ this.innerView.updateState(state);
if (!tr.getMeta("fromOutside")) {
- const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1);
+ const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
for (const transaction of transactions) {
- const steps = transaction.steps;
- for (const step of steps) {
+ for (const step of transaction.steps) {
outerTr.step(step.map(offsetMap));
}
}
- if (outerTr.docChanged) this.props.outerView.dispatch(outerTr);
+ if (outerTr.docChanged) this.outerView.dispatch(outerTr);
}
}
+
update(node: any) {
- if (!node.sameMarkup(this.props.node)) return false;
- this._node = node; //not sure
- if (this.props.innerView) {
- const state = this.props.innerView.state;
+ if (!node.sameMarkup(this.node)) return false;
+ this.node = node;
+ if (this.innerView) {
+ const state = this.innerView.state;
const start = node.content.findDiffStart(state.doc.content);
if (start !== null) {
let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
const overlap = start - Math.min(endA, endB);
if (overlap > 0) { endA += overlap; endB += overlap; }
- this.props.innerView.dispatch(
+ this.innerView.dispatch(
state.tr
.replace(start, endB, node.slice(start, endA))
.setMeta("fromOutside", true));
@@ -119,44 +126,17 @@ export class FootnoteView extends React.Component<IFootnoteView> {
}
return true;
}
- onPointerUp = (e: any) => {
- this.toggle(e);
- }
-
- toggle = (e: any) => {
- e.preventDefault();
- if (this.props.innerView) this.close();
- else {
- this.open();
- }
- }
-
- close() {
- this.props.innerView && this.props.innerView.destroy();
- this._innerView = null;
- this.dom.textContent = "";
- }
destroy() {
- if (this.props.innerView) this.close();
+ if (this.innerView) this.close();
}
stopEvent(event: any) {
- return this.props.innerView && this.props.innerView.dom.contains(event.target);
+ return this.innerView?.dom.contains(event.target);
}
- ignoreMutation() { return true; }
-
-
- render() {
- return (
- <div
- className="footnote"
- onPointerUp={this.onPointerUp}>
- <div className="footnote-tooltip" >
-
- </div >
- </div>
- );
+ ignoreMutation() {
+ return true;
}
}
+
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 49114d967..17421b1e3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -67,6 +67,7 @@
display: inline-block;
position: absolute;
right: 0;
+ overflow: hidden;
.collectionfreeformview-container {
position: relative;
@@ -91,10 +92,13 @@
left: 10%;
}
-.formattedTextBox-inner-rounded,
-.formattedTextBox-inner {
+.formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected,
+.formattedTextBox-inner, .formattedTextBox-inner-selected {
height: 100%;
white-space: pre-wrap;
+ .ProseMirror:hover {
+ background: rgba(200,200,200,0.8);
+ }
hr {
display: block;
unicode-bidi: isolate;
@@ -232,7 +236,51 @@ footnote::after {
}
}
+.prosemirror-anchor {
+ overflow:hidden;
+ display:inline-grid;
+}
+.prosemirror-linkBtn {
+ background:unset;
+ color:unset;
+ padding:0;
+ text-transform: unset;
+ letter-spacing: unset;
+ font-size:unset;
+}
+.prosemirror-links {
+ display: none;
+ position: absolute;
+ background-color: gray;
+ padding-bottom: 10px;
+ margin-top: 1em;
+ z-index: 1;
+ }
+ .prosemirror-hrefoptions{
+ width:0px;
+ border:unset;
+ padding:0px;
+
+ }
+
+ .prosemirror-links a {
+ float: left;
+ color: white;
+ text-decoration: none;
+ }
+
+ .prosemirror-links a:hover {
+ background-color: #eee;
+ color: black;
+ }
+
+ .prosemirror-anchor:hover .prosemirror-links {
+ display: grid;
+ }
+
.ProseMirror {
+ padding: 0px;
+ height: max-content;
touch-action: none;
span {
font-family: inherit;
@@ -273,4 +321,14 @@ footnote::after {
.multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
.multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
.multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
+}
+
+.formattedTextBox-inner-rounded-selected,
+.formattedTextBox-inner-selected {
+ .ProseMirror {
+ padding:10px;
+ }
+ .ProseMirror:hover {
+ background: unset;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1fab54d7e..c23ecb8ac 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
@@ -22,7 +22,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
import { RichTextUtils } from '../../../../fields/RichTextUtils';
import { createSchema, makeInterface } from "../../../../fields/Schema";
import { Cast, DateCast, NumCast, StrCast } from "../../../../fields/Types";
-import { TraceMobx } from '../../../../fields/util';
+import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../../DocServer";
@@ -34,15 +34,15 @@ import { makeTemplate } from '../../../util/DropConverter';
import buildKeymap from "./ProsemirrorExampleTransfer";
import RichTextMenu from './RichTextMenu';
import { RichTextRules } from "./RichTextRules";
-import { DashDocCommentView, DashDocView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "./RichTextSchema";
-// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "./RichTextSchema";
-// import { OrderedListView } from "./RichTextSchema";
-// import { ImageResizeView } from "./ImageResizeView";
-// import { DashDocCommentView } from "./DashDocCommentView";
-// import { FootnoteView } from "./FootnoteView";
-// import { SummaryView } from "./SummaryView";
-// import { DashDocView } from "./DashDocView";
+
+//import { DashDocView } from "./DashDocView";
+import { DashDocView } from "./RichTextSchema";
+
+import { DashDocCommentView } from "./DashDocCommentView";
import { DashFieldView } from "./DashFieldView";
+import { SummaryView } from "./SummaryView";
+import { OrderedListView } from "./OrderedListView";
+import { FootnoteView } from "./FootnoteView";
import { schema } from "./schema_rts";
import { SelectionManager } from "../../../util/SelectionManager";
@@ -57,7 +57,6 @@ import { FieldView, FieldViewProps } from "../FieldView";
import "./FormattedTextBox.scss";
import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
import React = require("react");
-import { InkingStroke } from '../../InkingStroke';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -180,7 +179,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.linkOnDeselect.set(key, value);
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
- const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value });
+ const allHrefs = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
+ const link = this._editorView.state.schema.marks.link.create({ allHrefs, location: "onRight", title: value });
const mval = this._editorView.state.schema.marks.metadataVal.create();
const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -198,22 +198,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
const json = JSON.stringify(state.toJSON());
- if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
- this._applyingChange = true;
- this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
- if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
- if (json !== curLayout?.Data) {
- !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
- !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
- this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
- this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+ if (!this.dataDoc[AclSym]) {
+ if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
+ this._applyingChange = true;
+ this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+ if (json !== curLayout?.Data) {
+ !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
+ !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
+ this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
+ this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+ }
+ } else { // if we've deleted all the text in a note driven by a template, then restore the template data
+ this.dataDoc[this.props.fieldKey] = undefined;
+ this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
+ this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
}
- } else { // if we've deleted all the text in a note driven by a template, then restore the template data
- this.dataDoc[this.props.fieldKey] = undefined;
- this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
- this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+ this._applyingChange = false;
}
- this._applyingChange = false;
+ } else {
+ const json = JSON.parse(Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!);
+ json.selection = state.toJSON().selection;
+ this._editorView.updateState(EditorState.fromJSON(this.config, json));
}
this.updateTitle();
this.tryUpdateHeight();
@@ -239,10 +245,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!;
- const link = this._editorView.state.schema.marks.link.create({
- href: Utils.prepend("/doc/" + alink[Id]),
- title: "a link", location: location, linkId: alink[Id], targetId: target[Id]
- });
+ const allHrefs = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }];
+ const link = this._editorView.state.schema.marks.link.create({ allHrefs, title: "a link", location });
this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
}
}
@@ -283,17 +287,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@undoBatch
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
- if (de.complete.docDragData) {
- const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
+ const dragData = de.complete.docDragData;
+ if (dragData) {
+ const draggedDoc = dragData.draggedDocuments.length && dragData.draggedDocuments[0];
// replace text contents whend dragging with Alt
if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
if (draggedDoc.data instanceof RichTextField) {
Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
e.stopPropagation();
}
- // embed document when dragging with a userDropAction or an embedDoc flag set
- } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
- const target = de.complete.docDragData.droppedDocuments[0];
+ // embed document when dragg marked as embed
+ } else if (de.embedKey) {
+ const target = dragData.droppedDocuments[0];
// const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
// if (link) {
target._fitToBox = true;
@@ -481,7 +486,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}), icon: "eye"
});
});
- changeItems.push({ description: "FreeForm", event: undoBatch(() => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
+ changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
!change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
}
@@ -583,11 +588,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
};
}
- makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) {
- if (this._editorView) {
- const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
- this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link).
- addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link));
+ makeLinkToSelection(linkId: string, title: string, location: string, targetId: string) {
+ const state = this._editorView?.state;
+ if (state) {
+ const href = Utils.prepend("/doc/" + linkId);
+ const sel = state.selection;
+ const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
+ let tr = state.tr.addMark(sel.from, sel.to, splitter);
+ sel.from !== sel.to && tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
+ const allHrefs = [{ href, title, targetId, linkId }];
+ allHrefs.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.link.name)?.attrs.allHrefs ?? []));
+ const link = state.schema.marks.link.create({ href, allHrefs, title, location, linkId, targetId });
+ tr = tr.addMark(pos, pos + node.nodeSize, link);
+ }
+ });
+ OVERRIDE_ACL(true);
+ this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
+ OVERRIDE_ACL(false);
}
}
componentDidMount() {
@@ -695,7 +713,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
- return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
+ return linkIndex !== -1 && marks[linkIndex].attrs.allRefs.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
};
let start = 0;
@@ -893,17 +911,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
- dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
- // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); },
-
- // image(node, view, getPos) {
- // //const addDocTab = this.props.addDocTab;
- // return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab });
- // },
- // // WAS :
- // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); },
-
+ dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
ordered_list(node, view, getPos) { return new OrderedListView(); },
footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
@@ -986,8 +995,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (!FormattedTextBox._downEvent) return;
FormattedTextBox._downEvent = false;
if (!(e.nativeEvent as any).formattedHandled) {
+ const editor = this._editorView!;
FormattedTextBoxComment.textBox = this;
- FormattedTextBoxComment.update(this._editorView!);
+ const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
+ const node = pcords && editor.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
+ !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(node && pcords ?
+ new NodeSelection(editor.state.doc.resolve(pcords.pos)) : new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
+ FormattedTextBoxComment.update(editor, undefined, (e.target as any)?.className === "prosemirror-dropdownlink" ? (e.target as any).href : "");
}
(e.nativeEvent as any).formattedHandled = true;
@@ -1164,7 +1178,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
this._lastTimedMark = mark;
- this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
+ // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
if (!this._undoTyping) {
this._undoTyping = UndoManager.StartBatch("undoTyping");
@@ -1205,7 +1219,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
- sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
+ sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0);
@computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
render() {
TraceMobx();
@@ -1213,10 +1227,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground;
if (this.props.isSelected()) {
- this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
+ setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), 0);
} else if (FormattedTextBoxComment.textBox === this) {
- FormattedTextBoxComment.Hide();
+ setTimeout(() => FormattedTextBoxComment.Hide(), 0);
}
+ const selPad = this.props.isSelected() ? -10 : 0;
+ const selclass = this.props.isSelected() ? "-selected" : ""
return (
<div className={"formattedTextBox-cont"} style={{
transform: `scale(${scale})`,
@@ -1257,12 +1273,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
})}
>
- <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
- <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
+ <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }} onScroll={this.onscrolled} ref={this._scrollRef}>
+ <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
style={{
- padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`,
- pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
- }} />
+ padding: `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
+ pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined
+ }}
+ />
</div>
{!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
<div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} /> :
@@ -1273,6 +1290,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
PanelWidth={this.sidebarWidth}
NativeHeight={returnZero}
NativeWidth={returnZero}
+ scaleField={this.annotationKey + "-scale"}
annotationsKey={this.annotationKey}
isAnnotationOverlay={false}
focus={this.props.focus}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 59a6045ab..0d8e22251 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -4,7 +4,7 @@ import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
import { Doc, DocCastAsync } from "../../../../fields/Doc";
import { Cast, FieldValue, NumCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne } from "../../../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { DocumentManager } from "../../../util/DocumentManager";
import { schema } from "./schema_rts";
@@ -50,7 +50,9 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
return after;
}
-
+// this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target.
+// this will also display metadata information about text when the view is configured to display things like other people who authored text.
+//
export class FormattedTextBoxComment {
static tooltip: HTMLElement;
static tooltipText: HTMLElement;
@@ -84,11 +86,13 @@ export class FormattedTextBoxComment {
const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
const textBox = FormattedTextBoxComment.textBox;
if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
- if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
- textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
- } else {
- DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
- (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ if (FormattedTextBoxComment.linkDoc.author) {
+ if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+ textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+ } else {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+ (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ }
}
} else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "onRight");
@@ -115,7 +119,24 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
}
- static update(view: EditorView, lastState?: EditorState) {
+ static showCommentbox(set: string, view: EditorView, nbef: number) {
+ const state = view.state;
+ if (set !== "none") {
+ // These are in screen coordinates
+ // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+ const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+ // The box in which the tooltip is positioned, to use as base
+ const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
+ // Find a center-ish x position from the selection endpoints (when
+ // crossing lines, end may be more to the left)
+ const left = Math.max((start.left + end.left) / 2, start.left + 3);
+ FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+ FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+ }
+ FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+ }
+
+ static update(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
const state = view.state;
// Don't do anything if the document/selection didn't change
if (lastState && lastState.doc.eq(state.doc) &&
@@ -160,32 +181,34 @@ export class FormattedTextBoxComment {
let child: any = null;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
const mark = child && findLinkMark(child.marks);
- if (mark && child && nbef && naft && mark.attrs.showPreview) {
- FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
- (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
- if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
- wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
+ const href = mark?.attrs.allHrefs.find((item: { href: string }) => item.href)?.href || forceUrl;
+ if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
+ FormattedTextBoxComment.tooltipText.textContent = "external => " + href;
+ (FormattedTextBoxComment.tooltipText as any).href = href;
+ if (href.startsWith("https://en.wikipedia.org/wiki/")) {
+ wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
} else {
FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
}
- if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
+ if (href.indexOf(Utils.prepend("/doc/")) === 0) {
FormattedTextBoxComment.tooltipText.textContent = "target not found...";
(FormattedTextBoxComment.tooltipText as any).href = "";
- const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
try {
ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
} catch (e) { }
docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
if (linkDoc instanceof Doc) {
- (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
+ (FormattedTextBoxComment.tooltipText as any).href = href;
FormattedTextBoxComment.linkDoc = linkDoc;
const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
if (anchor !== target && anchor && target) {
target._scrollY = NumCast(anchor?.y);
}
- if (target) {
+ if (target?.author) {
+ FormattedTextBoxComment.showCommentbox("", view, nbef);
ReactDOM.render(<ContentFittingDocumentView
Document={target}
LibraryPath={emptyPath}
@@ -199,9 +222,10 @@ export class FormattedTextBoxComment {
addDocTab={returnFalse}
pinToPres={returnFalse}
dontRegisterView={true}
+ docFilters={returnEmptyFilter}
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
- renderDepth={1}
+ renderDepth={0}
PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
focus={emptyFunction}
@@ -214,28 +238,13 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip.style.width = NumCast(target._width) ? `${NumCast(target._width)}` : "100%";
FormattedTextBoxComment.tooltip.style.height = NumCast(target._height) ? `${NumCast(target._height)}` : "100%";
}
- // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document ....
- // let text = ext && StrCast(ext.text);
- // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title)));
}
});
}
set = "";
}
}
- if (set !== "none") {
- // These are in screen coordinates
- // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
- const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
- // The box in which the tooltip is positioned, to use as base
- const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
- // Find a center-ish x position from the selection endpoints (when
- // crossing lines, end may be more to the left)
- const left = Math.max((start.left + end.left) / 2, start.left + 3);
- FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
- FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
- }
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+ FormattedTextBoxComment.showCommentbox(set, view, nbef);
}
destroy() { }
diff --git a/src/client/views/nodes/formattedText/ImageResizeView.tsx b/src/client/views/nodes/formattedText/ImageResizeView.tsx
deleted file mode 100644
index 401ecd7e6..000000000
--- a/src/client/views/nodes/formattedText/ImageResizeView.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import { NodeSelection } from "prosemirror-state";
-import { Doc } from "../../../../fields/Doc";
-import { DocServer } from "../../../DocServer";
-import { DocumentManager } from "../../../util/DocumentManager";
-import React = require("react");
-
-import { schema } from "./schema_rts";
-
-interface IImageResizeView {
- node: any;
- view: any;
- getPos: any;
- addDocTab: any;
-}
-
-export class ImageResizeView extends React.Component<IImageResizeView> {
- constructor(props: IImageResizeView) {
- super(props);
- }
-
- onClickImg = (e: any) => {
- e.stopPropagation();
- e.preventDefault();
- if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) {
- this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2))));
- }
- }
-
- onPointerDownImg = (e: any) => {
- if (e.ctrlKey) {
- e.preventDefault();
- e.stopPropagation();
- DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc =>
- (linkDoc instanceof Doc) &&
- DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document,
- document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false));
- }
- }
-
- onPointerDownHandle = (e: any) => {
- e.preventDefault();
- e.stopPropagation();
- const elementImage = document.getElementById("imageId") as HTMLElement;
- const wid = Number(getComputedStyle(elementImage).width.replace(/px/, ""));
- const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, ""));
- const startX = e.pageX;
- const startWidth = parseFloat(this.props.node.attrs.width);
-
- const onpointermove = (e: any) => {
- const elementOuter = document.getElementById("outerId") as HTMLElement;
-
- const currentX = e.pageX;
- const diffInPx = currentX - startX;
- elementOuter.style.width = `${startWidth + diffInPx}`;
- elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
- };
-
- const onpointerup = () => {
- document.removeEventListener("pointermove", onpointermove);
- document.removeEventListener("pointerup", onpointerup);
- const pos = this.props.view.state.selection.from;
- const elementOuter = document.getElementById("outerId") as HTMLElement;
- this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height }));
- this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos))));
- };
-
- document.addEventListener("pointermove", onpointermove);
- document.addEventListener("pointerup", onpointerup);
- }
-
- selectNode() {
- const elementImage = document.getElementById("imageId") as HTMLElement;
- const elementHandle = document.getElementById("handleId") as HTMLElement;
-
- elementImage.classList.add("ProseMirror-selectednode");
- elementHandle.style.display = "";
- }
-
- deselectNode() {
- const elementImage = document.getElementById("imageId") as HTMLElement;
- const elementHandle = document.getElementById("handleId") as HTMLElement;
-
- elementImage.classList.remove("ProseMirror-selectednode");
- elementHandle.style.display = "none";
- }
-
-
- render() {
-
- const outerStyle = {
- width: this.props.node.attrs.width,
- height: this.props.node.attrs.height,
- display: "inline-block",
- overflow: "hidden",
- float: this.props.node.attrs.float
- };
-
- const imageStyle = {
- width: "100%",
- };
-
- const handleStyle = {
- position: "absolute",
- width: "20px",
- heiht: "20px",
- backgroundColor: "blue",
- borderRadius: "15px",
- display: "none",
- bottom: "-10px",
- right: "-10px"
-
- };
-
-
-
- return (
- <div id="outer"
- style={outerStyle}
- >
- <img
- id="imageId"
- style={imageStyle}
- src={this.props.node.src}
- onClick={this.onClickImg}
- onPointerDown={this.onPointerDownImg}
-
- >
- </img>
- <span
- id="handleId"
- onPointerDown={this.onPointerDownHandle}
- >
-
- </span>
- </div >
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/OrderedListView.tsx b/src/client/views/nodes/formattedText/OrderedListView.tsx
new file mode 100644
index 000000000..c3595e59b
--- /dev/null
+++ b/src/client/views/nodes/formattedText/OrderedListView.tsx
@@ -0,0 +1,8 @@
+export class OrderedListView {
+
+ update(node: any) {
+ // if attr's of an ordered_list (e.g., bulletStyle) change,
+ // return false forces the dom node to be recreated which is necessary for the bullet labels to update
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 0e3e7f91e..0a4c52ef9 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -6,11 +6,11 @@ import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
import { splitListItem, wrapInList, } from "prosemirror-schema-list";
import { EditorState, Transaction, TextSelection } from "prosemirror-state";
import { SelectionManager } from "../../../util/SelectionManager";
-import { Docs } from "../../../documents/Documents";
import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
-import { Doc } from "../../../../fields/Doc";
+import { Doc, DataSym } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
import { Id } from "../../../../fields/FieldSymbols";
+import { Docs } from "../../../documents/Documents";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -30,6 +30,7 @@ export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string)
});
return tx2;
};
+
export default function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
const keys: { [key: string]: any } = {};
@@ -42,77 +43,27 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
keys[key] = cmd;
}
+ //History commands
bind("Mod-z", undo);
- bind("Shift-Mod-z", redo);
bind("Backspace", undoInputRule);
-
+ bind("Shift-Mod-z", redo);
!mac && bind("Mod-y", redo);
- bind("Alt-ArrowUp", joinUp);
- bind("Alt-ArrowDown", joinDown);
- bind("Mod-BracketLeft", lift);
- bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
- (document.activeElement as any).blur?.();
- SelectionManager.DeselectAll();
- });
-
+ //Commands to modify Mark
bind("Mod-b", toggleMark(schema.marks.strong));
bind("Mod-B", toggleMark(schema.marks.strong));
bind("Mod-e", toggleMark(schema.marks.em));
bind("Mod-E", toggleMark(schema.marks.em));
+ bind("Mod-*", toggleMark(schema.marks.code));
+
bind("Mod-u", toggleMark(schema.marks.underline));
bind("Mod-U", toggleMark(schema.marks.underline));
- bind("Mod-`", toggleMark(schema.marks.code));
-
+ //Commands for lists
bind("Ctrl-.", wrapInList(schema.nodes.bullet_list));
-
- bind("Ctrl-n", wrapInList(schema.nodes.ordered_list));
-
- bind("Ctrl->", wrapIn(schema.nodes.blockquote));
-
- // bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- // let newNode = schema.nodes.footnote.create({});
- // if (dispatch && state.selection.from === state.selection.to) {
- // let tr = state.tr;
- // tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
- // dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
- // tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
- // tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
- // return true;
- // }
- // return false;
- // });
-
-
- const cmd = chainCommands(exitCode, (state, dispatch) => {
- if (dispatch) {
- dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
- return true;
- }
- return false;
- });
- bind("Mod-Enter", cmd);
- bind("Shift-Enter", cmd);
- mac && bind("Ctrl-Enter", cmd);
-
-
- bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph));
-
- bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
-
- for (let i = 1; i <= 6; i++) {
- bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
- }
-
- const hr = schema.nodes.horizontal_rule;
- bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
- return true;
- });
+ bind("Ctrl-i", wrapInList(schema.nodes.ordered_list));
bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const ref = state.selection;
@@ -149,12 +100,49 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
console.log("bullet demote fail");
}
});
- bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+
+ //Command to create a new Tab with a PDF of all the command shortcuts
+ bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ const newDoc = Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 });
+ props.addDocTab(newDoc, "onRight");
+ });
+
+ //Commands to modify BlockType
+ bind("Ctrl->", wrapIn(schema.nodes.blockquote));
+ bind("Alt-\\", setBlockType(schema.nodes.paragraph));
+ bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
+
+ for (let i = 1; i <= 6; i++) {
+ bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
+ }
+
+ //Command to create a horizontal break line
+ const hr = schema.nodes.horizontal_rule;
+ bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
+ return true;
+ });
+
+ //Command to unselect all
+ bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+ (document.activeElement as any).blur?.();
+ SelectionManager.DeselectAll();
+ });
+
+ const splitMetadata = (marks: any, tx: Transaction) => {
+ marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+ marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+ return tx;
+ };
+
+ const addTextOnRight = (force: boolean) => {
const layoutDoc = props.Document;
const originalDoc = layoutDoc.rootDocument || layoutDoc;
- if (originalDoc instanceof Doc) {
+ if (force || props.Document._singleLine) {
const layoutKey = StrCast(originalDoc.layoutKey);
const newDoc = Doc.MakeCopy(originalDoc, true);
+ newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = undefined;
newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
@@ -162,20 +150,24 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
+ return true;
}
+ return false;
+ };
+
+ //Command to create a text document to the right of the selected textbox
+ bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ return addTextOnRight(true);
});
- const splitMetadata = (marks: any, tx: Transaction) => {
- marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
- marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
- return tx;
- };
- const addTextOnRight = (force: boolean) => {
+ //Command to create a text document to the bottom of the selected textbox
+ bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const layoutDoc = props.Document;
const originalDoc = layoutDoc.rootDocument || layoutDoc;
- if (force || props.Document._singleLine) {
+ if (originalDoc instanceof Doc) {
const layoutKey = StrCast(originalDoc.layoutKey);
const newDoc = Doc.MakeCopy(originalDoc, true);
+ newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = undefined;
newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
@@ -183,13 +175,10 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
- return true;
}
- return false;
- };
- bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
- return addTextOnRight(true);
});
+
+ //command to break line
bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
if (addTextOnRight(false)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -205,31 +194,73 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
}
return true;
});
+
+ //Command to create a blank space
bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
});
+
+ bind("Alt-ArrowUp", joinUp);
+ bind("Alt-ArrowDown", joinDown);
+ bind("Mod-BracketLeft", lift);
+
+ const cmd = chainCommands(exitCode, (state, dispatch) => {
+ if (dispatch) {
+ dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
+ return true;
+ }
+ return false;
+ });
+
+ // mac && bind("Ctrl-Enter", cmd);
+ // bind("Mod-Enter", cmd);
+ bind("Shift-Enter", cmd);
+
+
bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => {
return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata);
});
+
const path = (state.doc.resolve(state.selection.from - 1) as any).path;
+
const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1;
+
const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator;
+
if (anchor >= 0) {
+
const textsel = TextSelection.create(state.doc, anchor, range!.end);
+
const text = range ? state.doc.textBetween(textsel.from, textsel.to) : "";
+
let whitespace = text.length - 1;
+
for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { }
if (text.endsWith(":")) {
dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any).
addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any));
}
}
+
return false;
});
+ // bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ // let newNode = schema.nodes.footnote.create({});
+ // if (dispatch && state.selection.from === state.selection.to) {
+ // let tr = state.tr;
+ // tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
+ // dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
+ // tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+ // tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
+ // return true;
+ // }
+ // return false;
+ // });
return keys;
}
+
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index fd1b26208..03d393cde 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -631,7 +631,7 @@ export default class RichTextMenu extends AntimodeMenu {
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type.name === "link");
if (link) {
- const href = link.attrs.href;
+ const href = link.attrs.allHrefs.length > 0 ? link.attrs.allHrefs[0].href : undefined;
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -677,7 +677,7 @@ export default class RichTextMenu extends AntimodeMenu {
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
- const href = link!.attrs.href;
+ const href = link!.attrs.allHrefs.length > 0 ? link!.attrs.allHrefs[0].href : undefined;
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -705,8 +705,8 @@ export default class RichTextMenu extends AntimodeMenu {
let startIndex = $start.index();
let endIndex = $start.indexAfter();
- while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--;
- while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++;
+ while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) startIndex--;
+ while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) endIndex++;
let startPos = $start.start();
let endPos = startPos;
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index fbd6c87bb..91187edf9 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -30,7 +30,7 @@ export class RichTextRules {
// > blockquote
wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote),
- // 1. ordered list
+ // 1. create numerical ordered list
wrappingInputRule(
/^1\.\s$/,
schema.nodes.ordered_list,
@@ -42,49 +42,29 @@ export class RichTextRules {
},
(type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })
),
- // a. alphabbetical list
+
+ // A. create alphabetical ordered list
wrappingInputRule(
- /^a\.\s$/,
+ /^A\.\s$/,
schema.nodes.ordered_list,
// match => {
() => {
- return ({ mapStyle: "alpha", bulletStyle: 1 });
+ return ({ mapStyle: "multi", bulletStyle: 1 });
// return ({ order: +match[1] })
},
(match: any, node: any) => {
return node.childCount + node.attrs.order === +match[1];
},
- (type: any) => ({ type: type, attrs: { mapStyle: "alpha", bulletStyle: 1 } })
+ (type: any) => ({ type: type, attrs: { mapStyle: "multi", bulletStyle: 1 } })
),
- // * bullet list
+ // * + - create bullet list
wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
- // ``` code block
+ // ``` create code block
textblockTypeInputRule(/^```$/, schema.nodes.code_block),
- // create an inline view of a tag stored under the '#' field
- new InputRule(
- new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/),
- (state, match, start, end) => {
- const tag = match[1];
- if (!tag) return state.tr;
- const multiple = tag.split(";");
- this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag;
- const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
- }),
-
- // # heading
- textblockTypeInputRule(
- new RegExp(/^(#{1,6})\s$/),
- schema.nodes.heading,
- match => {
- return ({ level: match[1].length });
- }
- ),
-
- // set the font size using #<font-size>
+ // %<font-size> set the font size
new InputRule(
new RegExp(/%([0-9]+)\s$/),
(state, match, start, end) => {
@@ -92,51 +72,7 @@ export class RichTextRules {
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
}),
- // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]] // [[:Doc]] => hyperlink [[fieldKey]] => show field [[fieldKey:Doc]] => show field of doc
- new InputRule(
- new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/),
- (state, match, start, end) => {
- const fieldKey = match[1];
- const docid = match[3]?.substring(1);
- const value = match[2]?.substring(1);
- if (!fieldKey) {
- if (docid) {
- DocServer.GetRefField(docid).then(docx => {
- const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid);
- DocUtils.Publish(target, docid, returnFalse, returnFalse);
- DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
- });
- const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
- return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
- }
- return state.tr;
- }
- if (value !== "" && value !== undefined) {
- const num = value.match(/^[0-9.]$/);
- this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value);
- }
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
- }),
- // create an inline view of a document {{ <layoutKey> : <Doc> }} // {{:Doc}} => show default view of document {{<layout>}} => show layout for this doc {{<layout> : Doc}} => show layout for another doc
- new InputRule(
- new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/),
- (state, match, start, end) => {
- const fieldKey = match[1] || "";
- const fieldParam = match[2]?.replace("…", "...") || "";
- const docid = match[3]?.substring(1);
- if (!fieldKey && !docid) return state.tr;
- docid && DocServer.GetRefField(docid).then(docx => {
- if (!(docx instanceof Doc && docx)) {
- const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid);
- DocUtils.Publish(docx, docid, returnFalse, returnFalse);
- }
- });
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() });
- const sm = state.storedMarks || undefined;
- return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- }),
+ //Create annotation to a field on the text document
new InputRule(
new RegExp(/>>$/),
(state, match, start, end) => {
@@ -161,25 +97,7 @@ export class RichTextRules {
state.tr;
return replaced;
}),
- // stop using active style
- new InputRule(
- new RegExp(/%%$/),
- (state, match, start, end) => {
- const tr = state.tr.deleteRange(start, end);
- const marks = state.tr.selection.$anchor.nodeBefore?.marks;
- return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
- }),
- // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/[ti!x]$/),
- (state, match, start, end) => {
- if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
- const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
- const node = (state.doc.resolve(start) as any).nodeAfter;
- if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
- return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
- }),
// set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(
@@ -214,6 +132,7 @@ export class RichTextRules {
}
return null;
}),
+
// set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(
new RegExp(/(%q|q)$/),
@@ -235,56 +154,56 @@ export class RichTextRules {
return null;
}),
-
// center justify text
new InputRule(
- new RegExp(/%\^$/),
+ new RegExp(/%\^/),
(state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === "paragraph") {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "center" }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
}),
+
// left justify text
new InputRule(
- new RegExp(/%\[$/),
+ new RegExp(/%\[/),
(state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === "paragraph") {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "left" }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
}),
+
// right justify text
new InputRule(
- new RegExp(/%\]$/),
- (state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
- }),
- new InputRule(
- new RegExp(/%\(/),
- (state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || [];
- const mark = state.schema.marks.summarizeInclusive.create();
- sm.push(mark);
- const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
- const content = selected.selection.content();
- const replaced = node ? selected.replaceRangeWith(start, end,
- schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
- }),
- new InputRule(
- new RegExp(/%\)/),
+ new RegExp(/%\]/),
(state, match, start, end) => {
- return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === "paragraph") {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "right" }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
}),
+
+
+ // %f create footnote
new InputRule(
new RegExp(/%f$/),
(state, match, start, end) => {
@@ -296,26 +215,160 @@ export class RichTextRules {
tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)));
}),
- // activate a style by name using prefix '%'
+ // activate a style by name using prefix '%<color name>'
new InputRule(
new RegExp(/%[a-z]+$/),
(state, match, start, end) => {
+
const color = match[0].substring(1, match[0].length);
const marks = RichTextMenu.Instance._brushMap.get(color);
+
if (marks) {
const tr = state.tr.deleteRange(start, end);
return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
}
+
const isValidColor = (strColor: string) => {
const s = new Option().style;
s.color = strColor;
return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
};
+
if (isValidColor(color)) {
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
}
+
return null;
}),
+
+ // stop using active style
+ new InputRule(
+ new RegExp(/%%$/),
+ (state, match, start, end) => {
+
+ const tr = state.tr.deleteRange(start, end);
+ const marks = state.tr.selection.$anchor.nodeBefore?.marks;
+
+ return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
+ }),
+
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // [[ <fieldKey> : <Doc>]]
+ // [[:Doc]] => hyperlink
+ // [[fieldKey]] => show field
+ // [[fieldKey:Doc]] => show field of doc
+ new InputRule(
+ new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/),
+ (state, match, start, end) => {
+ const fieldKey = match[1];
+ const docid = match[3]?.substring(1);
+ const value = match[2]?.substring(1);
+ if (!fieldKey) {
+ if (docid) {
+ DocServer.GetRefField(docid).then(docx => {
+ const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid);
+ DocUtils.Publish(target, docid, returnFalse, returnFalse);
+ DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
+ });
+ const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
+ }
+ return state.tr;
+ }
+ if (value !== "" && value !== undefined) {
+ const num = value.match(/^[0-9.]$/);
+ this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value);
+ }
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
+ return state.tr.deleteRange(start, end).insert(start, fieldView);
+ }),
+
+ // create an inline view of a document {{ <layoutKey> : <Doc> }}
+ // {{:Doc}} => show default view of document
+ // {{<layout>}} => show layout for this doc
+ // {{<layout> : Doc}} => show layout for another doc
+ new InputRule(
+ new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/),
+ (state, match, start, end) => {
+ const fieldKey = match[1] || "";
+ const fieldParam = match[2]?.replace("…", "...") || "";
+ const docid = match[3]?.substring(1);
+ if (!fieldKey && !docid) return state.tr;
+ docid && DocServer.GetRefField(docid).then(docx => {
+ if (!(docx instanceof Doc && docx)) {
+ const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid);
+ DocUtils.Publish(docx, docid, returnFalse, returnFalse);
+ }
+ });
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() });
+ const sm = state.storedMarks || undefined;
+ return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ }),
+
+
+
+ // create an inline view of a tag stored under the '#' field
+ new InputRule(
+ new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/),
+ (state, match, start, end) => {
+ const tag = match[1];
+ if (!tag) return state.tr;
+ const multiple = tag.split(";");
+ this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag;
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
+ return state.tr.deleteRange(start, end).insert(start, fieldView);
+ }),
+
+
+ // # heading
+ textblockTypeInputRule(
+ new RegExp(/^(#{1,6})\s$/),
+ schema.nodes.heading,
+ match => {
+ return ({ level: match[1].length });
+ }
+ ),
+
+ // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
+ new InputRule(
+ new RegExp(/[ti!x]$/),
+ (state, match, start, end) => {
+
+ if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
+
+ const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+
+ if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
+
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
+
+ new InputRule(
+ new RegExp(/%\(/),
+ (state, match, start, end) => {
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const sm = state.storedMarks || [];
+ const mark = state.schema.marks.summarizeInclusive.create();
+
+ sm.push(mark);
+ const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
+ const content = selected.selection.content();
+ const replaced = node ? selected.replaceRangeWith(start, end,
+ schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
+ state.tr;
+
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
+ }),
+
+ new InputRule(
+ new RegExp(/%\)/),
+ (state, match, start, end) => {
+
+ return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
+ }),
+
]
};
}
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index 1cc7ec8bf..a989abd6a 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -1,188 +1,19 @@
-import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
-import { StepMap } from "prosemirror-transform";
-import { EditorView } from "prosemirror-view";
+import { IReactionDisposer, reaction } from "mobx";
+import { NodeSelection } from "prosemirror-state";
import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../fields/Doc";
+import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
-import { List } from "../../../../fields/List";
import { ObjectField } from "../../../../fields/ObjectField";
-import { listSpec } from "../../../../fields/Schema";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
import { ComputedField } from "../../../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, returnZero, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from "../../../documents/Documents";
-import { CollectionViewType } from "../../collections/CollectionView";
+import { Transform } from "../../../util/Transform";
import { DocumentView } from "../DocumentView";
import { FormattedTextBox } from "./FormattedTextBox";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { Transform } from "../../../util/Transform";
import React = require("react");
-import { schema } from "./schema_rts";
-
-export class OrderedListView {
- update(node: any) {
- return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
- }
-}
-
-export class ImageResizeView {
- _handle: HTMLElement;
- _img: HTMLElement;
- _outer: HTMLElement;
- constructor(node: any, view: any, getPos: any, addDocTab: any) {
- //moved
- this._handle = document.createElement("span");
- this._img = document.createElement("img");
- this._outer = document.createElement("span");
- this._outer.style.position = "relative";
- this._outer.style.width = node.attrs.width;
- this._outer.style.height = node.attrs.height;
- this._outer.style.display = "inline-block";
- this._outer.style.overflow = "hidden";
- (this._outer.style as any).float = node.attrs.float;
- //moved
- this._img.setAttribute("src", node.attrs.src);
- this._img.style.width = "100%";
- this._handle.style.position = "absolute";
- this._handle.style.width = "20px";
- this._handle.style.height = "20px";
- this._handle.style.backgroundColor = "blue";
- this._handle.style.borderRadius = "15px";
- this._handle.style.display = "none";
- this._handle.style.bottom = "-10px";
- this._handle.style.right = "-10px";
- const self = this;
- //moved
- this._img.onclick = function (e: any) {
- e.stopPropagation();
- e.preventDefault();
- if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) {
- view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2))));
- }
- };
- //moved
- this._img.onpointerdown = function (e: any) {
- if (e.ctrlKey) {
- e.preventDefault();
- e.stopPropagation();
- DocServer.GetRefField(node.attrs.docid).then(async linkDoc =>
- (linkDoc instanceof Doc) &&
- DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document,
- document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false));
- }
- };
- //moved
- this._handle.onpointerdown = function (e: any) {
- e.preventDefault();
- e.stopPropagation();
- const wid = Number(getComputedStyle(self._img).width.replace(/px/, ""));
- const hgt = Number(getComputedStyle(self._img).height.replace(/px/, ""));
- const startX = e.pageX;
- const startWidth = parseFloat(node.attrs.width);
- const onpointermove = (e: any) => {
- const currentX = e.pageX;
- const diffInPx = currentX - startX;
- self._outer.style.width = `${startWidth + diffInPx}`;
- self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
- };
-
- const onpointerup = () => {
- document.removeEventListener("pointermove", onpointermove);
- document.removeEventListener("pointerup", onpointerup);
- const pos = view.state.selection.from;
- view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
- view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos))));
- };
-
- document.addEventListener("pointermove", onpointermove);
- document.addEventListener("pointerup", onpointerup);
- };
- //Moved
- this._outer.appendChild(this._img);
- this._outer.appendChild(this._handle);
- (this as any).dom = this._outer;
- }
-
- selectNode() {
- this._img.classList.add("ProseMirror-selectednode");
-
- this._handle.style.display = "";
- }
-
- deselectNode() {
- this._img.classList.remove("ProseMirror-selectednode");
-
- this._handle.style.display = "none";
- }
-}
-
-export class DashDocCommentView {
- _collapsed: HTMLElement;
- _view: any;
- constructor(node: any, view: any, getPos: any) {
- //moved
- this._collapsed = document.createElement("span");
- this._collapsed.className = "formattedTextBox-inlineComment";
- this._collapsed.id = "DashDocCommentView-" + node.attrs.docid;
- this._view = view;
- //moved
- const targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
- for (let i = getPos() + 1; i < view.state.doc.content.size; i++) {
- const m = view.state.doc.nodeAt(i);
- if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) {
- return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
- }
- }
- const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
- view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
- setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0);
- return undefined;
- };
- //moved
- this._collapsed.onpointerdown = (e: any) => {
- e.stopPropagation();
- };
- //moved
- this._collapsed.onpointerup = (e: any) => {
- const target = targetNode();
- if (target) {
- const expand = target.hidden;
- const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
- view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs
- setTimeout(() => {
- expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
- try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
- }, 0);
- }
- e.stopPropagation();
- };
- //moved
- this._collapsed.onpointerenter = (e: any) => {
- DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
- e.preventDefault();
- e.stopPropagation();
- };
- //moved
- this._collapsed.onpointerleave = (e: any) => {
- DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
- e.preventDefault();
- e.stopPropagation();
- };
-
- (this as any).dom = this._collapsed;
- }
- //moved
- selectNode() { }
-}
export class DashDocView {
_dashSpan: HTMLDivElement;
@@ -192,16 +23,22 @@ export class DashDocView {
_renderDisposer: IReactionDisposer | undefined;
_textBox: FormattedTextBox;
+ //moved
getDocTransform = () => {
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
}
+
+ //moved
contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
+ //moved
outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ //moved
this._textBox = tbox;
+
this._dashSpan = document.createElement("div");
this._outer = document.createElement("span");
this._outer.style.position = "relative";
@@ -218,28 +55,35 @@ export class DashDocView {
this._dashSpan.style.position = "absolute";
this._dashSpan.style.display = "inline-block";
this._dashSpan.style.whiteSpace = "normal";
+
this._dashSpan.onpointerleave = () => {
const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
if (ele) {
(ele as HTMLDivElement).style.backgroundColor = "";
}
};
+
this._dashSpan.onpointerenter = () => {
const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
if (ele) {
(ele as HTMLDivElement).style.backgroundColor = "orange";
}
};
+
const removeDoc = () => {
+ console.log("DashDocView.removeDoc"); // SMM
const pos = getPos();
const ns = new NodeSelection(view.state.doc.resolve(pos));
view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
return true;
};
- const alias = node.attrs.alias;
+ const alias = node.attrs.alias;
+ const self = this;
const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id];
+
DocServer.GetRefField(docid + alias).then(async dashDoc => {
+
if (!(dashDoc instanceof Doc)) {
alias && DocServer.GetRefField(docid).then(async dashDocBase => {
if (dashDocBase instanceof Doc) {
@@ -253,7 +97,8 @@ export class DashDocView {
self.doRender(dashDoc, removeDoc, node, view, getPos);
}
});
- const self = this;
+
+
this._dashSpan.onkeydown = function (e: any) {
e.stopPropagation();
if (e.key === "Tab" || e.key === "Enter") {
@@ -281,6 +126,7 @@ export class DashDocView {
this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px";
this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
}, { fireImmediately: true });
+
const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
ReactDOM.unmountComponentAtNode(this._dashSpan);
@@ -306,10 +152,12 @@ export class DashDocView {
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
dontRegisterView={false}
+ docFilters={this._textBox.props.docFilters}
ContainingCollectionView={this._textBox.props.ContainingCollectionView}
ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
ContentScaling={this.contentScaling}
/>, this._dashSpan);
+
if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
@@ -318,6 +166,7 @@ export class DashDocView {
}
}
};
+
this._renderDisposer?.();
this._renderDisposer = reaction(() => {
// if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
@@ -337,202 +186,9 @@ export class DashDocView {
{ fireImmediately: true });
}
}
+
destroy() {
ReactDOM.unmountComponentAtNode(this._dashSpan);
this._reactionDisposer?.();
}
}
-
-export class FootnoteView {
- innerView: any;
- outerView: any;
- node: any;
- dom: any;
- getPos: any;
-
- constructor(node: any, view: any, getPos: any) {
- // We'll need these later
- this.node = node;
- this.outerView = view;
- this.getPos = getPos;
-
- // The node's representation in the editor (empty, for now)
- this.dom = document.createElement("footnote");
- this.dom.addEventListener("pointerup", this.toggle, true);
- // These are used when the footnote is selected
- this.innerView = null;
- }
- selectNode() {
- const attrs = { ...this.node.attrs };
- attrs.visibility = true;
- this.dom.classList.add("ProseMirror-selectednode");
- if (!this.innerView) this.open();
- }
-
- deselectNode() {
- const attrs = { ...this.node.attrs };
- attrs.visibility = false;
- this.dom.classList.remove("ProseMirror-selectednode");
- if (this.innerView) this.close();
- }
- open() {
- // Append a tooltip to the outer node
- const tooltip = this.dom.appendChild(document.createElement("div"));
- tooltip.className = "footnote-tooltip";
- // And put a sub-ProseMirror into that
- this.innerView = new EditorView(tooltip, {
- // You can use any node as an editor document
- state: EditorState.create({
- doc: this.node,
- plugins: [keymap(baseKeymap),
- keymap({
- "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
- "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
- "Mod-b": toggleMark(schema.marks.strong)
- }),
- // new Plugin({
- // view(newView) {
- // // TODO -- make this work with RichTextMenu
- // // return FormattedTextBox.getToolTip(newView);
- // }
- // })
- ],
-
- }),
- // This is the magic part
- dispatchTransaction: this.dispatchInner.bind(this),
- handleDOMEvents: {
- pointerdown: ((view: any, e: PointerEvent) => {
- // Kludge to prevent issues due to the fact that the whole
- // footnote is node-selected (and thus DOM-selected) when
- // the parent editor is focused.
- e.stopPropagation();
- document.addEventListener("pointerup", this.ignore, true);
- if (this.outerView.hasFocus()) this.innerView.focus();
- }) as any
- }
-
- });
- setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0);
- }
-
- ignore = (e: PointerEvent) => {
- e.stopPropagation();
- document.removeEventListener("pointerup", this.ignore, true);
- }
-
- toggle = () => {
- if (this.innerView) this.close();
- else {
- this.open();
- }
- }
- close() {
- this.innerView && this.innerView.destroy();
- this.innerView = null;
- this.dom.textContent = "";
- }
-
- dispatchInner(tr: any) {
- const { state, transactions } = this.innerView.state.applyTransaction(tr);
- this.innerView.updateState(state);
-
- if (!tr.getMeta("fromOutside")) {
- const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
- for (const transaction of transactions) {
- const steps = transaction.steps;
- for (const step of steps) {
- outerTr.step(step.map(offsetMap));
- }
- }
- if (outerTr.docChanged) this.outerView.dispatch(outerTr);
- }
- }
- update(node: any) {
- if (!node.sameMarkup(this.node)) return false;
- this.node = node;
- if (this.innerView) {
- const state = this.innerView.state;
- const start = node.content.findDiffStart(state.doc.content);
- if (start !== null) {
- let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
- const overlap = start - Math.min(endA, endB);
- if (overlap > 0) { endA += overlap; endB += overlap; }
- this.innerView.dispatch(
- state.tr
- .replace(start, endB, node.slice(start, endA))
- .setMeta("fromOutside", true));
- }
- }
- return true;
- }
-
- destroy() {
- if (this.innerView) this.close();
- }
-
- stopEvent(event: any) {
- return this.innerView && this.innerView.dom.contains(event.target);
- }
-
- ignoreMutation() { return true; }
-}
-
-export class SummaryView {
- _collapsed: HTMLElement;
- _view: any;
- constructor(node: any, view: any, getPos: any) {
- this._collapsed = document.createElement("span");
- this._collapsed.className = this.className(node.attrs.visibility);
- this._view = view;
- const js = node.toJSON;
- node.toJSON = function () {
- return js.apply(this, arguments);
- };
-
- this._collapsed.onpointerdown = (e: any) => {
- const visible = !node.attrs.visibility;
- const attrs = { ...node.attrs, visibility: visible };
- let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
- if (!visible) { // update summarized text and save in attrs
- textSelection = this.updateSummarizedText(getPos() + 1);
- attrs.text = textSelection.content();
- attrs.textslice = attrs.text.toJSON();
- }
- view.dispatch(view.state.tr.
- setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
- replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
- setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
- e.preventDefault();
- e.stopPropagation();
- this._collapsed.className = this.className(visible);
- };
- (this as any).dom = this._collapsed;
- }
- selectNode() { }
-
- deselectNode() { }
-
- className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
-
- updateSummarizedText(start?: any) {
- const mtype = this._view.state.schema.marks.summarize;
- const mtypeInc = this._view.state.schema.marks.summarizeInclusive;
- let endPos = start;
-
- const visited = new Set();
- for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) {
- let skip = false;
- this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
- if (node.isLeaf && !visited.has(node) && !skip) {
- if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
- visited.add(node);
- endPos = i + node.nodeSize - 1;
- }
- else skip = true;
- }
- });
- }
- return TextSelection.create(this._view.state.doc, start, endPos);
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 89908d8ee..c017db034 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -1,81 +1,81 @@
import { TextSelection } from "prosemirror-state";
import { Fragment, Node, Slice } from "prosemirror-model";
-
+import * as ReactDOM from 'react-dom';
import React = require("react");
-interface ISummaryView {
- node: any;
- view: any;
- getPos: any;
- self: any;
-}
-export class SummaryView extends React.Component<ISummaryView> {
+// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
+// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
+// really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering
+// method instead of changing prosemirror's text when the expand/elide buttons are clicked.
+export class SummaryView {
+ _fieldWrapper: HTMLSpanElement; // container for label and value
- onPointerDown = (e: any) => {
- const visible = !this.props.node.attrs.visibility;
- const attrs = { ...this.props.node.attrs, visibility: visible };
- let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1);
- if (!visible) { // update summarized text and save in attrs
- textSelection = this.updateSummarizedText(this.props.getPos() + 1);
- attrs.text = textSelection.content();
- attrs.textslice = attrs.text.toJSON();
- }
- this.props.view.dispatch(this.props.view.state.tr.
- setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
- replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it
- setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs
- e.preventDefault();
- e.stopPropagation();
- const _collapsed = document.getElementById('collapse') as HTMLElement;
- _collapsed.className = this.className(visible);
+ constructor(node: any, view: any, getPos: any) {
+ const self = this;
+ this._fieldWrapper = document.createElement("span");
+ this._fieldWrapper.className = this.className(node.attrs.visibility);
+ this._fieldWrapper.onpointerdown = function (e: any) { self.onPointerDown(e, node, view, getPos); };
+ this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+
+ const js = node.toJSON;
+ node.toJSON = function () { return js.apply(this, arguments); };
+
+ ReactDOM.render(<SummaryViewInternal />, this._fieldWrapper);
+ (this as any).dom = this._fieldWrapper;
}
- updateSummarizedText(start?: any) {
- const mtype = this.props.view.state.schema.marks.summarize;
- const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive;
+ className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
+ destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
+ selectNode() { }
+
+ updateSummarizedText(start: any, view: any) {
+ const mtype = view.state.schema.marks.summarize;
+ const mtypeInc = view.state.schema.marks.summarizeInclusive;
let endPos = start;
const visited = new Set();
- for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) {
+ for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
let skip = false;
- this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
- if (this.props.node.isLeaf && !visited.has(node) && !skip) {
- if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
+ view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+ if (node.isLeaf && !visited.has(node) && !skip) {
+ if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
- endPos = i + this.props.node.nodeSize - 1;
+ endPos = i + node.nodeSize - 1;
}
else skip = true;
}
});
}
- return TextSelection.create(this.props.view.state.doc, start, endPos);
+ return TextSelection.create(view.state.doc, start, endPos);
}
- className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
-
- selectNode() { }
-
- deselectNode() { }
+ onPointerDown = (e: any, node: any, view: any, getPos: any) => {
+ const visible = !node.attrs.visibility;
+ const attrs = { ...node.attrs, visibility: visible };
+ let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
+ if (!visible) { // update summarized text and save in attrs
+ textSelection = this.updateSummarizedText(getPos() + 1, view);
+ attrs.text = textSelection.content();
+ attrs.textslice = attrs.text.toJSON();
+ }
+ view.dispatch(view.state.tr.
+ setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
+ replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
+ setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
+ e.preventDefault();
+ e.stopPropagation();
+ this._fieldWrapper.className = this.className(visible);
+ }
+}
+interface ISummaryView {
+}
+// currently nothing needs to be rendered for the internal view of a summary.
+export class SummaryViewInternal extends React.Component<ISummaryView> {
render() {
- const _view = this.props.node.view;
- const js = this.props.node.toJSon;
-
- this.props.node.toJSON = function () {
- return js.apply(this, arguments);
- };
-
- const spanCollapsedClassName = this.className(this.props.node.attrs.visibility);
-
- return (
- <span
- className={spanCollapsedClassName}
- id='collapse'
- onPointerDown={this.onPointerDown}
- >
-
- </span>
- );
-
+ return <> </>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ebaa23e99..49d5c96a4 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -9,14 +9,20 @@ const codeDOM: DOMOutputSpecArray = ["code", 0];
// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
export const marks: { [index: string]: MarkSpec } = {
+ splitter: {
+ attrs: {
+ id: { default: "" }
+ },
+ toDOM(node: any) {
+ return ["div", { className: "dummy" }, 0];
+ }
+ },
// :: MarkSpec A link. Has `href` and `title` attributes. `title`
// defaults to the empty string. Rendered and parsed as an `<a>`
// element.
link: {
attrs: {
- href: {},
- targetId: { default: "" },
- linkId: { default: "" },
+ allHrefs: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] },
showPreview: { default: true },
location: { default: null },
title: { default: null },
@@ -25,13 +31,25 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: false,
parseDOM: [{
tag: "a[href]", getAttrs(dom: any) {
- return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") };
+ return { allHrefs: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.getAttribute("targetids") }], location: dom.getAttribute("location"), };
}
}],
toDOM(node: any) {
+ const targetids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
+ const linkids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
return node.attrs.docref && node.attrs.title ?
["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
- ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0];
+ node.attrs.allHrefs.length === 1 ?
+ ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allHrefs[0].href }, 0] :
+ ["div", { class: "prosemirror-anchor" },
+ ["span", { class: "prosemirror-linkBtn" },
+ ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}` }, 0],
+ ["input", { class: "prosemirror-hrefoptions" }],
+ ],
+ ["div", { class: "prosemirror-links" }, ...node.attrs.allHrefs.map((item: { href: string, title: string }) =>
+ ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
+ )]
+ ];
}
},
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index e39e96607..516774f44 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -51,6 +51,7 @@ interface IViewerProps {
fieldKey: string;
Document: Doc;
DataDoc?: Doc;
+ docFilters: () => string[];
ContainingCollectionView: Opt<CollectionView>;
PanelWidth: () => number;
PanelHeight: () => number;
@@ -126,16 +127,24 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
// file address of the pdf
const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!;
const { url: relative } = this.props;
- const pathComponents = relative.split("/pdfs/")[1].split("/");
- const coreFilename = pathComponents.pop()!.split(".")[0];
- const params: any = {
- coreFilename,
- pageNum: this.Document.curPage || 1,
- };
- if (pathComponents.length) {
- params.subtree = `${pathComponents.join("/")}/`;
+ if (relative.includes("/pdfs/")) {
+ const pathComponents = relative.split("/pdfs/")[1].split("/");
+ const coreFilename = pathComponents.pop()!.split(".")[0];
+ const params: any = {
+ coreFilename,
+ pageNum: this.Document.curPage || 1,
+ };
+ if (pathComponents.length) {
+ params.subtree = `${pathComponents.join("/")}/`;
+ }
+ this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
+ } else {
+ const params: any = {
+ coreFilename: relative.split("/")[relative.split("/").length - 1],
+ pageNum: this.Document.curPage || 1,
+ };
+ this._coverPath = "http://cs.brown.edu/~bcz/face.gif";//href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
}
- this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
runInAction(() => this._showWaiting = this._showCover = true);
this.props.startupLive && this.setupPdfJsViewer();
this._mainCont.current!.scrollTop = this.layoutDoc._scrollTop || 0;
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index caee06d8f..6b59a0563 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -194,6 +194,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
whenActiveChanged={returnFalse}
bringToFront={returnFalse}
opacity={returnOne}
+ docFilters={this.props.docFilters}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
ContentScaling={returnOne}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 24d6e9d6f..74262d81a 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
import { Doc } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero, returnEmptyString, returnEmptyFilter } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, SetupDrag } from "../../util/DragManager";
@@ -164,6 +164,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
removeDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={returnFalse}
+ docFilters={returnEmptyFilter}
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
ScreenToLocalTransform={Transform.Identity}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ffef9a384..8c8720179 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -100,7 +100,7 @@ export const UpdatingFromServer = Symbol("UpdatingFromServer");
const CachedUpdates = Symbol("Cached updates");
-function fetchProto(doc: Doc) {
+export function fetchProto(doc: Doc) {
if (doc.author !== Doc.CurrentUserEmail) {
const acl = Doc.Get(doc, "ACL", true);
switch (acl) {
@@ -116,21 +116,9 @@ function fetchProto(doc: Doc) {
}
}
- const proto = doc.proto;
- if (proto instanceof Promise) {
- proto.then(proto => {
- if (proto.author !== Doc.CurrentUserEmail) {
- if (proto.ACL === "ownerOnly") {
- proto[AclSym] = doc[AclSym] = AclPrivate;
- return undefined;
- } else if (proto.ACL === "readOnly") {
- proto[AclSym] = doc[AclSym] = AclReadonly;
- } else if (proto.ACL === "addOnly") {
- proto[AclSym] = doc[AclSym] = AclAddonly;
- }
- }
- });
- return proto;
+ if (doc.proto instanceof Promise) {
+ doc.proto.then(fetchProto);
+ return doc.proto;
}
}
@@ -146,7 +134,7 @@ export class Doc extends RefField {
has: (target, key) => target[AclSym] !== AclPrivate && key in target.__fields,
ownKeys: target => {
const obj = {} as any;
- (target[AclSym] !== AclPrivate) && Object.assign(obj, target.___fields);
+ if (target[AclSym] !== AclPrivate) Object.assign(obj, target.___fields);
runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
return Object.keys(obj);
},
@@ -442,7 +430,8 @@ export namespace Doc {
if (allowDuplicates !== true) {
const pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1);
if (pind !== -1) {
- list.splice(pind, 1);
+ return true;
+ //list.splice(pind, 1); // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection
}
}
if (first) {
@@ -681,7 +670,7 @@ export namespace Doc {
copy[key] = cfield[Copy]();// ComputedField.MakeFunction(cfield.script.originalScript);
} else if (field instanceof ObjectField) {
copy[key] = doc[key] instanceof Doc ?
- key.includes("layout[") ? Doc.MakeCopy(doc[key] as Doc, false) : doc[key] : // reference documents except copy documents that are expanded teplate fields
+ key.includes("layout[") ? undefined : doc[key] : // reference documents except remove documents that are expanded teplate fields
ObjectField.MakeCopy(field);
} else if (field instanceof Promise) {
debugger; //This shouldn't happend...
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index f81ec8c6d..66959882d 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -392,7 +392,7 @@ export namespace RichTextUtils {
const { attrs } = mark;
switch (converted) {
case "link":
- let url = attrs.href;
+ let url = attrs.allHrefs.length ? attrs.allHrefs[0].href : "";
const delimiter = "/doc/";
const alreadyShared = "?sharing=true";
if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index fc7f9ca80..11b3b0524 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -161,7 +161,7 @@ export class ComputedField extends ScriptField {
Scripting.addGlobal(function getIndexVal(list: any[], index: number) {
return list.reduce((p, x, i) => (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any);
-});
+}, "returns the value at a given index of a list", "(list: any[], index: number)");
export namespace ComputedField {
let useComputed = true;
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 54e7eca28..ad7b6ea7a 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -101,12 +101,16 @@ export function makeReadOnly() {
export function makeEditable() {
_setter = _setterImpl;
}
+var _overrideAcl = false;
+export function OVERRIDE_ACL(val: boolean) {
+ _overrideAcl = val;
+}
const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
"LODdisable", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
let prop = in_prop;
- if (target[AclSym]) return true;
+ if (target[AclSym] && !_overrideAcl) return true;
if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
if (!prop.startsWith("_")) {
console.log(prop + " is deprecated - switch to _" + prop);
@@ -125,8 +129,8 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
let prop = in_prop;
- if (in_prop === AclSym) return target[AclSym];
- if (target[AclSym] === AclPrivate) return undefined;
+ if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
+ if (target[AclSym] === AclPrivate && !_overrideAcl) return undefined;
if (prop === LayoutSym) {
return target.__LAYOUT__;
}
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 973931615..59c73ed27 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -114,7 +114,8 @@ export default class MobileInkOverlay extends React.Component {
altKey: false,
metaKey: false,
ctrlKey: false,
- shiftKey: false
+ shiftKey: false,
+ embedKey: false
}
}
)
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index da14ffc88..a50ec103e 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -22,7 +22,7 @@ import { listSpec } from '../fields/Schema';
import { Cast, FieldValue } from '../fields/Types';
import { WebField } from "../fields/URLField";
import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero } from '../Utils';
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from '../Utils';
import "./MobileInterface.scss";
import { CollectionView } from '../client/views/collections/CollectionView';
import { InkingStroke } from '../client/views/InkingStroke';
@@ -126,6 +126,7 @@ export default class MobileInterface extends React.Component {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />;
}
@@ -205,6 +206,7 @@ export default class MobileInterface extends React.Component {
whenActiveChanged={returnFalse}
ScreenToLocalTransform={Transform.Identity}
renderDepth={0}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
rootSelected={returnTrue}>
@@ -293,6 +295,7 @@ export default class MobileInterface extends React.Component {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
</div>
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 80f372733..ff0381fd3 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,7 +1,5 @@
import { Utils } from "../Utils";
import { Point } from "../pen-gestures/ndollar";
-import { Doc } from "../fields/Doc";
-import { Image } from "canvas";
import { AnalysisResult, ImportResults } from "../scraping/buxton/final/BuxtonImporter";
export class Message<T> {
diff --git a/src/server/database.ts b/src/server/database.ts
index a5f23c4b1..2372cbcf2 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -2,7 +2,6 @@ import * as mongodb from 'mongodb';
import { Transferable } from './Message';
import { Opt } from '../fields/Doc';
import { Utils, emptyFunction } from '../Utils';
-import { Credentials } from 'google-auth-library';
import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
import { IDatabase, DocumentsCollection } from './IDatabase';
import { MemoryDatabase } from './MemoryDatabase';