aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormehekj <mehek.jethani@gmail.com>2022-06-06 11:06:28 -0400
committermehekj <mehek.jethani@gmail.com>2022-06-06 11:06:28 -0400
commit0da188cccf46dea9e039d2f80907d2ee32edbfdb (patch)
tree6943902900274cc6d9ed954181cf935757a1e2ae /src
parent3b3695cfc5674a93c0ecee4d2af5d8ba8c8cfb70 (diff)
parent9acba91baa0ee2ee43106d344392039a2cbd0e46 (diff)
Merge branch 'master' into temporalmedia-mehek
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin8196 -> 8196 bytes
-rw-r--r--src/Utils.ts16
-rw-r--r--src/client/DocServer.ts60
-rw-r--r--src/client/documents/Documents.ts73
-rw-r--r--src/client/util/CurrentUserUtils.ts789
-rw-r--r--src/client/util/DocumentManager.ts70
-rw-r--r--src/client/util/DragManager.ts36
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx2
-rw-r--r--src/client/util/InteractionUtils.tsx2
-rw-r--r--src/client/util/LinkManager.ts11
-rw-r--r--src/client/util/SharingManager.tsx31
-rw-r--r--src/client/views/DocComponent.tsx4
-rw-r--r--src/client/views/DocumentButtonBar.tsx5
-rw-r--r--src/client/views/DocumentDecorations.tsx55
-rw-r--r--src/client/views/GestureOverlay.tsx31
-rw-r--r--src/client/views/GlobalKeyHandler.ts19
-rw-r--r--src/client/views/InkStrokeProperties.ts62
-rw-r--r--src/client/views/InkTranscription.scss5
-rw-r--r--src/client/views/InkTranscription.tsx298
-rw-r--r--src/client/views/InkingStroke.tsx55
-rw-r--r--src/client/views/LightboxView.tsx15
-rw-r--r--src/client/views/MainView.tsx160
-rw-r--r--src/client/views/MarqueeAnnotator.tsx6
-rw-r--r--src/client/views/OverlayView.tsx23
-rw-r--r--src/client/views/Palette.tsx1
-rw-r--r--src/client/views/PropertiesView.tsx39
-rw-r--r--src/client/views/ScriptingRepl.tsx2
-rw-r--r--src/client/views/SidebarAnnos.tsx4
-rw-r--r--src/client/views/StyleProvider.tsx92
-rw-r--r--src/client/views/TemplateMenu.tsx1
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx49
-rw-r--r--src/client/views/collections/CollectionMenu.tsx21
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx16
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx25
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx13
-rw-r--r--src/client/views/collections/CollectionSubView.tsx20
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx18
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx40
-rw-r--r--src/client/views/collections/CollectionView.tsx12
-rw-r--r--src/client/views/collections/TabDocView.tsx63
-rw-r--r--src/client/views/collections/TreeView.scss16
-rw-r--r--src/client/views/collections/TreeView.tsx162
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx70
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx186
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx11
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx108
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx5
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx51
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx49
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx2
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx5
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx1
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTable.tsx1
-rw-r--r--src/client/views/global/globalCssVariables.scss2
-rw-r--r--src/client/views/linking/LinkEditor.scss18
-rw-r--r--src/client/views/linking/LinkEditor.tsx30
-rw-r--r--src/client/views/linking/LinkPopup.tsx3
-rw-r--r--src/client/views/nodes/AudioBox.tsx26
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx4
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx8
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx4
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx1
-rw-r--r--src/client/views/nodes/DocumentView.tsx80
-rw-r--r--src/client/views/nodes/EquationBox.scss2
-rw-r--r--src/client/views/nodes/FieldView.tsx9
-rw-r--r--src/client/views/nodes/FilterBox.tsx46
-rw-r--r--src/client/views/nodes/ImageBox.tsx13
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx1
-rw-r--r--src/client/views/nodes/LabelBox.tsx20
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx1
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx1
-rw-r--r--src/client/views/nodes/LinkDocPreview.scss6
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx10
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx16
-rw-r--r--src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx4
-rw-r--r--src/client/views/nodes/PDFBox.tsx102
-rw-r--r--src/client/views/nodes/VideoBox.scss14
-rw-r--r--src/client/views/nodes/VideoBox.tsx17
-rw-r--r--src/client/views/nodes/WebBox.scss14
-rw-r--r--src/client/views/nodes/WebBox.tsx221
-rw-r--r--src/client/views/nodes/WebBoxRenderer.js26
-rw-r--r--src/client/views/nodes/button/ButtonScripts.ts4
-rw-r--r--src/client/views/nodes/button/FontIconBox.scss52
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx156
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx1
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx95
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx101
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts70
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx35
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts2
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts14
-rw-r--r--src/client/views/nodes/trails/PresBox.scss7
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx249
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss78
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx111
-rw-r--r--src/client/views/pdf/Annotation.tsx43
-rw-r--r--src/client/views/pdf/PDFViewer.scss50
-rw-r--r--src/client/views/pdf/PDFViewer.tsx192
-rw-r--r--src/client/views/search/SearchBox.tsx10
-rw-r--r--src/client/views/topbar/TopBar.tsx28
-rw-r--r--src/fields/Doc.ts37
-rw-r--r--src/fields/InkField.ts6
-rw-r--r--src/fields/List.ts1
-rw-r--r--src/fields/documentSchemas.ts1
-rw-r--r--src/fields/util.ts8
-rw-r--r--src/mobile/AudioUpload.tsx1
-rw-r--r--src/mobile/MobileInterface.tsx1
-rw-r--r--src/server/ApiManagers/UploadManager.ts14
-rw-r--r--src/server/server_Initialization.ts105
-rw-r--r--src/typings/index.d.ts1
111 files changed, 3109 insertions, 1976 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
index b0987293b..4bf9cdac7 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/Utils.ts b/src/Utils.ts
index 205f9379e..4dd9979ea 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -8,6 +8,7 @@ import Color = require('color');
export namespace Utils {
export let DRAG_THRESHOLD = 4;
+ export let SNAP_THRESHOLD = 10;
export function readUploadedFileAsText(inputFile: File) {
const temporaryFileReader = new FileReader();
@@ -208,7 +209,7 @@ export namespace Utils {
if (scrollTop + contextHgt < Math.min(scrollHeight, targetY + minSpacing + targetHgt)) {
return Math.ceil(targetY + minSpacing + targetHgt - contextHgt);
}
- if (scrollTop > Math.max(0, targetY - minSpacing - targetHgt)) {
+ if (scrollTop >= Math.max(0, targetY - minSpacing - targetHgt)) {
return Math.max(0, Math.floor(targetY - minSpacing - targetHgt));
}
}
@@ -218,14 +219,14 @@ export namespace Utils {
}
export function distanceBetweenHorizontalLines(xs: number, xe: number, y: number, xs2: number, xe2: number, y2: number): [number, number[]] {
- if ((xs2 < xs && xe2 > xs) || (xs2 < xe && xe2 > xe) || (xs2 > xs && xe2 < xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]];
- if (xe2 < xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]];
+ if ((xs2 <= xs && xe2 >= xs) || (xs2 <= xe && xe2 >= xe) || (xs2 >= xs && xe2 <= xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]];
+ if (xe2 <= xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]];
//if (xs2 > xe)
return [Math.sqrt((xs2 - xe) * (xs2 - xe) + (y2 - y) * (y2 - y)), [xe, y, xe, y]];
}
export function distanceBetweenVerticalLines(x: number, ys: number, ye: number, x2: number, ys2: number, ye2: number): [number, number[]] {
- if ((ys2 < ys && ye2 > ys) || (ys2 < ye && ye2 > ye) || (ys2 > ys && ye2 < ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]];
- if (ye2 < ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]];
+ if ((ys2 <= ys && ye2 >= ys) || (ys2 <= ye && ye2 >= ye) || (ys2 >= ys && ye2 <= ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]];
+ if (ye2 <= ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]];
//if (ys2 > ye)
return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]];
}
@@ -413,6 +414,7 @@ export function formatTime(time: number) {
return (hours ? hours.toString() + ":" : "") + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
}
+// x is furthest left, y is furthest top, r is furthest right, b is furthest bottom
export function aggregateBounds(boundsList: { x: number, y: number, width?: number, height?: number }[], xpad: number, ypad: number) {
const bounds = boundsList.map(b => ({ x: b.x, y: b.y, r: b.x + (b.width || 0), b: b.y + (b.height || 0) })).reduce((bounds, b) => ({
x: Math.min(b.x, bounds.x), y: Math.min(b.y, bounds.y),
@@ -440,6 +442,10 @@ export function returnTrue() { return true; }
export function returnFalse() { return false; }
+export function returnAll() { return "all"; }
+
+export function returnNone() { return "none"; }
+
export function returnVal(val1?: number, val2?: number) { return val1 !== undefined ? val1 : val2 !== undefined ? val2 : 0; }
export function returnOne() { return 1; }
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index e498a7cca..dbc4783d8 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -364,40 +364,36 @@ export namespace DocServer {
const proms: Promise<void>[] = [];
runInAction(() => {
for (const field of fields) {
- if (field !== undefined && field !== null && !_cache[field.id]) {
+ const cached = _cache[field.id];
+ if (!cached) {
// deserialize
- const cached = _cache[field.id];
- if (!cached) {
- const prom = SerializationHelper.Deserialize(field).then(deserialized => {
- fieldMap[field.id] = deserialized;
-
- //overwrite or delete any promises (that we inserted as flags
- // to indicate that the field was in the process of being fetched). Now everything
- // should be an actual value within or entirely absent from the cache.
- if (deserialized !== undefined) {
- _cache[field.id] = deserialized;
- } else {
- delete _cache[field.id];
- }
- return deserialized;
- });
- // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
- // we set the value at the field's id to a promise that will resolve to the field.
- // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
- // The mapping in the .then call ensures that when other callers await these promises, they'll
- // get the resolved field
- _cache[field.id] = prom;
-
- // adds to a list of promises that will be awaited asynchronously
- proms.push(prom);
- } else if (cached instanceof Promise) {
- proms.push(cached as any);
- }
- } else if (_cache[field.id] instanceof Promise) {
- proms.push(_cache[field.id] as any);
- (_cache[field.id] as any).then((f: any) => fieldMap[field.id] = f);
+ const prom = SerializationHelper.Deserialize(field).then(deserialized => {
+ fieldMap[field.id] = deserialized;
+
+ //overwrite or delete any promises (that we inserted as flags
+ // to indicate that the field was in the process of being fetched). Now everything
+ // should be an actual value within or entirely absent from the cache.
+ if (deserialized !== undefined) {
+ _cache[field.id] = deserialized;
+ } else {
+ delete _cache[field.id];
+ }
+ return deserialized;
+ });
+ // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
+ // we set the value at the field's id to a promise that will resolve to the field.
+ // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
+ // The mapping in the .then call ensures that when other callers await these promises, they'll
+ // get the resolved field
+ _cache[field.id] = prom;
+
+ // adds to a list of promises that will be awaited asynchronously
+ proms.push(prom);
+ } else if (cached instanceof Promise) {
+ proms.push(cached as any);
+ cached.then((f: any) => fieldMap[field.id] = f);
} else if (field) {
- proms.push(_cache[field.id] as any);
+ proms.push(cached as any);
fieldMap[field.id] = DocServer.GetCachedRefField(field.id) || field;
}
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index dd3e13cfd..2ca9cdb71 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -4,7 +4,7 @@ import { DateField } from "../../fields/DateField";
import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Initializing, Opt, updateCachedAcls, WidthSym } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { HtmlField } from "../../fields/HtmlField";
-import { InkField } from "../../fields/InkField";
+import { InkField, PointData } from "../../fields/InkField";
import { List } from "../../fields/List";
import { ProxyField } from "../../fields/Proxy";
import { RichTextField } from "../../fields/RichTextField";
@@ -116,14 +116,12 @@ export class DocumentOptions {
_fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents
_lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
_lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
- _freeformLOD?: boolean; // whether to use LOD to render a freeform document
_isPushpin?: boolean; // whether document, when clicked, toggles display of its link target
_showTitle?: string; // field name to display in header (:hover is an optional suffix)
_showCaption?: string; // which field to display in the caption area. leave empty to have no caption
_scrollTop?: number; // scroll location for pdfs
_noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged
_chromeHidden?: boolean; // whether the editing chrome for a document is hidden
- _layerTags?: List<string>; // layer tags a document has (used for tab filtering "layers" in document tab)
_searchDoc?: boolean; // is this a search document (used to change UI for search results in schema view)
_forceActive?: boolean; // flag to handle pointer events when not selected (or otherwise active)
_stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere
@@ -158,6 +156,7 @@ export class DocumentOptions {
"_carousel-caption-yMargin"?: number;
"icon-nativeWidth"?: number;
"icon-nativeHeight"?: number;
+ "dragFactory-count"?: number; // number of items created from a drag button (used for setting title with incrementing index)
x?: number;
y?: number;
z?: number; // whether document is in overlay (1) or not (0 or undefined)
@@ -175,6 +174,7 @@ export class DocumentOptions {
label?: string;
hidden?: boolean;
_hidden?: boolean;
+ pointerEvents?: string; // pointer events that the documentview should have
mediaState?: string; // status of media document: "pendingRecording", "recording", "paused", "playing"
autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline.
dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it.
@@ -369,7 +369,7 @@ export namespace Docs {
[DocumentType.RTF, {
layout: { view: FormattedTextBox, dataField: "text" },
options: {
- _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true,
+ _height: 35, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true,
links: "@links(self)"
}
}],
@@ -427,7 +427,7 @@ export namespace Docs {
childDontRegisterViews: true, _isLinkButton: true, _height: 150, description: "", showCaption: "description",
backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area
links: "@links(self)",
- _removeDropProperties: new List(["_layerTags", "isLinkButton"]),
+ _removeDropProperties: new List(["isLinkButton"]),
}
}],
[DocumentType.LINKDB, {
@@ -484,7 +484,7 @@ export namespace Docs {
}],
[DocumentType.MARKER, {
layout: { view: CollectionView, dataField: defaultDataKey },
- options: { links: "@links(self)", hideLinkButton: true }
+ options: { links: "@links(self)", hideLinkButton: true, pointerEvents: "none" }
}],
[DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
layout: { view: InkingStroke, dataField: defaultDataKey },
@@ -639,13 +639,14 @@ export namespace Docs {
const viewKeys = ["x", "y", "system"]; // keys that should be addded to the view document even though they don't begin with an "_"
const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_");
+ dataProps["acl-Override"] = "None";
+ dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+
dataProps.system = viewProps.system;
dataProps.isPrototype = true;
dataProps.author = Doc.CurrentUserEmail;
dataProps.creationDate = new DateField;
dataProps[`${fieldKey}-lastModified`] = new DateField;
- dataProps["acl-Override"] = "None";
- dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
dataProps[fieldKey] = data;
@@ -654,10 +655,12 @@ export namespace Docs {
dataProps[fieldKey + "-annotations"] = new List<Doc>();
const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true);
- viewProps.author = Doc.CurrentUserEmail;
- viewProps["acl-Override"] = "None";
- viewProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
- const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true);
+ const viewFirstProps: { [id: string]: any } = {};
+ viewFirstProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+ viewFirstProps["acl-Override"] = "None";
+ viewFirstProps.author = Doc.CurrentUserEmail;
+ const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true);
+ Doc.assign(viewDoc, viewProps, true, true);
![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
!Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) &&
@@ -673,8 +676,8 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options });
}
- export function PresDocument(initial: List<Doc> = new List(), options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.PRES), initial, options);
+ export function PresDocument(options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.PRES), new List<Doc>(), options);
}
export function ScriptingDocument(script: Opt<ScriptField>, options: DocumentOptions = {}, fieldKey?: string) {
@@ -694,8 +697,8 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options);
}
- export function ScreenshotDocument(title: string, options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", { ...options, title });
+ export function ScreenshotDocument(options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", options);
}
export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) {
@@ -746,7 +749,7 @@ export namespace Docs {
return linkDoc;
}
- export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) {
+ export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: PointData[], options: DocumentOptions = {}) {
const I = new Doc();
I[Initializing] = true;
I.type = DocumentType.INK;
@@ -771,6 +774,7 @@ export namespace Docs {
I.author = Doc.CurrentUserEmail;
I.rotation = 0;
I.data = new InkField(points);
+ I.creationDate = new DateField;
I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
I["acl-Override"] = "None";
I.links = ComputedField.MakeFunction("links(self)");
@@ -905,9 +909,7 @@ export namespace Docs {
}
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
- const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true, isFolder: true });
- const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true, isFolder: true });
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
}
export function DirectoryImportDocument(options: DocumentOptions = {}) {
@@ -1046,11 +1048,10 @@ export namespace DocUtils {
const key = docRangeFilters[i];
const min = Number(docRangeFilters[i + 1]);
const max = Number(docRangeFilters[i + 2]);
- const val = Cast(d[key], "number", null);
- if (val < min || val > max) return false;
+ const val = typeof d[key] === "string" ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], "number", null);
if (val === undefined) {
//console.log("Should 'undefined' pass range filter or not?")
- }
+ } else if (val < min || val > max) return false;
}
return true;
});
@@ -1317,7 +1318,7 @@ export namespace DocUtils {
}
export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
const templateName = templateSignature.replace(/\(.*\)/, "");
- docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.type), templateSignature);
+ docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc._isGroup && doc.transcription ? "transcription" : doc.type), templateSignature);
const customName = "layout_" + templateSignature;
const _width = NumCast(doc._width);
@@ -1407,30 +1408,6 @@ export namespace DocUtils {
return undefined;
}
- export async function addFieldEnumerations(doc: Opt<Doc>, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) {
- let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey);
- if (!(optionsCollection instanceof Doc)) {
- optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set`, system: true }, enumeratedFieldKey);
- Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc);
- }
- const options = optionsCollection as Doc;
- const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc);
- const docFind = `options.data?.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`;
- targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options }));
- targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options }));
- targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options }));
- enumerations.map(enumeration => {
- const found = DocListCast(options.data).find(d => d.title === enumeration.title);
- if (found) {
- found._backgroundColor = enumeration._backgroundColor || found._backgroundColor;
- found._color = enumeration.color || found._color;
- } else {
- Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, { ...enumeration, system: true }));
- }
- });
- return optionsCollection;
- }
-
// /**
// *
// * @param dms Degree Minute Second format exif gps data
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 9720343e1..51bfdbbd2 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -6,7 +6,6 @@ import { InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
import { RichTextField } from "../../fields/RichTextField";
-import { listSpec } from "../../fields/Schema";
import { ComputedField, ScriptField } from "../../fields/ScriptField";
import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
@@ -16,15 +15,18 @@ import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils } from "../documents/Documents";
import { DocumentType } from "../documents/DocumentTypes";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { CollectionFreeFormView } from "../views/collections/collectionFreeForm";
+import { TreeViewType } from "../views/collections/CollectionTreeView";
import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
import { TreeView } from "../views/collections/TreeView";
import { Colors } from "../views/global/globalEnums";
import { MainView } from "../views/MainView";
import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
-import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
+import { LabelBox } from "../views/nodes/LabelBox";
+import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
import { OverlayView } from "../views/OverlayView";
import { DocumentManager } from "./DocumentManager";
-import { DragManager } from "./DragManager";
+import { DragManager, dropActionType } from "./DragManager";
import { makeTemplate, MakeTemplate } from "./DropConverter";
import { HistoryUtil } from "./History";
import { LinkManager } from "./LinkManager";
@@ -51,16 +53,15 @@ interface Button {
list?: string[];
ignoreClick?: boolean;
buttonText?: string;
+ hidden?: string;
}
export let resolvedPorts: { server: number, socket: number };
-const headerViewVersion = "0.1";
export class CurrentUserUtils {
private static curr_id: string;
//TODO tfs: these should be temporary...
private static mainDocId: string | undefined;
- public static searchBtn: Doc;
public static get id() { return this.curr_id; }
public static get MainDocId() { return this.mainDocId; }
public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
@@ -70,204 +71,71 @@ export class CurrentUserUtils {
@observable public static GuestDashboard: Doc | undefined;
@observable public static GuestMobile: Doc | undefined;
@observable public static propertiesWidth: number = 0;
+ @observable public static headerBarHeight: number = 0;
@observable public static searchPanelWidth: number = 0;
// sets up the default User Templates - slideView, headerView
- static setupUserTemplateButtons(doc: Doc) {
- // Prototype for mobile button (not sure if 'Advanced Item Prototypes' is ideal location)
- if (doc["template-mobile-button"] === undefined) {
- const queryTemplate = this.mobileButton({
- title: "NEW MOBILE BUTTON",
- onClick: undefined,
- },
- [this.createToolButton({
- ignoreClick: true,
- icon: "mobile",
- btnType: ButtonType.ToolButton,
- backgroundColor: "transparent"
- }),
- this.mobileTextContainer({},
- [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]);
- doc["template-mobile-button"] = CurrentUserUtils.createToolButton({
- onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
- dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile", btnType: ButtonType.ToolButton,
- });
- }
-
- if (doc["template-button-slides"] === undefined) {
- const slideTemplate = Docs.Create.MultirowDocument(
- [
- Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }),
- Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) })
- ],
- { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true }
- );
- slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
- doc["template-button-slides"] = CurrentUserUtils.createToolButton({
- onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
- dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", icon: "address-card",
- btnType: ButtonType.ToolButton
- });
- }
-
- if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to.
- const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created
- linkTemplate.system = true;
- Doc.GetProto(linkTemplate).layout =
- "<div>" +
- " <FormattedTextBox {...props} dontSelectOnLoad={'true'} height='{this._headerHeight||75}px' ignoreAutoHeight={'true'} background='{this._headerColor||`lightGray`}' fieldKey={'header'}/>" +
- " <FormattedTextBox {...props} position='absolute' top='{(this._headerHeight||75)*scale}px' height='calc({100/scale}% - {this._headerHeight||75}px)' fieldKey={'text'}/>" +
- "</div>";
- (linkTemplate.proto as Doc).isTemplateDoc = makeTemplate(linkTemplate.proto as Doc, 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
- }
- }]
- }]
+ static setupExperimentalTemplateButtons(doc: Doc) {
+ if (doc["template-experimental-buttons"] === undefined) {
+ const requiredTypeNameFields = [
+ {
+ type: "slide", icon: "address-card", template: Docs.Create.MultirowDocument(
+ [
+ Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }),
+ Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) })
+ ],
+ { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true }
+ )
},
- selection: { type: "text", anchor: 1, head: 1 },
- storedMarks: []
- };
- linkTemplate.header = new RichTextField(JSON.stringify(rtf2), "");
-
- doc["template-button-link"] = CurrentUserUtils.createToolButton({
- onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
- dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", icon: "window-maximize", system: true,
- btnType: ButtonType.ToolButton
+ {
+ type: "mobile", icon: "mobile", template: this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, },
+ [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }),
+ this.mobileTextContainer({},
+ [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])
+ ]
+ )
+ },
+ ];
+ const requiredTypes = requiredTypeNameFields.map(({ type, icon, template }) => {
+ if (doc[`template-button-${type}`] === undefined) {
+ template.isTemplateDoc = makeTemplate(template);
+ doc[`template-button-${type}`] = CurrentUserUtils.createToolButton({
+ onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
+ dragFactory: new PrefetchProxy(template) as any as Doc,
+ title: type,
+ icon,
+ });
+ }
+ return doc[`template-button-${type}`] as Doc;
});
- }
- // if (doc["template-button-switch"] === undefined) {
- // const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create;
-
- // const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40, system: true });
- // const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1, system: true });
- // const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, system: true });
- // const labelTemplate = {
- // doc: {
- // type: "doc", content: [{
- // type: "paragraph",
- // content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }]
- // }]
- // },
- // selection: { type: "text", anchor: 1, head: 1 },
- // storedMarks: []
- // };
- // Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS");
- // Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'");
- // // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'");
- // // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true");
- // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]");
- // // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false");
- // const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, system: true });
- // box.isTemplateDoc = makeTemplate(box, true, "switch");
-
- // doc["template-button-switch"] = CurrentUserUtils.createToolButton({
- // onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'),
- // dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true,
- // btnType: ButtonType.ToolButton
- // });
- // }
-
- const requiredTypes = [
- doc["template-button-slides"] as Doc,
- doc["template-mobile-button"] 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(requiredTypes, {
- title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeHidden: true,
+ doc["template-experimental-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, {
+ title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true,
hidden: ComputedField.MakeFunction("IsNoviceMode()") as any,
_stayInCollection: true, _hideContextMenu: true, _forceActive: true,
_autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true
}));
- } else {
- const curButnTypes = Cast(doc["template-buttons"], Doc, null);
- DocListCastAsync(curButnTypes.data).then(async curBtns => {
- curBtns && await Promise.all(curBtns);
- requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype));
- });
}
- return doc["template-buttons"] as Doc;
+ return doc["template-experimental-buttons"] as Doc;
}
// setup the different note type skins
static setupNoteTemplates(doc: Doc) {
- if (doc["template-note-Note"] === undefined) {
- const noteView = Docs.Create.TextDocument("", {
- title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true, icon: "sticky-note",
- _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- });
- noteView.isTemplateDoc = makeTemplate(noteView, true, "Note");
- doc["template-note-Note"] = new PrefetchProxy(noteView);
- }
- if (doc["template-note-Idea"] === undefined) {
- const noteView = Docs.Create.TextDocument("", {
- title: "text", backgroundColor: "pink", system: true, icon: "lightbulb",
- _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- });
- noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea");
- doc["template-note-Idea"] = new PrefetchProxy(noteView);
- }
- if (doc["template-note-Topic"] === undefined) {
- const noteView = Docs.Create.TextDocument("", {
- title: "text", backgroundColor: "lightblue", system: true, icon: "book-open",
- _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- });
- noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic");
- doc["template-note-Topic"] = new PrefetchProxy(noteView);
- }
- // if (doc["template-note-Todo"] === undefined) {
- // const noteView = Docs.Create.TextDocument("", {
- // title: "text", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption",
- // layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true,
- // _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- // });
- // noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo");
- // doc["template-note-Todo"] = new PrefetchProxy(noteView);
- // }
- // const taskStatusValues = [
- // { title: "todo", _backgroundColor: "blue", color: "white", system: true },
- // { title: "in progress", _backgroundColor: "yellow", color: "black", system: true },
- // { title: "completed", _backgroundColor: "green", color: "white", system: true }
- // ];
- // if (doc.fieldTypes === undefined) {
- // doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations", system: true });
- // DocUtils.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues);
- // }
-
if (doc["template-notes"] === undefined) {
- doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc], // doc["template-note-Todo"] as any as Doc],
- { title: "Note Layouts", _height: 75, system: true }));
- } else {
- const curNoteTypes = Cast(doc["template-notes"], Doc, null);
- const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc];//, doc["template-note-Todo"] as any as Doc];
- DocListCastAsync(curNoteTypes.data).then(async curNotes => {
- curNotes && await Promise.all(curNotes);
- requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype));
- });
+ const requiredTypeNameFields = [
+ { type: "Note", backgroundColor: "yellow", icon: "sticky-note" },
+ { type: "Idea", backgroundColor: "pink", icon: "lightbulb" },
+ { type: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
+ const requiredTypes = requiredTypeNameFields.map(({ type, backgroundColor, icon }) =>
+ doc[`template-note-${type}`] as Doc ??
+ (() => {
+ const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor, system: true, icon });
+ noteView.isTemplateDoc = makeTemplate(noteView, true, type);
+ return doc[`template-note-${type}`] = new PrefetchProxy(noteView);
+ })());
+
+ doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument(requiredTypes, { title: "Note Layouts", _height: 75, system: true }));
}
return doc["template-notes"] as Doc;
@@ -275,11 +143,12 @@ export class CurrentUserUtils {
// creates Note templates, and initial "user" templates
static setupDocTemplates(doc: Doc) {
- const noteTemplates = CurrentUserUtils.setupNoteTemplates(doc);
- const userTemplateBtns = CurrentUserUtils.setupUserTemplateButtons(doc);
- const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc);
if (doc.templateDocs === undefined) {
- doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], {
+ doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([
+ CurrentUserUtils.setupNoteTemplates(doc),
+ CurrentUserUtils.setupExperimentalTemplateButtons(doc),
+ CurrentUserUtils.setupClickEditorTemplates(doc)
+ ], {
title: "template layouts", _xMargin: 0, system: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
}));
@@ -311,21 +180,26 @@ export class CurrentUserUtils {
"icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4,
_showTitle: "title", system: true, onClick: deiconifyScript(), ...extra
});
- const newIconsList = [
- makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })),
- makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })),
- makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })),
- makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })),
- makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })),
- makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })),
- makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})),
- makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})),
- makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() })),
- // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {}))
- ].filter(d => d).map(d => d!);
+ const fontBox = () => Docs.Create.FontIconDocument({
+ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript()
+ });
if (!templateIconsDoc) {
+ const newIconsList = [
+ makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })),
+ makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })),
+ makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })),
+ makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })),
+ makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })),
+ makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })),
+ makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})),
+ makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})),
+ makeIconTemplate(DocumentType.BUTTON, "data", fontBox),
+ //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
+ makeIconTemplate("transcription" as any, "transcription", () => labelBox({ _backgroundColor: "orange" })),
+ // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {}))
+ ].filter(d => d).map(d => d!);
+
doc["template-icons"] = Docs.Create.TreeDocument(newIconsList, { title: "icon templates", _height: 75, system: true });
- newIconsList.map(d => (doc["template-icons"] as Doc)[StrCast(d.title)] = new PrefetchProxy(d));
}
}
@@ -333,31 +207,23 @@ export class CurrentUserUtils {
title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean,
click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc
}[] {
+ const standardOps = () => ({ _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List<string>(["system"]) });
if (doc.emptyPresentation === undefined) {
- doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(),
- { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0;
+ doc.emptyPresentation = Docs.Create.PresDocument({ ...standardOps(), title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" });
}
if (doc.emptyCollection === undefined) {
- doc.emptyCollection = Docs.Create.FreeformDocument([],
- { _nativeWidth: undefined, _nativeHeight: undefined, _fitWidth: true, _width: 150, _height: 100, title: "freeform", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyCollection as Doc).proto as Doc)["dragFactory-count"] = 0;
+ doc.emptyCollection = Docs.Create.FreeformDocument([], { ...standardOps(), title: "freeform", _width: 150, _height: 100 });
}
if (doc.emptyPane === undefined) {
- doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _backgroundGridShow: true, _nativeHeight: undefined, _width: 500, _height: 800, title: "Untitled Tab", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0;
+ doc.emptyPane = Docs.Create.FreeformDocument([], { ...standardOps(), title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 });
}
if (doc.emptySlide === undefined) {
- const textDoc = Docs.Create.TreeDocument([], {
- title: "Slide", _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true,
- allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true,
- backgroundColor: "white", system: true, cloneFieldFilter: new List<string>(["system"])
+ doc.emptySlide = Docs.Create.TreeDocument([], {
+ ...standardOps(), title: ComputedField.MakeFunction('self.text?.Text') as any, _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, "dragFactory-count": undefined,
+ allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white"
});
- Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text');
- FormattedTextBox.SelectOnLoad = textDoc[Id];
- doc.emptySlide = textDoc;
}
- if ((doc.emptyHeader as Doc)?.version !== headerViewVersion) {
+ if (doc.emptyHeader === undefined) {
const json = {
doc: {
type: "doc",
@@ -378,9 +244,8 @@ export class CurrentUserUtils {
storedMarks: []
};
const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), {
- title: "text", version: headerViewVersion, _height: 70, _headerPointerEvents: "all",
- _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, _fitWidth: true,
- cloneFieldFilter: new List<string>(["system"])
+ ...standardOps(), title: "text", _height: 70,
+ _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,
}, "header");
const headerBtnHgt = 10;
headerTemplate[DataSym].layout =
@@ -396,47 +261,34 @@ export class CurrentUserUtils {
// "</div>";
(headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView");
doc.emptyHeader = headerTemplate;
- ((doc.emptyHeader as Doc).proto as Doc)["dragFactory-count"] = 0;
}
if (doc.emptyComparison === undefined) {
- doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "comparison box", _width: 300, _height: 300, system: true, cloneFieldFilter: new List<string>(["system"]) });
+ doc.emptyComparison = Docs.Create.ComparisonDocument({ ...standardOps(), title: "Comparer", _width: 300, _height: 300 });
}
if (doc.emptyScript === undefined) {
- doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0;
+ doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { ...standardOps(), title: "script", _width: 200, _height: 250, });
}
if (doc.emptyScreenshot === undefined) {
- doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, title: "empty screenshot", _width: 400, _height: 200, system: true, cloneFieldFilter: new List<string>(["system"]) });
+ doc.emptyScreenshot = Docs.Create.ScreenshotDocument({ ...standardOps(), title: "empty screenshot", _width: 400, _height: 200 });
}
if (doc.emptyWall === undefined) {
- doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List<string>(["system"]) });
+ doc.emptyWall = Docs.Create.ScreenshotDocument({ ...standardOps(), title: "screen snapshot", _width: 400, _height: 200, });
(doc.emptyWall as Doc).videoWall = true;
}
if (doc.emptyAudio === undefined) {
- doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "audio recording", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0;
+ doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { ...standardOps(), title: "audio recording", x: 200, y: 200, _width: 200, _height: 100, });
}
if (doc.emptyNote === undefined) {
- doc.emptyNote = Docs.Create.TextDocument("", {
- _width: 200, title: "text note", _autoHeight: true, system: true,
- _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize),
- cloneFieldFilter: new List<string>(["system"])
- });
- ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0;
- }
- if (doc.emptyImage === undefined) {
- doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat", system: true });
+ doc.emptyNote = Docs.Create.TextDocument("", { ...standardOps(), title: "text note", _width: 200, _autoHeight: true });
}
if (doc.emptyButton === undefined) {
- doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0;
+ doc.emptyButton = Docs.Create.ButtonDocument({ ...standardOps(), title: "Button", _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, });
}
if (doc.emptyWebpage === undefined) {
- doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, system: true, cloneFieldFilter: new List<string>(["system"]) });
+ doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { ...standardOps(), title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, });
}
if (doc.emptyMap === undefined) {
- doc.emptyMap = Docs.Create.MapDocument([], { title: "map", _showSidebar: true, _width: 800, _height: 600, system: true, cloneFieldFilter: new List<string>(["system"]) });
- ((doc.emptyMap as Doc).proto as Doc)["dragFactory-count"] = 0;
+ doc.emptyMap = Docs.Create.MapDocument([], { ...standardOps(), title: "map", _showSidebar: true, _width: 800, _height: 600, });
}
if (doc.activeMobileMenu === undefined) {
this.setupActiveMobileMenu(doc);
@@ -446,11 +298,10 @@ export class CurrentUserUtils {
{ toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, },
{ toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true },
{ toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc },
- { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc },
{ toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true },
{ toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc },
{ toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc },
- { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true },
+ { toolTip: "Tap to create an audio recorder in a overlay pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openInOverlay(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true },
{ toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc },
// { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
{ toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc },
@@ -463,15 +314,11 @@ export class CurrentUserUtils {
}
// setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
- static async setupCreatorButtons(doc: Doc) {
+ static setupCreatorButtons(doc: Doc) {
let alreadyCreatedButtons: string[] = [];
const dragCreatorSet = Cast(doc.myItemCreators, Doc, null);
if (dragCreatorSet) {
- const dragCreators = Cast(dragCreatorSet.data, listSpec(Doc));
- if (dragCreators) {
- const dragDocs = await Promise.all(Array.from(dragCreators));
- alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title));
- }
+ alreadyCreatedButtons = DocListCast(dragCreatorSet.data).map(d => StrCast(d.title));
}
const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({
@@ -507,7 +354,7 @@ export class CurrentUserUtils {
return doc.myItemCreators as Doc;
}
- static async menuBtnDescriptions(doc: Doc) {
+ static menuBtnDescriptions(doc: Doc) {
return [
{ title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' },
{ title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' },
@@ -524,7 +371,7 @@ export class CurrentUserUtils {
static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
if (doc.menuStack === undefined) {
await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing
- const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments, hidden }) =>
+ const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments, hidden }) =>
Docs.Create.FontIconDocument({
icon,
btnType: ButtonType.MenuButton,
@@ -545,17 +392,7 @@ export class CurrentUserUtils {
})
);
- menuBtns.forEach(menuBtn => {
- if (menuBtn.title === "Search") {
- this.searchBtn = menuBtn;
- }
- });
-
- menuBtns.forEach(menuBtn => {
- if (menuBtn.title === "Search") {
- doc.searchBtn = menuBtn;
- }
- });
+ doc.searchBtn = menuBtns.find(btn => btn.title === "Search");
doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, {
title: "menuItemPanel",
@@ -570,19 +407,6 @@ export class CurrentUserUtils {
_yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true
}));
}
- // this resets all sidebar buttons to being deactivated
- PromiseValue(Cast(doc.menuStack, Doc)).then(stack => {
- stack && PromiseValue(stack.data).then(btns => {
- DocListCastAsync(btns).then(bts => bts?.forEach(btn => {
- btn.dontUndo = true;
- btn.system = true;
- if (btn.title === "Catalog" || btn.title === "My Files") { // migration from Catalog to My Files
- btn.target = Doc.UserDoc().myFilesystem;
- btn.title = "My Files";
- }
- }));
- });
- });
return doc.menuStack as Doc;
}
@@ -712,35 +536,26 @@ export class CurrentUserUtils {
// setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker.
// when clicked, this panel will be displayed in the target container (ie, sidebarContainer)
- static async setupToolsBtnPanel(doc: Doc) {
+ static setupToolsBtnPanel(doc: Doc) {
// setup a masonry view of all he creators
- const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc);
- const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc);
-
- doc["tabs-button-tools"] = undefined;
+ const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc);
+ const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc);
- if (doc.myCreators === undefined) {
- doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], {
- title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true,
- _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true,
- }));
- }
- // setup a color picker
- if (doc.myColorPicker === undefined) {
- const color = Docs.Create.ColorDocument({
- title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List<string>(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true
- });
- doc.myColorPicker = new PrefetchProxy(color);
- }
+ doc.myCreators = doc.myCreators ?? new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], {
+ title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true,
+ _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true,
+ }));
+ if (!DocListCast(doc.myCreators).includes(creatorBtns) || !DocListCast(doc.myCreators).includes(templateBtns)) Doc.GetProto(doc.myCreators as Doc).data = new List<Doc>([creatorBtns, templateBtns]);
- if (doc.myTools === undefined) {
- const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc], {
- title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true,
- system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0",
- })) as any as Doc;
+ doc.myColorPicker = doc.myColorPicker ?? new PrefetchProxy(Docs.Create.ColorDocument({
+ title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List<string>(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true
+ }));
- doc.myTools = toolsStack;
- }
+ doc.myTools = doc.myTools ?? new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc], {
+ title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true,
+ system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0",
+ })) as any as Doc;
+ if (!DocListCast(doc.myTools).includes(doc.myCreators as Doc)) Doc.GetProto(doc.myTools as Doc).data = new List<Doc>([doc.myCreators as Doc]);
}
static async setupDashboards(doc: Doc) {
@@ -753,7 +568,7 @@ export class CurrentUserUtils {
title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, freezeChildren: "remove|add",
treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton,
- _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true,
+ _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true,
explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
}));
const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`);
@@ -773,8 +588,7 @@ export class CurrentUserUtils {
return doc.myDashboards as any as Doc;
}
- static async setupPresentations(doc: Doc) {
- await doc.myTrails;
+ static setupPresentations(doc: Doc) {
if (doc.myTrails === undefined) {
const newTrail = ScriptField.MakeScript(`createNewPresentation()`);
const newTrailButton: Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "Create new trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
@@ -807,7 +621,7 @@ export class CurrentUserUtils {
title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
- isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true,
+ isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true,
explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
}));
@@ -877,7 +691,7 @@ export class CurrentUserUtils {
// setup the list of sidebar mode buttons which determine what is displayed in the sidebar
static async setupSidebarButtons(doc: Doc) {
CurrentUserUtils.setupSidebarContainer(doc);
- await CurrentUserUtils.setupToolsBtnPanel(doc);
+ CurrentUserUtils.setupToolsBtnPanel(doc);
CurrentUserUtils.setupImportSidebar(doc);
CurrentUserUtils.setupDashboards(doc);
CurrentUserUtils.setupPresentations(doc);
@@ -893,22 +707,22 @@ export class CurrentUserUtils {
})) as any as Doc
static createToolButton = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({
- ...opts, btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _removeDropProperties: new List<string>(["_dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true
+ btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List<string>(["_dropAction", "_hideContextMenu", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts,
})) as any as Doc
/// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
static setupDockedButtons(doc: Doc) {
- if (doc["dockedBtn-undo"] === undefined) {
- doc["dockedBtn-undo"] = CurrentUserUtils.createToolButton({ onClick: ScriptField.MakeScript("undo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List<string>(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo", title: "undo", icon: "undo-alt", system: true });
- }
- if (doc["dockedBtn-redo"] === undefined) {
- doc["dockedBtn-redo"] = CurrentUserUtils.createToolButton({ onClick: ScriptField.MakeScript("redo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List<string>(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", title: "redo", icon: "redo-alt", system: true });
- }
if (doc.dockedBtns === undefined) {
- doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]);
+ const dockBtn = (opts: DocumentOptions) => CurrentUserUtils.createToolButton({ _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...opts });
+ const btnDescs = [
+ { title: "undo", opts: () => ({ icon: "undo-alt", onClick: ScriptField.MakeScript("undo()"), toolTip: "Click to undo" }) },
+ { title: "redo", opts: () => ({ icon: "redo-alt", onClick: ScriptField.MakeScript("redo()"), toolTip: "Click to redo" }) }
+ ];
+ doc.dockedBtns = CurrentUserUtils.linearButtonList({
+ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true,
+ linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true
+ }, btnDescs.map(desc => doc[`dockedBtn-${desc.title}`] as Doc ?? (doc[`dockedBtn-${desc.title}`] = dockBtn({ title: desc.title, ...desc.opts() }))));
}
- (doc["dockedBtn-undo"] as Doc).dontUndo = true;
- (doc["dockedBtn-redo"] as Doc).dontUndo = true;
}
static textTools(doc: Doc) {
@@ -920,51 +734,44 @@ export class CurrentUserUtils {
"Comic Sans MS", "Tahoma", "Impact", "Crimson Text"],
script: 'setFont(value, _readOnly_)'
},
- { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize(value, _readOnly_)' },
- { title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor(value, _readOnly_)' },
+ { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize(value, _readOnly_)' },
+ { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor(value, _readOnly_)' },
{ title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold(_readOnly_)' },
{ title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic(_readOnly_)' },
- { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline(_readOnly_)' },
- { title: "Bullet List", toolTip: "Bullet", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet", _readOnly_)' },
- { title: "Number List", toolTip: "Number", btnType: ButtonType.ToggleButton, icon: "list-ol", click: 'setBulletList("decimal", _readOnly_)' },
+ { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline(_readOnly_)' },
+ { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet", _readOnly_)' },
+ { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", click: 'setBulletList("decimal", _readOnly_)' },
// { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'},
// { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'},
// { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'},
- { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left", _readOnly_)' },
- { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center", _readOnly_)' },
- { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right", _readOnly_)' },
+ { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left", _readOnly_)' },
+ { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center", _readOnly_)' },
+ { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right", _readOnly_)' },
+ { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", click: 'toggleNoAutoLinkAnchor(_readOnly_)' },
];
return tools;
}
static inkTools(doc: Doc) {
const tools: Button[] = [
- { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen", _readOnly_)' },
+ { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", click: 'setActiveInkTool("pen", _readOnly_)' },
+ { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("write", _readOnly_)' },
{ title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", click: 'setActiveInkTool("eraser", _readOnly_)' },
// { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")' },
{ title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle", _readOnly_)' },
// { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' },
{ title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line", _readOnly_)' },
- { title: "Fill color", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor(value, _readOnly_)" },
- { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth(value, _readOnly_)' },
- { title: "Stroke color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor(value, _readOnly_)' },
+ { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor(value, _readOnly_)" },
+ { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth(value, _readOnly_)' },
+ { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor(value, _readOnly_)' },
];
return tools;
}
static schemaTools(doc: Doc) {
const tools: Button[] =
- [
- {
- title: "Show preview",
- toolTip: "Show preview of selected document",
- btnType: ButtonType.ToggleButton,
- buttonText: "Show Preview",
- icon: "eye",
- click: 'toggleSchemaPreview(_readOnly_)',
- },
- ];
+ [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", click: 'toggleSchemaPreview(_readOnly_)', }];
return tools;
}
@@ -980,7 +787,7 @@ export class CurrentUserUtils {
return tools;
}
- static async contextMenuTools(doc: Doc) {
+ static contextMenuTools(doc: Doc) {
return [
{
title: "Perspective", toolTip: "View", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true,
@@ -991,120 +798,85 @@ export class CurrentUserUtils {
CollectionViewType.Grid],
script: 'setView(value, _readOnly_)',
}, // Always show
- {
- title: "Background Color", toolTip: "Background Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip",
- script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()'
- }, // Only when a document is selected
- {
- title: "Header Color", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading",
- script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()',
- }, // Only when a document is selected
+ { title: "Back", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'prevKeyFrame(_readOnly_)', icon: "chevron-left", hidden: 'IsNoviceMode()' },
+ { title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'nextKeyFrame(_readOnly_)', icon: "chevron-right", hidden: 'IsNoviceMode()' },
+ { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()' }, // Only when a document is selected
+ { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()', },
{ title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay(_readOnly_)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform
// { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected
- { title: "Text", type: "textTools", subMenu: true, expanded: 'selectedDocumentType("rtf")' }, // Always available
- { title: "Ink", type: "inkTools", subMenu: true, expanded: 'selectedDocumentType("ink")' }, // Always available
- { title: "Web", type: "webTools", subMenu: true, hidden: 'selectedDocumentType("web")' }, // Only when Web is selected
- { title: "Schema", type: "schemaTools", subMenu: true, hidden: 'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected
+ { title: "Text", type: "textTools", subMenu: CurrentUserUtils.textTools(doc), expanded: 'selectedDocumentType("rtf")' }, // Always available
+ { title: "Ink", type: "inkTools", subMenu: CurrentUserUtils.inkTools(doc), expanded: 'selectedDocumentType("ink")' }, // Always available
+ { title: "Web", type: "webTools", subMenu: CurrentUserUtils.webTools(doc), hidden: 'selectedDocumentType("web")' }, // Only when Web is selected
+ { title: "Schema", type: "schemaTools", subMenu: CurrentUserUtils.schemaTools(doc), hidden: 'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected
];
}
// Sets up the default context menu buttons
- static async setupContextMenuButtons(doc: Doc) {
+ static setupContextMenuButtons(doc: Doc) {
+ const btnFunc = (params: Button) => Docs.Create.FontIconDocument({
+ title: params.title, icon: params.icon, toolTip: params.toolTip, color: Colors.WHITE, system: true, dontUndo: true, ignoreClick: params.ignoreClick,
+ _nativeWidth: params.width ? params.width : 30,
+ _nativeHeight: 30,
+ _width: params.width ? params.width : 30,
+ _height: 30,
+ btnType: params.btnType,
+ numBtnType: params.numBtnType, numBtnMin: params.numBtnMin, numBtnMax: params.numBtnMax,
+ btnList: new List<string>(params.list),
+ _stayInCollection: true,
+ _hideContextMenu: true,
+ _lockedPosition: true,
+ _dropAction: "alias",
+ _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
+ script: params.script ? ScriptField.MakeScript(params.script, { value: "any" }) : undefined,
+ backgroundColor: params.click ? ComputedField.MakeFunction(params.click) as any : "transparent",
+ onClick: params.click ? ScriptField.MakeScript(params.click, { scriptContext: "any" }, { _readOnly_: false }) : undefined,
+ hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined,
+ });
if (doc.contextMenuBtns === undefined) {
- const docList: Doc[] = [];
-
- (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded }) => {
- const menuDocList: Doc[] = [];
- if (subMenu) {
- // default is textTools
- let tools: Button[];
- switch (type) {
- case "inkTools":
- tools = CurrentUserUtils.inkTools(doc);
- break;
- case "schemaTools":
- tools = CurrentUserUtils.schemaTools(doc);
- break;
- case "webTools":
- tools = CurrentUserUtils.webTools(doc);
- break;
- case "textTools":
- tools = CurrentUserUtils.textTools(doc);
- break;
- default:
- tools = CurrentUserUtils.textTools(doc);
- break;
- }
- tools.map(({ title, toolTip, icon, btnType, numBtnType, numBtnMax, numBtnMin, click, script, width, list, ignoreClick, switchToggle }) => {
- const computed = click ? ComputedField.MakeFunction(click) as any : "transparent";
- menuDocList.push(Docs.Create.FontIconDocument({
- _nativeWidth: width ? width : 25,
- _nativeHeight: 25,
- _width: width ? width : 25,
- _height: 25,
- icon,
- toolTip,
- numBtnType,
- numBtnMin,
- numBtnMax,
- script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined,
- btnType: btnType,
- btnList: new List<string>(list),
- ignoreClick: ignoreClick,
- _stayInCollection: true,
- _hideContextMenu: true,
- _lockedPosition: true,
- system: true,
- dontUndo: true,
- title,
- switchToggle,
- color: Colors.WHITE,
- backgroundColor: computed,
- _dropAction: "alias",
- _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
- onClick: click ? ScriptField.MakeScript(click) : undefined
- }));
+ doc.contextMenuBtns = CurrentUserUtils.linearButtonList(
+ { title: "menu buttons", flexGap: 0, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 },
+ CurrentUserUtils.contextMenuTools(doc).map(params =>
+ !params.subMenu ?
+ btnFunc(params) :
+ CurrentUserUtils.linearButtonList({
+ title: params.title,
+ linearViewSubMenu: true, flexGap: 0, ignoreClick: true,
+ linearViewExpandable: true, icon: params.title, _height: 30,
+ linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined,
+ hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined,
+ }, params.subMenu.map(btnFunc))));
+ } else {
+ const menuBtnList = DocListCast((doc.contextMenuBtns as Doc).data);
+ let prev = "";
+ CurrentUserUtils.contextMenuTools(doc).forEach(params => {
+ const menuBtnDoc = menuBtnList.find(doc => doc.title === params.title);
+ if (!menuBtnDoc) {
+ const newMenuBtnDoc = !params.subMenu ?
+ btnFunc(params) :
+ CurrentUserUtils.linearButtonList({
+ title: params.title,
+ linearViewSubMenu: true, flexGap: 0, ignoreClick: true,
+ linearViewExpandable: true, icon: params.title, _height: 30,
+ linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined,
+ hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined,
+ }, params.subMenu.map(btnFunc));
+ const after = menuBtnList.find(doc => doc.title === prev);
+ Doc.AddDocToList(doc.contextMenuBtns as Doc, "data", newMenuBtnDoc, after, false, !after);
+ }
+ const subMenuBtnList = menuBtnDoc?.data ? DocListCast(menuBtnDoc.data) : undefined;
+ if (menuBtnDoc && subMenuBtnList && params.subMenu && DocListCast(doc.data).length !== subMenuBtnList.length) {
+ let prevSub = "";
+ params.subMenu.forEach(sub => {
+ if (!subMenuBtnList.find(doc => doc.title === sub.title)) {
+ const newSubMenuBtnDoc = btnFunc(sub);
+ const after = subMenuBtnList.find(doc => doc.title === prevSub);
+ Doc.AddDocToList(menuBtnDoc, "data", newSubMenuBtnDoc, after, false, !prevSub);
+ }
+ prevSub = params.title;
});
- docList.push(CurrentUserUtils.linearButtonList({
- linearViewSubMenu: true,
- flexGap: 0,
- ignoreClick: true,
- linearViewExpandable: true,
- icon: title,
- _height: 30,
- // backgroundColor: hidden ? ComputedField.MakeFunction(hidden, { }, { _readOnly_: true }) as any : "transparent",
- linearViewIsExpanded: expanded ? !(ComputedField.MakeFunction(expanded) as any) : undefined,
- hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined,
- }, menuDocList));
- } else {
- docList.push(Docs.Create.FontIconDocument({
- _nativeWidth: width ? width : 25,
- _nativeHeight: 25,
- _width: width ? width : 25,
- _height: 25,
- icon,
- toolTip,
- script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined,
- btnType,
- btnList: new List<string>(list),
- ignoreClick,
- _stayInCollection: true,
- _hideContextMenu: true,
- _lockedPosition: true,
- system: true,
- dontUndo: true,
- title,
- color: Colors.WHITE,
- // backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult, {}, {_readOnly_:true}) as any : "transparent",
- _dropAction: "alias",
- hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined,
- _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
- onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }, { _readOnly_: false }) : undefined
- }));
}
+ prev = params.title;
});
-
- doc.contextMenuBtns = CurrentUserUtils.linearButtonList({ title: "menu buttons", flexGap: 0, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, docList);
}
}
@@ -1162,7 +934,7 @@ export class CurrentUserUtils {
sharedDocs.childContextMenuIcons = new List<string>(["user-plus",]);
}
- doc.mySharedDocs = new PrefetchProxy(sharedDocs);
+ doc.mySharedDocs = new PrefetchProxy(sharedDocs as Doc);
}
}
@@ -1238,7 +1010,7 @@ export class CurrentUserUtils {
static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
- const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
+ await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]),
async () => {
const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
@@ -1250,7 +1022,7 @@ export class CurrentUserUtils {
doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
doc.title = Doc.CurrentUserEmail;
doc._raiseWhenDragged = true;
- doc._showLabel = false;
+ doc._showLabel = true;
doc._showMenuLabel = true;
doc.textAlign = StrCast(doc.textAlign, "left");
doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
@@ -1267,12 +1039,10 @@ export class CurrentUserUtils {
doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false);
doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null);
doc.noviceMode = BoolCast(doc.noviceMode, true);
- doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
- doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
- Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
doc.savedFilters = new List<Doc>();
doc.filterDocCount = 0;
doc.freezeChildren = "remove|add";
+ doc.myHeaderBarDoc = doc.myHeaderBarDoc ?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true });
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
this.setupDocTemplates(doc); // sets up the template menu of templates
this.setupImportSidebar(doc); // sets up the import sidebar
@@ -1291,15 +1061,6 @@ export class CurrentUserUtils {
doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
- // uncomment this to setup a default note style that uses the custom header layout
- // PromiseValue(doc.emptyHeader).then(factory => {
- // if (Cast(doc.defaultTextLayout, Doc, null)?.version !== headerViewVersion) {
- // const deleg = Doc.delegateDragFactory(factory as Doc);
- // deleg.title = "header";
- // doc.defaultTextLayout = new PrefetchProxy(deleg);
- // Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg);
- // }
- // });
setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500);
doc.fieldInfos = await Docs.setupFieldInfos();
if (doc.activeDashboard instanceof Doc) {
@@ -1449,22 +1210,8 @@ export class CurrentUserUtils {
freeformDoc.context = dashboardDoc;
// switching the tabs from the datadoc to the regular doc
- const dashboardTabs = dashboardDoc[DataSym].data;
- dashboardDoc[DataSym].data = new List<Doc>();
- dashboardDoc.data = dashboardTabs;
-
- // collating all docs on the dashboard to make a data-all field
- const allDocs = new List<Doc>();
- const allDocs2 = new List<Doc>(); // Array.from, spread, splice all cause so stack or acl issues for some reason
- DocListCast(dashboardTabs).forEach(doc => {
- const tabDocs = DocListCast(doc.data);
- allDocs.push(...tabDocs);
- allDocs2.push(...tabDocs);
- });
- dashboardDoc[DataSym]["data-all"] = allDocs;
- dashboardDoc["data-all"] = allDocs2;
- DocListCast(dashboardDoc.data).forEach(doc => doc.dashboard = dashboardDoc);
- DocListCast(dashboardDoc.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
+ const dashboardTabs = DocListCast(dashboardDoc[DataSym].data);
+ dashboardDoc.data = new List<Doc>(dashboardTabs);
userDoc.activePresentation = presentation;
@@ -1475,7 +1222,7 @@ export class CurrentUserUtils {
public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) {
const tbox = Docs.Create.TextDocument("", {
_xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor,
- _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, title
+ _width: width || 200, _height: 35, x: x, y: y, _fitWidth: true, _autoHeight: true, title
});
const template = Doc.UserDoc().defaultTextLayout;
if (template instanceof Doc) {
@@ -1489,6 +1236,7 @@ export class CurrentUserUtils {
public static get DockedBtns() { return Cast(Doc.UserDoc().dockedBtns, Doc, null); }
public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); }
public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); }
+ public static get MyHeaderBarDoc() { return Cast(Doc.UserDoc().myHeaderBarDoc, Doc, null); }
public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); }
public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); }
public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); }
@@ -1506,26 +1254,16 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) {
view && SelectionManager.SelectView(view, false);
}
});
-ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); },
- "document containing all shared Docs");
-ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; },
- "is Dash in novice mode");
-ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); },
- "creates a snapshot copy of a dashboard");
-ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); },
- "creates a new dashboard when called");
-ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); },
- "creates a new presentation when called");
-ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); },
- "creates a new folder in myFiles when called");
-ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
- "returns all the links to the document or its annotations", "(doc: any)");
-ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); },
- "imports files from device directly into the import sidebar");
-ScriptingGlobals.add(function shareDashboard(dashboard: Doc) {
- SharingManager.Instance.open(undefined, dashboard);
-},
- "opens sharing dialog for Dashboard");
+ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); }, "document containing all shared Docs");
+ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, "is Dash in novice mode");
+ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
+ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard");
+ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, "creates a new dashboard when called");
+ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called");
+ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called");
+ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)");
+ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
+ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard");
ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) {
const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data);
if (dashboards && dashboards.length > 1) {
@@ -1534,42 +1272,13 @@ ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) {
}
},
"Remove Dashboard from Dashboards");
-ScriptingGlobals.add(async function addToDashboards(dashboard: Doc) {
+ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {
const dashboardAlias = Doc.MakeAlias(dashboard);
-
- const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]);
-
- // moves the data-all field from the datadoc to the layoutdoc, necessary for off screen docs tab to function properly
- // dashboard["data-all"] = new List<Doc>(allDocs);
- // dashboardAlias["data-all"] = new List<Doc>((allDocs || []).map(doc => Doc.MakeAlias(doc)));
-
- // const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig));
- // dashboardAlias.dockingConfig = JSON.stringify(dockingConfig);
-
- dashboardAlias.data = new List<Doc>(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder)));
- DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias);
- //new List<Doc>();
- DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any;
Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias);
CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias);
},
"adds Dashboard to set of Dashboards");
-/**
- * Dynamically computes which docs should be rendered in the off-screen tabs tree of a dashboard.
- */
-ScriptingGlobals.add(function dynamicOffScreenDocs(dashboard: Doc) {
- if (dashboard[DataSym] instanceof Doc) {
- const allDocs = DocListCast(dashboard["data-all"]);
- const onScreenTab = DocListCast(dashboard.data)[0];
- const onScreenDocs = DocListCast(onScreenTab.data);
- return new List<Doc>(allDocs.reduce((result: Doc[], doc) => {
- !onScreenDocs.includes(doc) && !onScreenDocs.includes(doc.aliasOf as Doc) && (result.push(doc));
- return result;
- }, []));
- }
- return [];
-});
ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) {
let selected = SelectionManager.Docs().length ? SelectionManager.Docs()[0] : undefined;
if (selected && checkParent) {
@@ -1586,6 +1295,28 @@ ScriptingGlobals.add(function makeTopLevelFolder() {
TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined };
return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder);
});
-ScriptingGlobals.add(function toggleComicMode() {
- Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic";
+ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
+ if (readOnly) return;
+ const sel = SelectionManager.Views()[0];
+ const col = (sel.ComponentView as CollectionFreeFormView);
+ const currentFrame = Cast(sel.props.Document._currentFrame, "number", null);
+ if (currentFrame === undefined) {
+ sel.props.Document._currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.updateKeyframe(col.childDocs, currentFrame || 0);
+ sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ sel.rootDoc.lastFrame = Math.max(NumCast(sel.rootDoc._currentFrame), NumCast(sel.rootDoc.lastFrame));
+});
+ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
+ if (readOnly) return;
+ const sel = SelectionManager.Views()[0];
+ const col = (sel.ComponentView as CollectionFreeFormView);
+ const currentFrame = Cast(sel.props.Document._currentFrame, "number", null);
+ if (currentFrame === undefined) {
+ sel.props.Document._currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.gotoKeyframe(col.childDocs.slice());
+ sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1);
}); \ No newline at end of file
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index b6c28d2fe..a82f99d5a 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -16,7 +16,7 @@ import { SelectionManager } from './SelectionManager';
export class DocumentManager {
//global holds all of the nodes (regardless of which collection they're in)
- @observable public DocumentViews: DocumentView[] = [];
+ @observable public DocumentViews = new Set<DocumentView>();
@observable public LinkAnchorBoxViews: DocumentView[] = [];
@observable public RecordingEvent = 0;
@observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = [];
@@ -46,7 +46,7 @@ export class DocumentManager {
// this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
// view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString));
} else {
- this.DocumentViews.push(view);
+ this.DocumentViews.add(view);
}
}
public RemoveView = action((view: DocumentView) => {
@@ -61,8 +61,7 @@ export class DocumentManager {
const index = this.LinkAnchorBoxViews.indexOf(view);
this.LinkAnchorBoxViews.splice(index, 1);
} else {
- const index = this.DocumentViews.indexOf(view);
- index !== -1 && this.DocumentViews.splice(index, 1);
+ this.DocumentViews.delete(view);
}
SelectionManager.DeselectView(view);
});
@@ -70,13 +69,13 @@ export class DocumentManager {
//gets all views
public getDocumentViewsById(id: string) {
const toReturn: DocumentView[] = [];
- DocumentManager.Instance.DocumentViews.map(view => {
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => {
if (view.rootDoc[Id] === id) {
toReturn.push(view);
}
});
if (toReturn.length === 0) {
- DocumentManager.Instance.DocumentViews.map(view => {
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => {
const doc = view.rootDoc.proto;
if (doc && doc[Id] && doc[Id] === id) {
toReturn.push(view);
@@ -96,14 +95,14 @@ export class DocumentManager {
const passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
for (const pass of passes) {
- DocumentManager.Instance.DocumentViews.map(view => {
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => {
if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
toReturn = view;
return;
}
});
if (!toReturn) {
- DocumentManager.Instance.DocumentViews.map(view => {
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => {
const doc = view.rootDoc.proto;
if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
toReturn = view;
@@ -122,9 +121,8 @@ export class DocumentManager {
}
public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
- const docViews = DocumentManager.Instance.DocumentViews;
const views: DocumentView[] = [];
- docViews.map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
+ Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
}
public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
@@ -133,8 +131,8 @@ export class DocumentManager {
}
public getDocumentViews(toFind: Doc): DocumentView[] {
const toReturn: DocumentView[] = [];
- const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
- const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.IsLightboxDocView(view.docViewPath));
+ const docViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
+ const lightViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => LightboxView.IsLightboxDocView(view.docViewPath));
// heuristic to return the "best" documents first:
// choose a document in the lightbox first
@@ -156,7 +154,7 @@ export class DocumentManager {
targetDoc: Doc, // document to display
willZoom: boolean, // whether to zoom doc to take up most of screen
createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist
- docContext?: Doc, // context to load that should contain the target
+ docContext: Doc[], // context to load that should contain the target
linkDoc?: Doc, // link that's being followed
closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there
originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
@@ -190,31 +188,40 @@ export class DocumentManager {
finished?.();
};
const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && getFirstDocView(annotatedDoc);
- const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
- const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
+ const contextDocs = docContext.length ? await DocListCastAsync(docContext[0].data) : undefined;
+ const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext.lastElement() : undefined;
const targetDocContext = contextDoc || annotatedDoc;
const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) ||
(wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
- if ((!docView && targetDoc.type !== DocumentType.MARKER) && annoContainerView) {
+ if (annoContainerView) {
if (annoContainerView.props.Document.layoutKey === "layout_icon") {
- annoContainerView.iconify(() => this.jumpToDocument(
- targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
- finished, originalTarget, noSelect, presZoom));
+ annoContainerView.iconify(() => annoContainerView.focus(targetDoc, {
+ originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => {
+ focusAndFinish(true);
+ res(ViewAdjustment.doNothing);
+ })
+ }));
return;
- } else {
+ } else if (!docView && targetDoc.type !== DocumentType.MARKER) {
annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
}
}
if (focusView) {
!noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox
- focusView.focus(targetDoc, {
+ const doFocus = (forceDidFocus: boolean) => focusView.focus(originalTarget ?? targetDoc, {
originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) =>
new Promise<ViewAdjustment>(res => {
- focusAndFinish(didFocus);
+ focusAndFinish(forceDidFocus || didFocus);
res(ViewAdjustment.doNothing);
})
});
+ if (focusView.props.Document.layoutKey === "layout_icon") {
+ focusView.iconify(() => doFocus(true));
+ } else {
+ doFocus(false);
+ }
} else {
if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
@@ -228,7 +235,7 @@ export class DocumentManager {
targetDocContext._viewTransition = undefined;
if (targetDocContext.layoutKey === "layout_icon") {
targetDocContextView.iconify(() => this.jumpToDocument(
- targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
+ resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
finished, originalTarget, noSelect, presZoom));
}
return ViewAdjustment.doNothing;
@@ -263,7 +270,16 @@ export class DocumentManager {
};
setTimeout(() => findView(0), 0);
}
- } else { // there's no context view so we need to create one first and try again when that finishes
+ } else {
+ if (docContext.length && docContext[0]?.layoutKey === "layout_icon") {
+ const docContextView = this.getFirstDocumentView(docContext[0]);
+ if (docContextView) {
+ return docContextView.iconify(() => this.jumpToDocument(
+ targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc,
+ finished, originalTarget, noSelect, presZoom));
+ }
+ }
+ // there's no context view so we need to create one first and try again when that finishes
const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget);
createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
finishFunc);
@@ -272,7 +288,7 @@ export class DocumentManager {
}
}
}
-export function DocFocusOrOpen(doc: any, collectionDoc?: Doc) {
+export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) {
const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc);
const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView);
if (dv && Doc.AreProtosEqual(dv.props.Document, doc)) {
@@ -282,7 +298,9 @@ export function DocFocusOrOpen(doc: any, collectionDoc?: Doc) {
else {
const context = doc.context !== Doc.UserDoc().myFilesystem && Cast(doc.context, Doc, null);
const showDoc = context || doc;
- CollectionDockingView.AddSplit(showDoc === Doc.GetProto(showDoc) ? Doc.MakeAlias(showDoc) : showDoc, "right") && context &&
+ const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc;
+
+ CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), "right") && context &&
setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc));
}
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 13e5940d6..9f8c49081 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,4 +1,3 @@
-import { extend } from "lodash";
import { action } from "mobx";
import { DateField } from "../../fields/DateField";
import { Doc, Field, Opt } from "../../fields/Doc";
@@ -8,7 +7,7 @@ import { listSpec } from "../../fields/Schema";
import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
import { ScriptField } from "../../fields/ScriptField";
import { Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types";
-import { emptyFunction } from "../../Utils";
+import { emptyFunction, Utils } from "../../Utils";
import { Docs, DocUtils } from "../documents/Documents";
import * as globalCssVariables from "../views/global/globalCssVariables.scss";
import { DocumentView } from "../views/nodes/DocumentView";
@@ -278,7 +277,7 @@ export namespace DragManager {
SnappingManager.setSnapLines(horizLines, vertLines);
}
export function snapDragAspect(dragPt: number[], snapAspect: number) {
- let closest = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
+ let closest = Utils.SNAP_THRESHOLD;
let near = dragPt;
const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => {
if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0
@@ -313,7 +312,7 @@ export namespace DragManager {
}
// snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line
export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
- const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
+ const snapThreshold = Utils.SNAP_THRESHOLD;
const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
if (snapLines.length) {
const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt
@@ -450,20 +449,29 @@ export namespace DragManager {
SnappingManager.clearSnapLines();
batch.end();
});
- const moveHandler = async (e: PointerEvent) => {
+ var startWindowDragTimer: any;
+ 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" : dragData.defaultDropAction;
}
if (((e.target as any)?.className === "lm_tabs" || e?.shiftKey) && dragData.draggedDocuments.length === 1) {
- dragData.dropAction = dragData.userDropAction || "same";
- AbortDrag();
- await finishDrag?.(new DragCompleteEvent(true, dragData));
- DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => {
- if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) {
- dragData.removeDocument?.(dragData.draggedDocuments[0]);
- }
- });
+ if (!startWindowDragTimer) {
+ startWindowDragTimer = setTimeout(async () => {
+ startWindowDragTimer = undefined;
+ dragData.dropAction = dragData.userDropAction || "same";
+ AbortDrag();
+ await finishDrag?.(new DragCompleteEvent(true, dragData));
+ DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => {
+ if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) {
+ dragData.removeDocument?.(dragData.draggedDocuments[0]);
+ }
+ });
+ }, 500);
+ }
+ } else {
+ clearTimeout(startWindowDragTimer);
+ startWindowDragTimer = undefined;
}
const target = document.elementFromPoint(e.x, e.y);
@@ -529,6 +537,8 @@ export namespace DragManager {
);
};
const upHandler = (e: PointerEvent) => {
+ clearTimeout(startWindowDragTimer);
+ startWindowDragTimer = undefined;
dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, cleanupDrag);
};
document.addEventListener("pointermove", moveHandler, true);
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 39e9251a5..37571ae01 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -161,7 +161,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
!this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
- DocumentManager.Instance.jumpToDocument(importContainer, true);
+ DocumentManager.Instance.jumpToDocument(importContainer, true, undefined, []);
}
runInAction(() => {
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 3e051dec8..289c5bc51 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -286,6 +286,8 @@ export namespace InteractionUtils {
// pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
+ case TOUCHTYPE:
+ return e.pointerType === TOUCHTYPE;
default: return e.pointerType === type;
}
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 9445533dc..2100b1195 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,3 +1,4 @@
+import { validationResult } from "express-validator/check";
import { action, observable, observe } from "mobx";
import { computedFn } from "mobx-utils";
import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc";
@@ -241,10 +242,12 @@ export class LinkManager {
} else {
const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
const containerDoc = containerAnnoDoc || target;
- const containerDocContext = Cast(containerDoc?.context, Doc, null);
- const targetContext = LightboxView.LightboxDoc ? containerAnnoDoc || containerDocContext : containerDocContext;
- const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
- DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, allFinished);
+ var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : [] as Doc[];
+ while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) {
+ containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
+ }
+ const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext;
+ DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished);
}
} else {
allFinished();
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 6d7f7e8df..f439d4488 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -370,11 +370,11 @@ export class SharingManager extends React.Component<{}> {
if (!uniform) dropdownValues.unshift("-multiple-");
if (override) dropdownValues.unshift("None");
return dropdownValues.filter(permission => !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
- (
- <option key={permission} value={permission}>
- {permission}
- </option>
- )
+ (
+ <option key={permission} value={permission}>
+ {permission}
+ </option>
+ )
);
}
@@ -388,7 +388,7 @@ export class SharingManager extends React.Component<{}> {
onClick={() => {
let context: Opt<CollectionView>;
if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) {
- DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document);
+ DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, [context.props.Document]);
}
}}
onPointerEnter={action(() => {
@@ -548,10 +548,10 @@ export class SharingManager extends React.Component<{}> {
{this.sharingOptions(uniform)}
</select>
) : (
- <div className={"permissions-dropdown"}>
- {permissions}
- </div>
- )}
+ <div className={"permissions-dropdown"}>
+ {permissions}
+ </div>
+ )}
</div>
</div>
);
@@ -572,7 +572,7 @@ export class SharingManager extends React.Component<{}> {
<div className="edit-actions">
<div className={"permissions-dropdown"}>
Owner
- </div>
+ </div>
</div>
</div>
) : null,
@@ -622,10 +622,10 @@ export class SharingManager extends React.Component<{}> {
{this.sharingOptions(uniform, group.title === "Override")}
</select>
) : (
- <div className={"permissions-dropdown"}>
- {permissions}
- </div>
- )}
+ <div className={"permissions-dropdown"}>
+ {permissions}
+ </div>
+ )}
</div>
</div>
);
@@ -658,6 +658,7 @@ export class SharingManager extends React.Component<{}> {
isSearchable
closeMenuOnSelect={false}
options={options}
+ onKeyDown={e => e.stopPropagation()}
onChange={this.handleUsersChange}
value={this.selectedUsers}
styles={{
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 79aaf2158..9c176a4fd 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -44,7 +44,6 @@ interface ViewBoxBaseProps {
ContainingCollectionDoc: Opt<Doc>;
DocumentView?: () => DocumentView;
fieldKey: string;
- layerProvider?: (doc: Doc, assign?: boolean) => boolean;
isSelected: (outsideReaction?: boolean) => boolean;
isContentActive: () => boolean | undefined;
renderDepth: number;
@@ -85,7 +84,6 @@ export interface ViewBoxAnnotatableProps {
DataDoc?: Doc;
fieldKey: string;
filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
- layerProvider?: (doc: Doc, assign?: boolean) => boolean;
isContentActive: () => boolean | undefined;
select: (isCtrlPressed: boolean) => void;
whenChildContentsActiveChanged: (isActive: boolean) => void;
@@ -209,13 +207,11 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
doc.context = this.props.Document;
if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
- this.props.layerProvider?.(doc, true);
Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
});
}
else {
added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document
- this.props.layerProvider?.(doc, true);
//DocUtils.LeavePushpin(doc);
doc._stayInCollection = undefined;
doc.context = this.props.Document;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 9b95f1525..25297e167 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -192,7 +192,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
<div className="dash-tooltip">{"Set onClick to follow primary link"}</div>}>
<div className="documentButtonBar-icon"
style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
- onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false, false)))}>
+ onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
</div>
</Tooltip>;
@@ -224,6 +224,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
activeDoc.presPinView = true;
activeDoc.presPinViewScroll = scroll;
} else if (targetDoc.type === DocumentType.VID) {
+ activeDoc.presPinView = true;
+ activeDoc.presStartTime = targetDoc._currentTimecode;
+ activeDoc.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1;
activeDoc.presPinTimecode = targetDoc._currentTimecode;
} else if (pannable) {
const x = targetDoc._panX;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index fb8cfbc15..29e088143 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -19,6 +19,7 @@ import { SelectionManager } from "../util/SelectionManager";
import { SnappingManager } from '../util/SnappingManager';
import { undoBatch, UndoManager } from "../util/UndoManager";
import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
import { DocumentButtonBar } from './DocumentButtonBar';
import './DocumentDecorations.scss';
import { KeyManager } from './GlobalKeyHandler';
@@ -27,8 +28,8 @@ import { InkStrokeProperties } from './InkStrokeProperties';
import { LightboxView } from './LightboxView';
import { DocumentView } from "./nodes/DocumentView";
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { ImageBox } from './nodes/ImageBox';
import React = require("react");
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
@observer
export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> {
@@ -48,7 +49,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
@observable private _titleControlString: string = "#title";
@observable private _edtingTitle = false;
@observable private _hidden = false;
-
+ @observable public AddToSelection = false; // if Shift is pressed, then this should be set so that clicking on the selection background is ignored so overlapped documents can be added to the selection set.
@observable public Interacting = false;
@observable public pushIcon: IconProp = "arrow-alt-circle-up";
@observable public pullIcon: IconProp = "arrow-alt-circle-down";
@@ -63,7 +64,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
@computed
get Bounds() {
const views = SelectionManager.Views();
- return views.map(dv => dv.getBounds()).reduce((bounds, rect) =>
+ return views.filter(dv => dv.props.renderDepth > 0).map(dv => dv.getBounds()).reduce((bounds, rect) =>
!rect ? bounds :
{
x: Math.min(rect.left, bounds.x),
@@ -112,7 +113,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
}
}
- titleEntered = (e: React.KeyboardEvent) => e.key === "Enter" && (e.target as any).blur();
+ titleEntered = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ e.stopPropagation();
+ (e.target as any).blur();
+ }
+ }
@action onTitleDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => {
@@ -224,14 +230,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
@action
onRotateDown = (e: React.PointerEvent): void => {
const rotateUndo = UndoManager.StartBatch("rotatedown");
- const centerPoint = { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 };
+ const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
+ const centerPoint = !selectedInk.length ? { X: this.Bounds.x, Y: this.Bounds.y } : { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 };
setupMoveUpEvents(this, e,
(e: PointerEvent, down: number[], delta: number[]) => {
const previousPoint = { X: e.clientX, Y: e.clientY };
const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
const angle = InkStrokeProperties.angleChange(previousPoint, movedPoint, centerPoint);
- const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
- angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint);
+ if (selectedInk.length) {
+ angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint);
+ } else {
+ SelectionManager.Views().forEach(dv => dv.rootDoc._jitterRotation = NumCast(dv.rootDoc._jitterRotation) - angle * 180 / Math.PI);
+ }
return false;
},
() => {
@@ -496,16 +506,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
value={this._accumulatedTitle}
onBlur={e => this.titleBlur()}
onChange={action(e => this._accumulatedTitle = e.target.value)}
- onKeyPress={this.titleEntered} /> :
+ onKeyDown={this.titleEntered} /> :
<div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
<span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
</div>;
- let inMainMenuPanel = false;
- for (let node = seldoc.ContentDiv; node && !inMainMenuPanel; node = node?.parentNode as any) {
- if (node.className === "mainView-mainContent") inMainMenuPanel = true;
- }
- const leftBounds = inMainMenuPanel ? 0 : this.props.boundsLeft;
+
+ const leftBounds = this.props.boundsLeft;
const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
@@ -513,24 +520,28 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
- const useRotation = seldoc.ComponentView instanceof InkingStroke;
+ const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox;
const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : "";
+ const rotation = NumCast(seldoc.rootDoc._jitterRotation);
+
return (<div className={`documentDecorations${colorScheme}`}>
<div className="documentDecorations-background" style={{
+ transform: `rotate(${rotation}deg)`,
+ transformOrigin: "top left",
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
left: bounds.x - this._resizeBorderWidth / 2,
top: bounds.y - this._resizeBorderWidth / 2,
- pointerEvents: KeyManager.Instance.ShiftPressed || this.Interacting ? "none" : "all",
+ pointerEvents: DocumentDecorations.Instance.AddToSelection || this.Interacting ? "none" : "all",
display: SelectionManager.Views().length <= 1 ? "none" : undefined
}} onPointerDown={this.onBackgroundDown} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} />
{bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
<div className="documentDecorations-container" key="container" style={{
+ transform: ` translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
+ transformOrigin: `8px 26px`,
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
- left: bounds.x - this._resizeBorderWidth / 2,
- top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
}}>
{!canDelete ? <div /> : topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), "Close")}
{titleArea}
@@ -554,10 +565,14 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
onContextMenu={e => e.preventDefault()}>{useRotation && "⟲"}</div>
</>
}
+ {seldoc?.Document.type === DocumentType.FONTICON ? (null) :
+ <div className="link-button-container" key="links"
+ style={{
+ transform: ` translate(${- this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+ }}>
+ <DocumentButtonBar views={SelectionManager.Views} />
+ </div>}
</div >
- {seldoc?.Document.type === DocumentType.FONTICON ? (null) : <div className="link-button-container" key="links" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
- <DocumentButtonBar views={SelectionManager.Views} />
- </div>}
</>}
</div >
);
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 50dca0a99..a118cc144 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -18,7 +18,8 @@ import { SelectionManager } from "../util/SelectionManager";
import { Transform } from "../util/Transform";
import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
import "./GestureOverlay.scss";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveArrowScale, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke";
+import { ActiveArrowEnd, ActiveArrowScale, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke";
+import { checkInksToGroup, createInkGroup } from "./nodes/button/FontIconBox";
import { DocumentView } from "./nodes/DocumentView";
import { RadialMenu } from "./nodes/RadialMenu";
import HorizontalPalette from "./Palette";
@@ -73,6 +74,8 @@ export class GestureOverlay extends Touchable {
this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc));
}
+ // TODO: nda - add dragging groups with one finger drag and have to click into group to scroll within the group
+
/**
* Ignores all touch events that belong to a hand being held down.
*/
@@ -124,6 +127,8 @@ export class GestureOverlay extends Touchable {
// pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
// and this seems to be the only way of differentiating pen and touch on touch events
if (pt.radiusX > 1 && pt.radiusY > 1) {
+ createInkGroup();
+ Doc.UserDoc().activeInkTool = InkTool.None;
this.prevPoints.set(pt.identifier, pt);
}
}
@@ -291,9 +296,6 @@ export class GestureOverlay extends Touchable {
else if (thumb.clientX === leftMost) {
pointer = fingers.reduce((a, v) => a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v);
}
- else {
- console.log("not hand");
- }
this.pointerIdentifier = pointer?.identifier;
runInAction(() => {
@@ -491,7 +493,19 @@ export class GestureOverlay extends Touchable {
@action
onPointerDown = (e: React.PointerEvent) => {
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, action((e: PointerEvent, doubleTap?: boolean) => {
+ if (doubleTap) {
+ createInkGroup();
+ CurrentUserUtils.SelectedTool = InkTool.None;
+ return;
+ }
+ }));
+ }
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ CurrentUserUtils.SelectedTool = InkTool.Write;
+ }
this._points.push({ X: e.clientX, Y: e.clientY });
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
// if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
@@ -625,14 +639,14 @@ export class GestureOverlay extends Touchable {
controlPoints.push({ X: curve[2][0], Y: curve[2][1] });
controlPoints.push({ X: curve[3][0], Y: curve[3][1] });
-
}
const dist = Math.sqrt((controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) +
(controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y));
if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0];
this._points = controlPoints;
-
this.dispatchGesture(GestureUtils.Gestures.Stroke);
+ // TODO: nda - check inks to group here
+ checkInksToGroup();
}
this._points = [];
}
@@ -808,7 +822,7 @@ export class GestureOverlay extends Touchable {
points: stroke ?? this._points,
gesture: gesture as any,
bounds: this.getBounds(stroke ?? this._points),
- text: data
+ text: data,
}
}
)
@@ -881,7 +895,6 @@ export class GestureOverlay extends Touchable {
isContentActive={returnFalse}
renderDepth={0}
styleProvider={returnEmptyString}
- layerProvider={undefined}
docViewPath={returnEmptyDoclist}
focus={DocUtils.DefaultFocus}
whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 914802041..767efdbc2 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,5 +1,5 @@
import { random } from "lodash";
-import { action, observable } from "mobx";
+import { action, observable, runInAction } from "mobx";
import { DateField } from "../../fields/DateField";
import { Doc, DocListCast } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
@@ -30,7 +30,7 @@ import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
import { AnchorMenu } from "./pdf/AnchorMenu";
const modifiers = ["control", "meta", "shift", "alt"];
-type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
+type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
type KeyControlInfo = {
preventDefault: boolean,
stopPropagation: boolean
@@ -39,7 +39,6 @@ type KeyControlInfo = {
export class KeyManager {
public static Instance: KeyManager = new KeyManager();
private router = new Map<string, KeyHandler>();
- @observable ShiftPressed = false;
constructor() {
const isMac = navigator.platform.toLowerCase().indexOf("mac") >= 0;
@@ -53,11 +52,11 @@ export class KeyManager {
}
public unhandle = action((e: KeyboardEvent) => {
- if (e.key?.toLowerCase() === "shift") KeyManager.Instance.ShiftPressed = false;
+ if (e.key?.toLowerCase() === "shift") runInAction(() => DocumentDecorations.Instance.AddToSelection = false);
});
- public handle = action(async (e: KeyboardEvent) => {
- if (e.key?.toLowerCase() === "shift" && e.ctrlKey && e.altKey) KeyManager.Instance.ShiftPressed = true;
+ public handle = action((e: KeyboardEvent) => {
+ if (e.key?.toLowerCase() === "shift") DocumentDecorations.Instance.AddToSelection = true;
//if (!Doc.UserDoc().noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true);
const keyname = e.key && e.key.toLowerCase();
this.handleGreedy(keyname);
@@ -74,7 +73,7 @@ export class KeyManager {
return;
}
- const control = await handleConstrained(keyname, e);
+ const control = handleConstrained(keyname, e);
control.stopPropagation && e.stopPropagation();
control.preventDefault && e.preventDefault();
@@ -173,7 +172,7 @@ export class KeyManager {
};
});
- private shift = action(async (keyname: string) => {
+ private shift = action((keyname: string) => {
const stopPropagation = false;
const preventDefault = false;
@@ -334,9 +333,7 @@ export class KeyManager {
}
}
- async printClipboard() {
- const text: string = await navigator.clipboard.readText();
- }
+ getClipboard() { return navigator.clipboard.readText(); }
private ctrl_shift = action((keyname: string) => {
const stopPropagation = true;
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index e4486fc78..0449da483 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -230,42 +230,42 @@ export class InkStrokeProperties {
this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
const order = controlIndex % 4;
const closed = InkingStroke.IsClosed(ink);
- if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1) {
+ const brokenIndices = Cast(inkView.props.Document.brokenInkIndices, listSpec("number"), []);
+ if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
const cpt_before = ink[controlIndex];
const cpt = { X: cpt_before.X + deltaX, Y: cpt_before.Y + deltaY };
- if (true) {
- const newink = origInk.slice();
- const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
- const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
- const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt);
- const samplesLeft: Point[] = [];
- const samplesRight: Point[] = [];
- var startDir = { x: 0, y: 0 };
- var endDir = { x: 0, y: 0 };
- for (var i = 0; i < nearestSeg / 4 + 1; i++) {
- const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
- if (i === 0) startDir = bez.derivative(0);
- if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
- for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + .05 : 1); t += 0.05) {
- const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
- samplesLeft.push(new Point(pt.x, pt.y));
- }
+ const newink = origInk.slice();
+ const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
+ const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
+ const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt);
+ if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && (1 - nearestT) < 1e-1)) return ink.slice();
+ const samplesLeft: Point[] = [];
+ const samplesRight: Point[] = [];
+ var startDir = { x: 0, y: 0 };
+ var endDir = { x: 0, y: 0 };
+ for (var i = 0; i < nearestSeg / 4 + 1; i++) {
+ const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+ if (i === 0) startDir = bez.derivative(0);
+ if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
+ for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + .05 : 1); t += 0.05) {
+ const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
+ samplesLeft.push(new Point(pt.x, pt.y));
}
- var { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- for (var i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
- const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
- if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
- if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
- for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + .05 + 1e-7 : 1 + 1e-7); t += 0.05) {
- const pt = bez.compute(Math.min(1, t));
- samplesRight.push(new Point(pt.x, pt.y));
- }
+ }
+ var { finalCtrls } = FitOneCurve(samplesLeft, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ for (var i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
+ const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+ if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
+ if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
+ for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + .05 + 1e-7 : 1 + 1e-7); t += 0.05) {
+ const pt = bez.compute(Math.min(1, t));
+ samplesRight.push(new Point(pt.x, pt.y));
}
- const { finalCtrls: rightCtrls, error: errorRight } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
- finalCtrls = finalCtrls.concat(rightCtrls);
- newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
- return newink;
}
+ const { finalCtrls: rightCtrls, error: errorRight } = FitOneCurve(samplesRight, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y });
+ finalCtrls = finalCtrls.concat(rightCtrls);
+ newink.splice(this._currentPoint - 4, 8, ...finalCtrls);
+ return newink;
}
return ink.map((pt, i) => {
diff --git a/src/client/views/InkTranscription.scss b/src/client/views/InkTranscription.scss
new file mode 100644
index 000000000..bbb0a1afa
--- /dev/null
+++ b/src/client/views/InkTranscription.scss
@@ -0,0 +1,5 @@
+.ink-transcription {
+ .error-msg {
+ display: none !important;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
new file mode 100644
index 000000000..0ac1770cc
--- /dev/null
+++ b/src/client/views/InkTranscription.tsx
@@ -0,0 +1,298 @@
+import * as iink from 'iink-js';
+import { action, observable } from 'mobx';
+import * as React from 'react';
+import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc';
+import { InkData, InkField } from "../../fields/InkField";
+import { Cast, DateCast, NumCast } from '../../fields/Types';
+import { aggregateBounds } from '../../Utils';
+import { DocumentType } from "../documents/DocumentTypes";
+import { DocumentManager } from "../util/DocumentManager";
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
+import { InkingStroke } from './InkingStroke';
+import './InkTranscription.scss';
+
+
+export class InkTranscription extends React.Component {
+ static Instance: InkTranscription;
+
+ @observable _mathRegister: any;
+ @observable _mathRef: any;
+ @observable _textRegister: any;
+ @observable _textRef: any;
+ private lastJiix: any;
+ private currGroup?: Doc;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ InkTranscription.Instance = this;
+ }
+
+ componentWillUnmount() {
+ this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
+ this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
+ }
+
+ @action
+ setMathRef = (r: any) => {
+ if (!this._mathRegister) {
+ this._mathRegister = r ? iink.register(r, {
+ recognitionParams: {
+ type: 'MATH',
+ protocol: 'WEBSOCKET',
+ server: {
+ host: 'cloud.myscript.com',
+ applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f',
+ hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1',
+ websocket: {
+ pingEnabled: false,
+ autoReconnect: true
+ }
+ },
+ iink: {
+ math: {
+ mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix']
+ },
+ export: {
+ jiix: {
+ strokes: true
+ }
+ }
+ }
+ }
+ }) : null;
+ }
+
+ r.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
+
+ return this._mathRef = r;
+ }
+
+ @action
+ setTextRef = (r: any) => {
+ if (!this._textRegister) {
+ this._textRegister = r ? iink.register(r, {
+ recognitionParams: {
+ type: 'TEXT',
+ protocol: 'WEBSOCKET',
+ server: {
+ host: 'cloud.myscript.com',
+ applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f',
+ hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1',
+ websocket: {
+ pingEnabled: false,
+ autoReconnect: true
+ }
+ },
+ iink: {
+ text: {
+ mimeTypes: ['text/plain']
+ },
+ export: {
+ jiix: {
+ strokes: true
+ }
+ }
+ }
+ }
+ }) : null;
+ }
+
+ r.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
+
+ return this._textRef = r;
+ }
+
+ transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean, ffView?: CollectionFreeFormView) => {
+ if (!groupDoc) return;
+ const validInks = inkDocs.filter(s => s.type === DocumentType.INK);
+
+ const strokes: InkData[] = [];
+ const times: number[] = [];
+ validInks.filter(i => Cast(i.data, InkField)).forEach(i => {
+ const d = Cast(i.data, InkField, null);
+ const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke;
+ strokes.push(d.inkData.map(pd => (inkStroke.ptToScreen({ X: pd.X, Y: pd.Y }))));
+ times.push(DateCast(i.creationDate).getDate().getTime());
+ });
+
+ this.currGroup = groupDoc;
+
+ const pointerData = { "events": strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) };
+ const processGestures = false;
+
+ if (math) {
+ this._mathRef.editor.pointerEvents(pointerData, processGestures);
+ }
+ else {
+ this._textRef.editor.pointerEvents(pointerData, processGestures);
+ }
+ }
+
+ inkJSON = (stroke: InkData, time: number) => {
+ return {
+ "pointerType": "PEN",
+ "pointerId": 1,
+ "x": stroke.map(point => point.X),
+ "y": stroke.map(point => point.Y),
+ "t": new Array(stroke.length).fill(time),
+ "p": new Array(stroke.length).fill(1.0)
+ };
+ }
+
+ mmToPixel = (mm: number) => {
+ return ((96 * mm) / 25.4);
+ }
+
+ calcBounds = (coords: any) => {
+ // find max and min x values and subtract
+ const max = Math.max(...coords);
+ const min = Math.min(...coords);
+ return max - min;
+ }
+
+ subgroupsTranscriptions = (wordInkDocMap: Map<string, Doc[]>) => {
+ // loop through the words in wordInkDocMap
+ // for each word, get the inkDocs
+
+ // iterate through the keys of wordInkDocMap
+ wordInkDocMap.forEach(async (inkDocs: Doc[], word: string) => {
+ const selected = inkDocs.slice();
+ if (!selected) {
+ return;
+ }
+ // TODO: nda - probably have to cast this to an actual Doc
+ const ctx = await Cast(selected[0].context, Doc);
+ if (!ctx) {
+ return;
+ }
+ const docView: CollectionFreeFormView = DocumentManager.Instance.getDocumentView(ctx)?.ComponentView as CollectionFreeFormView;
+
+ if (!docView) return;
+ const marqViewRef = docView._marqueeViewRef.current;
+ if (!marqViewRef) return;
+ // loop through selected an get the bound
+ const bounds: { x: number, y: number, width?: number, height?: number }[] = []
+
+ selected.map(action(d => {
+ const x = NumCast(d.x);
+ const y = NumCast(d.y);
+ const width = d[WidthSym]();
+ const height = d[HeightSym]();
+ bounds.push({ x, y, width, height });
+ }))
+
+ const aggregBounds = aggregateBounds(bounds, 0, 0);
+
+ if (marqViewRef) {
+ marqViewRef._downX = aggregBounds.x;
+ marqViewRef._downY = aggregBounds.y;
+ marqViewRef._lastX = aggregBounds.r;
+ marqViewRef._lastY = aggregBounds.b;
+ }
+
+ // set the vals for bounds in marqueeView
+
+ selected.map(action(d => {
+ const dx = NumCast(d.x);
+ const dy = NumCast(d.y);
+ delete d.x;
+ delete d.y;
+ delete d.activeFrame;
+ delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ // calculate pos based on bounds
+ if (marqViewRef?.Bounds) {
+ d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+ d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+ }
+ return d;
+ }));
+
+ docView.props.removeDocument?.(selected);
+ const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+ if (newCollection) {
+ newCollection.height = newCollection[HeightSym]();
+ newCollection.width = newCollection[WidthSym]();
+ newCollection.title = word;
+ }
+ // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+ newCollection && docView.props.addDocument?.(newCollection);
+ });
+ }
+
+ exportInk = (e: any, ref: any) => {
+ const exports = e.detail.exports;
+ if (exports) {
+ if (exports['application/x-latex']) {
+ const latex = exports['application/x-latex'];
+ if (this.currGroup) {
+ this.currGroup.text = latex;
+ this.currGroup.title = latex;
+ }
+
+ ref.editor.clear();
+ }
+ else if (exports['text/plain']) {
+ if (exports['application/vnd.myscript.jiix']) {
+ this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']);
+ // map timestamp to strokes
+ const timestampWord = new Map<number, string>();
+ this.lastJiix.words.map((word: any) => {
+ if (word.items) {
+ word.items.forEach((i: { id: string, timestamp: string, X: Array<number>, Y: Array<number>, F: Array<number> }) => {
+ const ms = Date.parse(i.timestamp);
+ timestampWord.set(ms, word.label);
+ })
+ }
+ })
+
+ const wordInkDocMap = new Map<string, Doc[]>();
+ if (this.currGroup) {
+ const docList = DocListCast(this.currGroup.data)
+ docList.forEach((inkDoc: Doc) => {
+ // just having the times match up and be a unique value (actual timestamp doesn't matter)
+ const ms = DateCast(inkDoc.creationDate).getDate().getTime() + 14400000;
+ const word = timestampWord.get(ms);
+ if (!word) {
+ return;
+ }
+ const entry = wordInkDocMap.get(word);
+ if (entry) {
+ entry.push(inkDoc);
+ wordInkDocMap.set(word, entry);
+ } else {
+ const newEntry = [inkDoc];
+ wordInkDocMap.set(word, newEntry);
+ }
+ });
+ if (this.lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap);
+ }
+ }
+ const text = exports['text/plain'];
+
+ if (this.currGroup) {
+ this.currGroup.transcription = text;
+ this.currGroup.title = text.split("\n")[0];
+ }
+
+ ref.editor.clear();
+ }
+ }
+ }
+
+ render() {
+ return (
+ <div className="ink-transcription">
+ <div className='math-editor'
+ ref={this.setMathRef}
+ touch-action="none">
+ </div>
+ <div className='text-editor'
+ ref={this.setTextRef}
+ touch-action="none">
+ </div>
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 06671961d..aa5a815ac 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -323,17 +323,33 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
markerScale, StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false);
- const highlightIndex = BoolCast(this.props.Document.isLinkButton) && Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
+ const highlightIndex = /*BoolCast(this.props.Document.isLinkButton) && */ Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
const highlightColor = !highlightIndex ?
StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent") :
["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
// Invisible polygonal line that enables the ink to be selected by the user.
- const clickableLine = (downHdlr?: (e: React.PointerEvent) => void) => InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
- inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && fillColor && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
+ const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) => InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor,
+ inkStrokeWidth, fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? closed ? 0 : (highlightIndex + 2) : 0),
StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
- StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
- markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0,
+ StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" || suppressFill ? "none" : fillColor, startMarker, endMarker,
+ markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? "none" : "visiblepainted"), 0.0,
false, downHdlr);
+ const fsize = +(StrCast(this.props.Document.fontSize, "12px").replace("px", ""));
+ // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size.
+ // this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429
+ // see: https://bibwild.wordpress.com/2019/06/10/bootstrap-3-to-4-changes-in-how-font-size-line-height-and-spacing-is-done-or-what-happened-to-line-height-computed/
+ const lineHeightGuess = (+getComputedStyle(document.body).lineHeight.replace("px", "")) / (+getComputedStyle(document.body).fontSize.replace("px", ""));
+ const interactions = {
+ onPointerLeave: action(() => this._nearestScrPt = undefined),
+ onPointerMove: this.props.isSelected() ? this.onPointerMove : undefined,
+ onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
+ onContextMenu: () => {
+ const cm = ContextMenu.Instance;
+ !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
+ cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
+ cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
+ }
+ };
return <div className="inkStroke-wrapper">
<svg className="inkStroke"
style={{
@@ -341,24 +357,19 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
cursor: this.props.isSelected() ? "default" : undefined
}}
- onPointerLeave={action(e => this._nearestScrPt = undefined)}
- onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined}
- onClick={e => this._handledClick && e.stopPropagation()}
- onContextMenu={() => {
- const cm = ContextMenu.Instance;
- !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
- cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
- cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
- }}
+ {...(!closed ? interactions : {})}
>
- {clickableLine(this.onPointerDown)}
- {inkLine}
+ {closed ? inkLine : clickableLine(this.onPointerDown)}
+ {closed ? clickableLine(this.onPointerDown) : inkLine}
</svg>
{!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? (null) :
<div className="inkStroke-text" style={{
color: StrCast(this.layoutDoc.textColor, "black"),
pointerEvents: this.props.isDocumentActive?.() ? "all" : undefined,
width: this.layoutDoc[WidthSym](),
+ transform: `scale(${this.props.scaling?.() || 1})`,
+ transformOrigin: "top left",
+ top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.scaling?.() || 1)) / 2
}}>
<FormattedTextBox
{...OmitKeys(this.props, ['children']).omit}
@@ -366,7 +377,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
yPadding={10}
xPadding={10}
fieldKey={"text"}
- fontSize={12}
+ fontSize={fsize}
dontRegisterView={true}
noSidebar={true}
dontScale={true}
@@ -374,6 +385,16 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
/>
</div>
}
+ {!closed ? null : <svg className="inkStroke"
+ style={{
+ transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
+ cursor: this.props.isSelected() ? "default" : undefined, position: "absolute"
+ }}
+ {...interactions}
+ >
+ {clickableLine(this.onPointerDown, true)}
+ </svg>}
</div>;
}
}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 59ed0dc92..f67f37bfb 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -16,7 +16,6 @@ import { TabDocView } from './collections/TabDocView';
import "./LightboxView.scss";
import { DocumentView } from './nodes/DocumentView';
import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
-import { CollectionMenu } from './collections/CollectionMenu';
interface LightboxViewProps {
PanelWidth: number;
@@ -38,6 +37,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
private static _history: Opt<{ doc: Doc, target?: Doc }[]> = [];
@observable private static _future: Opt<Doc[]> = [];
private static _docView: Opt<DocumentView>;
+ private static openInTabFunc: any;
static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, scrollTop: Opt<number> }> }[] = [];
@action public static SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc) {
if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) {
@@ -51,6 +51,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
this._docFilters && (this._docFilters.length = 0);
this._future = this._history = [];
} else {
+ if (doc) {
+ const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+ l && (Cast(l.anchor2, Doc, null).backgroundColor = "lightgreen");
+ }
//TabDocView.PinDoc(doc, { hidePresBox: true });
this._history ? this._history.push({ doc, target }) : this._history = [{ doc, target }];
if (doc !== LightboxView.LightboxDoc) {
@@ -106,7 +110,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
this._docFilters = (f => this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f])(`cookies:${cookie}:provide`);
}
}
- public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc) => {
+ public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc, openInTabFunc?: any) => {
+ LightboxView.openInTabFunc = openInTabFunc;
SelectionManager.DeselectAll();
return LightboxView.SetLightboxDoc(doc, undefined,
[...DocListCast(doc[Doc.LayoutFieldKey(doc)]),
@@ -246,7 +251,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
docFilters={this.docFilters}
removeDocument={undefined}
styleProvider={DefaultStyleProvider}
- layerProvider={returnTrue}
ScreenToLocalTransform={this.lightboxScreenToLocal}
PanelWidth={this.lightboxWidth}
PanelHeight={this.lightboxHeight}
@@ -274,7 +278,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
<div className="lightboxView-tabBtn" title={"open in tab"}
onClick={e => {
e.stopPropagation();
- CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, "onRight");
+ CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, "");
+ //LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace");
SelectionManager.DeselectAll();
LightboxView.SetLightboxDoc(undefined);
}}>
@@ -282,7 +287,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
</div>
<div className="lightboxView-navBtn" title={"toggle fit width"}
onClick={e => { e.stopPropagation(); LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; }}>
- <FontAwesomeIcon icon={"arrows-alt-h"} size="2x" />
+ <FontAwesomeIcon icon={LightboxView.LightboxDoc?._fitWidth ? "arrows-alt-h" : "arrows-alt-v"} size="2x" />
</div>
</div>;
}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 15ccd7b31..f0011d65f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -8,11 +8,9 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
import { ScriptField } from '../../fields/ScriptField';
import { PromiseValue, StrCast } from '../../fields/Types';
-import { TraceMobx } from '../../fields/util';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
@@ -34,7 +32,8 @@ import { CollectionDockingView } from './collections/CollectionDockingView';
import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
import { CollectionLinearView } from './collections/collectionLinear';
import { CollectionMenu } from './collections/CollectionMenu';
-import { CollectionViewType } from './collections/CollectionView';
+import { TreeViewType } from './collections/CollectionTreeView';
+import { CollectionView, CollectionViewType } from './collections/CollectionView';
import "./collections/TreeView.scss";
import { ComponentDecorations } from './ComponentDecorations';
import { ContextMenu } from './ContextMenu';
@@ -44,6 +43,7 @@ import { GestureOverlay } from './GestureOverlay';
import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss';
import { Colors } from './global/globalEnums';
import { KeyManager } from './GlobalKeyHandler';
+import { InkTranscription } from './InkTranscription';
import { LightboxView } from './LightboxView';
import { LinkMenu } from './linking/LinkMenu';
import "./MainView.scss";
@@ -51,6 +51,7 @@ import { AudioBox } from './nodes/AudioBox';
import { ButtonType } from './nodes/button/FontIconBox';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView } from './nodes/DocumentView';
+import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
@@ -82,14 +83,19 @@ export class MainView extends React.Component {
@computed private get dashboardTabHeight() { return 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js
@computed private get topOfDashUI() { return Number(DASHBOARD_SELECTOR_HEIGHT.replace("px", "")); }
- @computed private get topOfMainDoc() { return this.topOfDashUI + this.topMenuHeight(); }
+ @computed private get topOfHeaderBarDoc() { return this.topOfDashUI + this.topMenuHeight(); }
+ @computed private get topOfSidebarDoc() { return this.topOfDashUI + this.topMenuHeight(); }
+ @computed private get topOfMainDoc() { return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight(); }
@computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; }
@computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; }
@computed private get userDoc() { return Doc.UserDoc(); }
@computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); }
@computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; }
+ @computed private get headerBarDoc() { return this.userDoc ? CurrentUserUtils.MyHeaderBarDoc : CurrentUserUtils.MyHeaderBarDoc; }
@computed public get mainFreeform(): Opt<Doc> { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
+ headerBarDocWidth = () => this.mainDocViewWidth();
+ headerBarDocHeight = () => CurrentUserUtils.headerBarHeight ?? 0;
topMenuHeight = () => 35;
topMenuWidth = returnZero; // value is ignored ...
leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace("px", ""));
@@ -98,8 +104,8 @@ export class MainView extends React.Component {
leftMenuFlyoutHeight = () => this._dashUIHeight;
propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, CurrentUserUtils.propertiesWidth || 0));
propertiesHeight = () => this._dashUIHeight;
- mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth();
- mainDocViewHeight = () => this._dashUIHeight;
+ mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
+ mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
componentDidMount() {
document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!));
@@ -116,8 +122,8 @@ export class MainView extends React.Component {
}
this._sidebarContent.proto = undefined;
if (!MainView.Live) {
- DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition",
- "panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap",
+ DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "showSidebar", "sidebarWidthPercent", "viewTransition",
+ "panX", "panY", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap",
"viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's
}
DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
@@ -133,7 +139,7 @@ export class MainView extends React.Component {
window.addEventListener("paste", KeyManager.Instance.paste as any);
document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on
const id = FormattedTextBox.GetDocFromUrl(e.detail);
- DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined) : (null));
+ DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : (null));
});
document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener);
this.initEventListeners();
@@ -173,18 +179,18 @@ export class MainView extends React.Component {
fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt,
fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faArrowsAltV,
- fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes,
+ fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes, fa.faFlag,
fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper as any,
fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, far.faCircle as any,
- fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer as any, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp,
- fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl,
+ fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, fa.faAngleDoubleDown, fa.faAngleDoubleLeft, fa.faAngleDoubleUp, faBuffer as any, fa.faExpand, fa.faUndo,
+ fa.faSlidersH, fa.faAngleUp, fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl,
fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo,
fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical,
fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll,
fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines,
- fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt, fa.faSearchPlus, fa.faVolumeUp, fa.faVolumeMute]);
+ fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt, fa.faSearchPlus, fa.faVolumeUp, fa.faVolumeDown, fa.faSquareRootAlt, fa.faVolumeMute]);
this.initAuthenticationRouters();
}
@@ -244,9 +250,8 @@ export class MainView extends React.Component {
title: "TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true
}));
}
- const pres = Docs.Create.PresDocument(new List<Doc>(),
- { title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" });
- CollectionDockingView.AddSplit(pres, "right");
+ const pres = Docs.Create.PresDocument({ title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" });
+ CollectionDockingView.AddSplit(pres, "left");
this.userDoc.activePresentation = pres;
Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres);
}
@@ -264,7 +269,7 @@ export class MainView extends React.Component {
title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
treeViewTruncateTitleWidth: 150, ignoreClick: true,
- isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true,
+ isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true,
explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
}));
@@ -273,33 +278,72 @@ export class MainView extends React.Component {
Doc.AddDocToList(this.userDoc.myFilesystem as Doc, "data", folder);
}
+ @observable _exploreMode = false;
+ @computed get exploreMode() {
+ return () => this._exploreMode ? ScriptField.MakeScript("CollectionBrowseClick(documentView, clientX, clientY)",
+ { documentView: "any", clientX: "number", clientY: "number" })! : undefined;
+ }
+ headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
+
+ @computed get headerBarDocView() {
+ return <div style={{ height: this.headerBarDocHeight() }}>
+ <DocumentView key="headerBarDoc"
+ Document={this.headerBarDoc}
+ DataDoc={undefined}
+ addDocument={undefined}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ docViewPath={returnEmptyDoclist}
+ styleProvider={DefaultStyleProvider}
+ rootSelected={returnTrue}
+ removeDocument={returnFalse}
+ fitContentsToDoc={returnTrue}
+ isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
+ isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
+ ScreenToLocalTransform={this.headerBarScreenXf}
+ childHideResizeHandles={returnTrue}
+ hideResizeHandles={true}
+ PanelWidth={this.headerBarDocWidth}
+ PanelHeight={this.headerBarDocHeight}
+ renderDepth={0}
+ focus={DocUtils.DefaultFocus}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ /></div>;
+ }
@computed get mainDocView() {
- return <DocumentView key="main"
- Document={this.mainContainer!}
- DataDoc={undefined}
- addDocument={undefined}
- addDocTab={this.addDocTabFunc}
- pinToPres={emptyFunction}
- docViewPath={returnEmptyDoclist}
- layerProvider={undefined}
- styleProvider={undefined}
- rootSelected={returnTrue}
- isContentActive={returnTrue}
- removeDocument={undefined}
- ScreenToLocalTransform={Transform.Identity}
- PanelWidth={this.mainDocViewWidth}
- PanelHeight={this.mainDocViewHeight}
- focus={DocUtils.DefaultFocus}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- suppressSetHeight={true}
- renderDepth={-1}
- />;
+ return <>
+ {this.headerBarDocView}
+ <DocumentView key="main"
+ Document={this.mainContainer!}
+ DataDoc={undefined}
+ addDocument={undefined}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ docViewPath={returnEmptyDoclist}
+ styleProvider={undefined}
+ rootSelected={returnTrue}
+ isContentActive={returnTrue}
+ removeDocument={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={this.mainDocViewWidth}
+ PanelHeight={this.mainDocViewHeight}
+ focus={DocUtils.DefaultFocus}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ suppressSetHeight={true}
+ renderDepth={-1}
+ /></>;
}
@computed get dockingContent() {
@@ -328,7 +372,7 @@ export class MainView extends React.Component {
this.closeFlyout);
}
- sidebarScreenToLocal = () => new Transform(0, -this.topOfMainDoc, 1);
+ sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
addDocTabFunc = (doc: Doc, location: string): boolean => {
const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":");
@@ -361,7 +405,6 @@ export class MainView extends React.Component {
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
- layerProvider={undefined}
styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === Doc.UserDoc().myFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
rootSelected={returnTrue}
removeDocument={returnFalse}
@@ -402,7 +445,6 @@ export class MainView extends React.Component {
docViewPath={returnEmptyDoclist}
focus={DocUtils.DefaultFocus}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
isContentActive={returnTrue}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -460,6 +502,16 @@ export class MainView extends React.Component {
</>;
}
+ @computed get headerBar() {
+ return !this.userDoc ? (null) :
+ <div className="mainView-dashboardArea" style={{
+ height: this.headerBarDocHeight(),
+ width: "100%",
+ }} >
+ {this.headerBarDocView}
+ </div>;
+ }
+
@computed get mainDashboardArea() {
return !this.userDoc ? (null) :
<div className="mainView-dashboardArea" ref={r => {
@@ -516,7 +568,6 @@ export class MainView extends React.Component {
dropAction={"alias"}
setHeight={returnFalse}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
rootSelected={returnTrue}
bringToFront={emptyFunction}
select={emptyFunction}
@@ -575,13 +626,6 @@ export class MainView extends React.Component {
</svg>;
}
- @computed get topbar() {
- TraceMobx();
- return <div className="mainView-topbar">
- <TopBar />
- </div>;
- }
-
@computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
return !DocumentLinksButton.invisibleWebDoc ? null :
<div className="mainView-invisibleWebRef" ref={DocumentLinksButton.invisibleWebRef}>
@@ -591,7 +635,6 @@ export class MainView extends React.Component {
ContainingCollectionDoc={undefined}
Document={DocumentLinksButton.invisibleWebDoc}
dropAction={"move"}
- layerProvider={undefined}
styleProvider={undefined}
isSelected={returnFalse}
select={returnFalse}
@@ -628,9 +671,9 @@ export class MainView extends React.Component {
<CaptureManager />
<GroupManager />
<GoogleAuthenticationManager />
- <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
+ <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
<ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
- {this.topbar}
+ <TopBar />
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
{DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => DocumentLinksButton.LinkEditorDocView = undefined)} docView={DocumentLinksButton.LinkEditorDocView} /> : (null)}
{LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
@@ -645,10 +688,12 @@ export class MainView extends React.Component {
<ContextMenu />
<RadialMenu />
<AnchorMenu />
+ <DashFieldViewMenu />
<MarqueeOptionsMenu />
<OverlayView />
<TimelineMenu />
<RichTextMenu />
+ <InkTranscription />
{this.snapLines}
<div className="mainView-webRef" ref={this.makeWebRef} />
<LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
@@ -673,7 +718,6 @@ export class MainView extends React.Component {
rootSelected={returnFalse}
renderDepth={0}
setHeight={returnFalse}
- layerProvider={undefined}
styleProvider={undefined}
addDocTab={returnFalse}
pinToPres={returnFalse}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a6b012bd6..e15624e23 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -27,7 +27,7 @@ export interface MarqueeAnnotatorProps {
containerOffset?: () => number[];
mainCont: HTMLDivElement;
docView: DocumentView;
- savedAnnotations: ObservableMap<number, HTMLDivElement[]>;
+ savedAnnotations: () => ObservableMap<number, HTMLDivElement[]>;
annotationLayer: HTMLDivElement;
addDocument: (doc: Doc) => boolean;
getPageFromScroll?: (top: number) => number;
@@ -128,7 +128,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
@undoBatch
@action
makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
- const savedAnnoMap = savedAnnotations ?? this.props.savedAnnotations;
+ const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
if (savedAnnoMap.size === 0) return undefined;
const savedAnnos = Array.from(savedAnnoMap.values())[0];
if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) {
@@ -239,7 +239,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
copy.style.height = fbounds.height.toString() + "px";
copy.className = "marqueeAnnotator-annotationBox";
(copy as any).marqueeing = true;
- MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations, this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
+ MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
}
AnchorMenu.Instance.jumpTo(cliX, cliY);
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 0f51cf9b2..95e146df4 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -2,10 +2,10 @@ import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import ReactLoading from 'react-loading';
-import { Doc } from "../../fields/Doc";
+import { Doc, HeightSym, WidthSym } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { Cast, NumCast } from "../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils } from "../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from "../../Utils";
import { DocUtils } from "../documents/Documents";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { DragManager } from "../util/DragManager";
@@ -146,6 +146,10 @@ export class OverlayView extends React.Component {
return remove;
}
+ removeOverlayDoc = (doc: Doc | Doc[]) => {
+ (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc));
+ return true;
+ }
@computed get overlayDocs() {
return CurrentUserUtils.OverlayDocs?.map(d => {
@@ -179,23 +183,22 @@ export class OverlayView extends React.Component {
offsetx = NumCast(d.x) - e.clientX;
offsety = NumCast(d.y) - e.clientY;
};
- return <div className="overlayView-doc" ref={dref} key={d[Id]} onPointerDown={onPointerDown} style={{ top: d.type === 'presentation' ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}>
+ return <div className="overlayView-doc" ref={dref} key={d[Id]} onPointerDown={onPointerDown} style={{ top: 0, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}>
<DocumentView
Document={d}
rootSelected={returnTrue}
bringToFront={emptyFunction}
addDocument={undefined}
- removeDocument={undefined}
- PanelWidth={returnOne}
- PanelHeight={returnOne}
- ScreenToLocalTransform={Transform.Identity}
+ removeDocument={this.removeOverlayDoc}
+ PanelWidth={d[WidthSym]}
+ PanelHeight={d[HeightSym]}
+ ScreenToLocalTransform={() => new Transform(-NumCast(d.x), -NumCast(d.y), 1)}
renderDepth={1}
- isDocumentActive={returnTrue}
- isContentActive={emptyFunction}
+ isDocumentActive={returnFalse}
+ isContentActive={returnTrue}
whenChildContentsActiveChanged={emptyFunction}
focus={DocUtils.DefaultFocus}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
docViewPath={returnEmptyDoclist}
addDocTab={returnFalse}
pinToPres={emptyFunction}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 529697f71..954529bc9 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -54,7 +54,6 @@ export default class Palette extends React.Component<PaletteProps> {
focus={emptyFunction}
docViewPath={returnEmptyDoclist}
styleProvider={returnEmptyString}
- layerProvider={undefined}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 21c688421..bcfd2dd56 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -303,7 +303,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
fitContentsToDoc={returnTrue}
rootSelected={returnFalse}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
docViewPath={returnEmptyDoclist}
freezeDimensions={true}
dontCenter={"y"}
@@ -1010,7 +1009,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
createNewFilterDoc={this.createNewFilterDoc}
updateFilterDoc={this.updateFilterDoc}
docViewPath={returnEmptyDoclist}
- layerProvider={undefined}
dontCenter="y"
/>
</div>
@@ -1229,6 +1227,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow)));
}
+ toggleZoomToTarget1 = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom)));
+ }
+ toggleZoomToTarget2 = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom)));
+ }
+
@computed
get editRelationship() {
return <input
@@ -1285,18 +1290,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
<div className="propertiesView-section">
<p className="propertiesView-label">Information</p>
- <div className="propertiesView-input first" id="propertiesView-category">
+ <div className="propertiesView-input first">
<p>Link Relationship</p>
{this.editRelationship}
</div>
- <div className="propertiesView-input" id="propertiesView-description">
+ <div className="propertiesView-input">
<p>Description</p>
{this.editDescription}
</div>
</div>
<div className="propertiesView-section">
<p className="propertiesView-label">Behavior</p>
- <div className="propertiesView-input inline first" id="propertiesView-follow">
+ <div className="propertiesView-input inline first">
<p>Follow</p>
<select
name="selectList"
@@ -1316,7 +1321,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
: null}
</select>
</div>
- <div className="propertiesView-input inline" id="propertiesView-anchor">
+ <div className="propertiesView-input inline">
<p>Auto-move anchor</p>
<button
style={{ background: this.selectedDoc.hidden ? "gray" : !this.selectedDoc.linkAutoMove ? "" : "#4476f7", borderRadius: 3 }}
@@ -1326,7 +1331,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
</button>
</div>
- <div className="propertiesView-input inline" id="propertiesView-displayArrow">
+ <div className="propertiesView-input inline">
<p>Display arrow</p>
<button
style={{ background: this.selectedDoc.hidden ? "gray" : !this.selectedDoc.displayArrow ? "" : "#4476f7", borderRadius: 3 }}
@@ -1336,6 +1341,26 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
</button>
</div>
+ <div className="propertiesView-input inline">
+ <p>Zoom to target</p>
+ <button
+ style={{ background: this.selectedDoc.hidden ? "gray" : !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom ? "" : "#4476f7", borderRadius: 3 }}
+ onPointerDown={this.toggleZoomToTarget1} onClick={e => e.stopPropagation()}
+ className="propertiesButton"
+ >
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Zoom to source</p>
+ <button
+ style={{ background: this.selectedDoc.hidden ? "gray" : !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom ? "" : "#4476f7", borderRadius: 3 }}
+ onPointerDown={this.toggleZoomToTarget2} onClick={e => e.stopPropagation()}
+ className="propertiesButton"
+ >
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
</div>
</div >;
}
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 4ed6da24a..2b045aa6c 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -212,7 +212,7 @@ export class ScriptingRepl extends React.Component {
}
onBlur = () => {
- this.overlayDisposer && this.overlayDisposer();
+ this.overlayDisposer?.();
}
render() {
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 04c0565ea..9e939aa19 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -4,7 +4,7 @@ import { Doc, DocListCast, StrListCast, Opt } from "../../fields/Doc";
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, OmitKeys, returnOne, returnTrue, returnZero } from '../../Utils';
+import { emptyFunction, OmitKeys, returnAll, returnOne, returnTrue, returnZero } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
import { Transform } from '../util/Transform';
import { CollectionStackingView } from './collections/CollectionStackingView';
@@ -147,7 +147,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
renderDepth={this.props.renderDepth + 1}
viewType={CollectionViewType.Stacking}
fieldKey={this.sidebarKey}
- pointerEvents={"all"}
+ pointerEvents={returnAll}
/>
</div>
</div>;
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index a530ff90a..35415ae4e 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -24,13 +24,11 @@ import { SliderBox } from './nodes/SliderBox';
import "./StyleProvider.scss";
import React = require("react");
import { InkingStroke } from './InkingStroke';
-
-export enum StyleLayers {
- Background = "background"
-}
+import { TreeSort } from './collections/TreeView';
export enum StyleProp {
TreeViewIcon = "treeViewIcon",
+ TreeViewSortings = "treeViewSortings",// options for how to sort tree view items
DocContents = "docContents", // when specified, the JSX returned will replace the normal rendering of the document view
Opacity = "opacity", // opacity of the document view
Hidden = "hidden", // whether the document view should not be isplayed
@@ -54,14 +52,10 @@ export enum StyleProp {
function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; }
-function toggleBackground(doc: Doc) {
+function toggleLockedPosition(doc: Doc) {
UndoManager.RunInBatch(() => runInAction(() => {
- const layers = StrListCast(doc._layerTags);
- if (!layers.includes(StyleLayers.Background)) {
- if (!layers.length) doc._layerTags = new List<string>([StyleLayers.Background]);
- else layers.push(StyleLayers.Background);
- }
- else layers.splice(layers.indexOf(StyleLayers.Background), 1);
+ doc._lockedPosition = !doc._lockedPosition;
+ doc._pointerEvents = doc._lockedPosition ? "none" : undefined;
}), "toggleBackground");
}
@@ -76,6 +70,7 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
// a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
//
export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any {
+ const remoteDocHeader = "author;creationDate;noMargin";
const docProps = testDocProps(props) ? props : undefined;
const selected = property.includes(":selected");
const isCaption = property.includes(":caption");
@@ -84,7 +79,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const isOpen = property.includes(":open");
const fieldKey = props?.fieldKey ? props.fieldKey + "-" : isCaption ? "caption-" : "";
const comicStyle = () => doc && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === "comic";
- const isBackground = () => StrListCast(doc?._layerTags).includes(StyleLayers.Background);
+ const isBackground = () => doc && BoolCast(doc._lockedPosition);
const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
const showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
@@ -98,6 +93,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
return <img src={url} width={20} height={15} style={{ margin: "auto", display: "block", objectFit: "contain" }} />;
}
return Doc.toIcon(doc, isOpen);
+
+ case StyleProp.TreeViewSortings:
+ const allSorts: { [key: string]: { color: string, label: string } | undefined } = {};
+ allSorts[TreeSort.Down] = { color: "blue", label: "↓" };
+ allSorts[TreeSort.Up] = { color: "crimson", label: "↑" };
+ if (doc?._viewType === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: "green", label: "z" };
+ allSorts[TreeSort.None] = { color: "darkgray", label: '\u00A0\u00A0\u00A0' };
+ return allSorts;
case StyleProp.DocContents: return undefined;
case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? "lightgrey" : "dimgrey";
case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
@@ -109,7 +112,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
props?.showTitle?.() ||
(!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ?
(doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) :
- "author;creationDate") : "")) || "");
+ remoteDocHeader) : "")) || "");
case StyleProp.Color:
if (MainView.Instance.LastButton === doc) return Colors.DARK_GRAY;
const docColor: Opt<string> = StrCast(doc?.[fieldKey + "color"], StrCast(doc?._color));
@@ -124,7 +127,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case StyleProp.BorderPath: return comicStyle() && props?.renderDepth && doc?.type !== DocumentType.INK ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 };
case StyleProp.JitterRotation: return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
- doc?.type === DocumentType.RTF || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0;
+ (doc?.type === DocumentType.RTF && !showTitle()?.includes("noMargin")) || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0;
case StyleProp.BackgroundColor: {
if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY;
let docColor: Opt<string> =
@@ -133,7 +136,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
StrCast(props?.Document.backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : "")));
switch (doc?.type) {
case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break;
- case DocumentType.PRES: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
+ case DocumentType.PRES: docColor = docColor || (darkScheme() ? "transparent" : "transparent"); break;
case DocumentType.FONTICON: docColor = docColor || Colors.DARK_GRAY; break;
case DocumentType.RTF: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
case DocumentType.FILTER: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : "rgba(105, 105, 105, 0.432)"); break;
@@ -154,20 +157,19 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break;
docColor = docColor ||
(doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc) ? (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY) : // system docs (seen in treeView) get a grayish background
- isBackground() ? "cyan" : // ?? is there a good default for a background collection
- doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc
- (doc?._isGroup ? undefined :
- StrCast((props?.renderDepth || 0) > 0 ?
- Doc.UserDoc().activeCollectionNestedBackground :
- Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ?
- Colors.BLACK :
- "linear-gradient(#065fff, #85c1f9)"))
- ));
+ doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc
+ (doc?._isGroup ? undefined :
+ Cast((props?.renderDepth || 0) > 0 ?
+ Doc.UserDoc().activeCollectionNestedBackground :
+ Doc.UserDoc().activeCollectionBackground, "string") ?? (darkScheme() ?
+ Colors.BLACK :
+ "linear-gradient(#065fff, #85c1f9)"))
+ );
break;
//if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
}
- if (docColor && (!doc || props?.layerProvider?.(doc) === false)) docColor = DashColor(docColor).fade(0.5).toString();
+ if (docColor && !doc) docColor = DashColor(docColor).fade(0.5).toString();
return docColor;
}
case StyleProp.BoxShadow: {
@@ -193,19 +195,17 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
}
case StyleProp.PointerEvents:
- if (doc?.type === DocumentType.MARKER) return "none";
- if (props?.pointerEvents === "none") return "none";
- const layer = doc && props?.layerProvider?.(doc);
+ if (doc?.pointerEvents) return StrCast(doc.pointerEvents);
+ if (props?.pointerEvents?.() === "none") return "none";
const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name);
if (opacity() === 0 || (isInk && !docProps?.treeViewDoc) || doc?.isInkMask) return "none";
- if (layer === false && !selected && !SnappingManager.GetIsDragging()) return "none";
- if (!isInk && layer === true) return "all";
+ if (!isInk) return "all";
return undefined;
case StyleProp.Decorations:
if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform || doc?.x !== undefined || doc?.y !== undefined) {
- return doc && (isBackground() || selected) && (props?.renderDepth || 0) > 0 &&
+ return doc && (isBackground() || selected) && !Doc.IsSystem(doc) && (props?.renderDepth || 0) > 0 &&
((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ?
- <div className="styleProvider-lock" onClick={() => toggleBackground(doc)}>
+ <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
<FontAwesomeIcon icon={isBackground() ? "lock" : "unlock"} style={{ color: isBackground() ? "red" : undefined }} size="lg" />
</div>
: (null);
@@ -236,35 +236,7 @@ export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps
DocFocusOrOpen(doc, props?.ContainingCollectionDoc);
}
})}
- {DashboardToggleButton(doc, "lockedPosition", "lock", "unlock")}
</>;
}
return DefaultStyleProvider(doc, props, property);
}
-
-//
-// a preliminary semantic-"layering/grouping" mechanism for determining interactive properties of documents
-// currently, the provider tests whether the docuemnt's layer field matches the activeLayer field of the tab.
-// if it matches, then the document gets pointer events, otherwise it does not.
-//
-export function DefaultLayerProvider(thisDoc: Doc) {
- return (doc: Doc, assign?: boolean) => {
- if (doc.z) return true;
- if (assign) {
- const activeLayer = StrCast(thisDoc?.activeLayer);
- if (activeLayer) {
- const layers = Cast(doc._layerTags, listSpec("string"), []);
- if (layers.length && !layers.includes(activeLayer)) layers.push(activeLayer);
- else if (!layers.length) doc._layerTags = new List<string>([activeLayer]);
- if (activeLayer === "red" || activeLayer === "green" || activeLayer === "blue") doc._backgroundColor = activeLayer;
- }
- return true;
- } else {
- if (Doc.AreProtosEqual(doc, thisDoc)) return true;
- const layers = StrListCast(doc._layerTags);
- if (!layers.length && !thisDoc?.activeLayer) return true;
- if (layers.includes(StrCast(thisDoc?.activeLayer))) return true;
- return false;
- }
- };
-} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index b3a24e031..636f7042f 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -131,7 +131,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
setHeight={returnFalse}
docViewPath={returnEmptyDoclist}
docFilters={returnEmptyFilter}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 64a5f38d4..de2106e4a 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -81,6 +81,8 @@ export class CollectionDockingView extends CollectionSubView() {
tabItemDropped = () => DragManager.CompleteWindowDrag?.(false);
tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => {
+ const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc;
+ dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc]));
DragManager.CompleteWindowDrag = (aborted: boolean) => {
if (aborted) {
proxy._dragListener.AbortDrag();
@@ -184,18 +186,6 @@ export class CollectionDockingView extends CollectionSubView() {
}
const instance = CollectionDockingView.Instance;
if (!instance) return false;
- else {
- const docList = DocListCast(instance.props.Document[DataSym]["data-all"]);
- // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases
- !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document);
- // adds an alias of the doc to the data-all field of the layoutdocs of the aliases
- DocListCast(instance.props.Document[DataSym].aliases).forEach(alias => {
- const aliasDocList = DocListCast(alias["data-all"]);
- // if aliasDocList contains the alias, don't do anything
- // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias
- !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data-all", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document);
- });
- }
const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
if (!pullSide && stack) {
@@ -384,7 +374,7 @@ export class CollectionDockingView extends CollectionSubView() {
}
}
if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) &&
- ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
e.stopPropagation();
}
}
@@ -404,7 +394,10 @@ export class CollectionDockingView extends CollectionSubView() {
const origtabdocs = DocListCast(origtab.data);
const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeAlias(origtab);
const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeAlias(origtabdoc));
- newtabdocs.length && (Doc.GetProto(newtab).data = new List<Doc>(newtabdocs));
+ if (newtabdocs.length) {
+ Doc.GetProto(newtab).data = new List<Doc>(newtabdocs);
+ newtabdocs.forEach(ntab => ntab.context = newtab);
+ }
json = json.replace(origtab[Id], newtab[Id]);
return newtab;
});
@@ -418,29 +411,10 @@ export class CollectionDockingView extends CollectionSubView() {
const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", ""));
const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
- const changesMade = this.props.Document.dockingConfig !== json;
+ const changesMade = this.props.Document.dockcingConfig !== json;
if (changesMade && !this._flush) {
this.props.Document.dockingConfig = json;
- setTimeout(async () => {
- const sublists = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
- const tabs = sublists && Cast(sublists[0], Doc, null);
- // const other = sublists && Cast(sublists[1], Doc, null);
- const tabdocs = await DocListCastAsync(tabs?.data);
- // const otherdocs = await DocListCastAsync(other?.data);
- if (tabs) {
- tabs.data = new List<Doc>(docs);
- // DocListCast(tabs.aliases).forEach(tab => tab !== tabs && (tab.data = new List<Doc>(docs)));
- }
- // const otherSet = new Set<Doc>();
- // otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc));
- // tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc));
- // const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP);
- // this.props.Document[DataSym][this.props.fieldKey + "-all"] = new List<Doc>([...docs, ...vals]);
- // if (other) {
- // other.data = new List<Doc>(vals);
- // // DocListCast(other.aliases).forEach(tab => tab !== other && (tab.data = new List<Doc>(vals)));
- // }
- }, 0);
+ this.props.Document.data = new List<Doc>(docs);
}
return changesMade;
}
@@ -474,12 +448,11 @@ export class CollectionDockingView extends CollectionSubView() {
//if (confirm('really close this?')) {
if (!stack.parent.parent.isRoot || stack.parent.contentItems.length > 1) {
stack.remove();
- stack.contentItems.forEach((contentItem: any) => Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", contentItem.tab.DashDoc, undefined, true, true));
} else {
alert('cant delete the last stack');
}
}));
- stack.header?.controlsContainer.find('.lm_popout') //get the close icon
+ stack.header?.controlsContainer.find('.lm_popout') //get the popout icon
.off('click') //unbind the current click handler
.click(action(() => {
// stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size
@@ -502,4 +475,6 @@ ScriptingGlobals.add(function openInLightbox(doc: any) { LightboxView.AddDocTab(
"opens up document in a lightbox", "(doc: any)");
ScriptingGlobals.add(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); },
"opens up document in tab on right side of the screen", "(doc: any)");
+ScriptingGlobals.add(function openInOverlay(doc: any) { return Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); },
+ "opens up document in screen overlay layer", "(doc: any)");
ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); }); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 364a2440e..23fd4206c 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -84,11 +84,11 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{
}
@action
- toggleProperties = () => {
- if (CurrentUserUtils.propertiesWidth > 0) {
- CurrentUserUtils.propertiesWidth = 0;
+ toggleTopBar = () => {
+ if (CurrentUserUtils.headerBarHeight > 0) {
+ CurrentUserUtils.headerBarHeight = 0;
} else {
- CurrentUserUtils.propertiesWidth = 250;
+ CurrentUserUtils.headerBarHeight = 60;
}
}
@@ -108,7 +108,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{
dropAction={"alias"}
setHeight={returnFalse}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
rootSelected={returnTrue}
bringToFront={emptyFunction}
select={emptyFunction}
@@ -138,14 +137,13 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{
render() {
- const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left";
- const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel";
+ const propIcon = CurrentUserUtils.headerBarHeight > 0 ? "angle-double-up" : "angle-double-down";
+ const propTitle = CurrentUserUtils.headerBarHeight > 0 ? "Close Header Bar" : "Open Header Bar";
- const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="properties" placement="bottom">
+ const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="topar" placement="bottom">
<div className="collectionMenu-hardCodedButton"
style={{ backgroundColor: CurrentUserUtils.propertiesWidth > 0 ? Colors.MEDIUM_BLUE : undefined }}
- key="properties"
- onPointerDown={this.toggleProperties}>
+ onPointerDown={this.toggleTopBar}>
<FontAwesomeIcon icon={propIcon} size="lg" />
</div>
</Tooltip>;
@@ -468,7 +466,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
@undoBatch
@action
startRecording = () => {
- const doc = Docs.Create.ScreenshotDocument("screen recording", { _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" });
+ const doc = Docs.Create.ScreenshotDocument({ title: "screen recording", _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" });
//Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc);
CollectionDockingView.AddSplit(doc, "right");
}
@@ -733,6 +731,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
@computed get drawButtons() {
const func = action((e: React.MouseEvent | React.PointerEvent, i: number, keep: boolean) => {
this._keepPrimitiveMode = keep;
+ // these are for shapes
if (this._selectedPrimitive !== i) {
this._selectedPrimitive = i;
if (this._title[i] === "highlighter") {
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index de307416f..ca0b9d3d3 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -22,6 +22,7 @@ import {
returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent
} from "../../../Utils";
import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
@@ -182,7 +183,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
) {
// if shift pressed scrub 1 second otherwise 1/10th
const jump = e.shiftKey ? 1 : 0.1;
- e.stopPropagation();
switch (e.key) {
case " ":
if (!CollectionStackedTimeline.SelectingRegion) {
@@ -202,18 +202,22 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerEnd = undefined;
CollectionStackedTimeline.SelectingRegion = undefined;
}
+ e.stopPropagation();
break;
case "Escape":
// abandons current trim
this._trimStart = this.clipStart;
this._trimStart = this.clipEnd;
this._trimming = TrimScope.None;
+ e.stopPropagation();
break;
case "ArrowLeft":
this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
+ e.stopPropagation();
break;
case "ArrowRight":
this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
+ e.stopPropagation();
break;
}
}
@@ -392,7 +396,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
// handles dragging and dropping markers in timeline
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
- if (!de.embedKey && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
+ if (!de.embedKey && this.props.Document._isGroup) return false;
if (!super.onInternalDrop(e, de)) return false;
// determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
@@ -434,6 +438,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
title: ComputedField.MakeFunction(
`"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`
) as any,
+ _minFontSize: 12,
+ _maxFontSize: 24,
+ _singleLine: false,
_stayInCollection: true,
useLinkSmallAnchor: true,
hideLinkButton: true,
@@ -692,7 +699,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
PanelHeight={this.timelineContentHeight}
PanelWidth={this.timelineContentWidth}
/>
- {this.renderDictation}
+ {/* {this.renderDictation} */}
<div
className="collectionStackedTimeline-current"
@@ -785,6 +792,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
// updates marker document title to reflect correct timecodes
computeTitle = () => {
+ if (this.props.mark.type !== DocumentType.LABEL) return undefined;
const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
return `#${formatTime(start)}-${formatTime(end)}`;
@@ -918,7 +926,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
DataDoc={undefined}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={undefined}
- LayoutTemplateString={LabelBox.LayoutStringWithTitle(LabelBox, "data", this.computeTitle())}
+ LayoutTemplateString={LabelBox.LayoutStringWithTitle("data", this.computeTitle())}
isDocumentActive={this.props.isDocumentActive}
PanelWidth={width}
PanelHeight={height}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 509005b45..dddae4a34 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -28,6 +28,8 @@ import "./CollectionStackingView.scss";
import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionViewType } from "./CollectionView";
+import { FieldViewProps } from "../nodes/FieldView";
+import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
const _global = (window /* browser */ || global /* node */) as any;
@@ -207,6 +209,25 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
return this.props.styleProvider?.(doc, props, property);
}
+ @undoBatch
+ @action
+ onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ const docView = fieldProps.DocumentView?.();
+ if (docView && ["Enter"].includes(e.key) && e.ctrlKey) {
+ e.stopPropagation?.();
+ const below = !e.altKey && e.key !== "Tab";
+ const layoutKey = StrCast(docView.LayoutFieldKey);
+ const newDoc = Doc.MakeCopy(docView.rootDoc, true);
+ const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
+ newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+ if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) {
+ newDoc[layoutKey] = docView.rootDoc[layoutKey];
+ }
+ Doc.GetProto(newDoc).text = undefined;
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ return this.addDocument?.(newDoc);
+ }
+ }
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
getDisplayDoc(doc: Doc, width: () => number) {
const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc;
@@ -222,10 +243,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
PanelWidth={width}
PanelHeight={height}
styleProvider={this.styleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={this.props.docViewPath}
fitWidth={this.props.childFitWidth}
isContentActive={emptyFunction}
+ onKey={this.onKeyDown}
isDocumentActive={this.isContentActive}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
@@ -483,7 +504,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (value && this.columnHeaders) {
const schemaHdrField = new SchemaHeaderField(value);
this.columnHeaders.push(schemaHdrField);
- DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: "schemaHdrField.color" }]);
return true;
}
return false;
@@ -543,7 +563,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
renderDepth={this.props.renderDepth}
focus={emptyFunction}
styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 58289a161..7f96217b8 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -59,6 +59,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@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;
createColumnDropRef = (ele: HTMLDivElement | null) => {
@@ -69,6 +70,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
}
}
+
componentWillUnmount() {
this.props.unobserveHeight(this._ele);
}
@@ -237,6 +239,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y);
ContextMenu.Instance.displayMenu(x, y, undefined, true);
}
+
+
+
@computed get innards() {
TraceMobx();
const key = this.props.pivotField;
@@ -307,7 +312,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
}}>
{this.props.renderChildren(this.props.docList)}
</div>
- {!this.props.chromeHidden && type !== DocumentType.PRES ?
+ {!this.props.chromeHidden ?
<div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}>
<EditableView
@@ -317,7 +322,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
contents={"+ NEW"}
toggle={this.toggleVisibility}
menuCallback={this.menuCallback} />
- </div> : null}
+ </div>
+ : null
+ }
+
+
</div>
}
</>;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 30e0adf24..17fdba764 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -316,28 +316,20 @@ export function CollectionSubView<X>(moreProps?: X) {
}
});
} else {
- const srcWeb = SelectionManager.Docs().lastElement();
- const srcUrl = (srcWeb?.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
+ const srcWeb = SelectionManager.Views().lastElement();
+ const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0];
const reg = new RegExp(Utils.prepend(""), "g");
const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
- const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
+ const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, "$1")).filter(t => t)?.[0];
+ const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? "from:" + srcUrl : "-web clip-", _width: 300, _height: 300, backgroundColor });
Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc).text = text;
addDocument(htmlDoc);
if (srcWeb) {
const iframe = SelectionManager.Views()[0].ContentDiv?.getElementsByTagName("iframe")?.[0];
const focusNode = (iframe?.contentDocument?.getSelection()?.focusNode as any);
if (focusNode) {
- const rects = iframe?.contentWindow?.getSelection()?.getRangeAt(0).getClientRects();
- "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
- const x = (rects && Array.from(rects).reduce((x: any, r: DOMRect) => x === undefined || r.x < x ? r.x : x, undefined as any)) || 0;
- const y = NumCast(srcWeb._scrollTop) + ((rects && Array.from(rects).reduce((y: any, r: DOMRect) => y === undefined || r.y < y ? r.y : y, undefined as any)) || 0);
- const r = (rects && Array.from(rects).reduce((x: any, r: DOMRect) => x === undefined || r.x + r.width > x ? r.x + r.width : x, undefined as any)) || 0;
- const b = NumCast(srcWeb._scrollTop) + ((rects && Array.from(rects).reduce((y: any, r: DOMRect) => y === undefined || r.y + r.height > y ? r.y + r.height : y, undefined as any)) || 0);
- const anchor = Docs.Create.FreeformDocument([], { backgroundColor: "transparent", _width: r - x, _height: b - y, x, y, annotationOn: srcWeb });
- anchor.context = srcWeb;
- const key = Doc.LayoutFieldKey(srcWeb);
- Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
- DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
+ const anchor = srcWeb?.ComponentView?.getAnchor?.();
+ anchor && DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
}
}
}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index e63ea7151..4f6f45d2f 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -10,6 +10,7 @@ import { ComputedField, ScriptField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { ScriptingGlobals } from "../../util/ScriptingGlobals";
import { ContextMenu } from "../ContextMenu";
@@ -128,6 +129,7 @@ export class CollectionTimeView extends CollectionSubView() {
}
}
+ dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF;
@computed get contents() {
return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : "none" }}
onClick={this.contentsDown}>
@@ -137,6 +139,7 @@ export class CollectionTimeView extends CollectionSubView() {
childClickScript={this._childClickedScript}
viewDefDivClick={this._viewDefDivClick}
childFreezeDimensions={true}
+ dontScaleFilter={this.dontScaleFilter}
layoutEngine={this.layoutEngine} />
</div>;
}
@@ -174,7 +177,7 @@ export class CollectionTimeView extends CollectionSubView() {
typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] !== "#" || fieldKey === "#") && (fieldKey === "tags" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey)));
Array.from(keySet).map(fieldKey =>
docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" }));
- docItems.push({ description: ":(null)", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
+ docItems.push({ description: ":default", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
ContextMenu.Instance.displayMenu(x, y, ":");
@@ -234,16 +237,17 @@ export class CollectionTimeView extends CollectionSubView() {
}
ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
+ const pivotField = StrCast(pivotDoc._pivotField) || "author";
let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
- pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotDoc._pivotField;
+ pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotField;
pivotDoc._prevFilterIndex = ++prevFilterIndex;
- runInAction(() => {
- pivotDoc._docFilters = new List();
+ pivotDoc._docFilters = new List();
+ setTimeout(action(() => {
const filterVals = (bounds.payload as string[]);
- filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+ filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, "check"));
const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
@@ -252,11 +256,11 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
}
const newFilters = StrListCast(pivotDoc._docFilters);
if (newFilters.length && originalFilter.length &&
- newFilters[newFilters.length - 1] === originalFilter[originalFilter.length - 1]) {
+ newFilters.lastElement() === originalFilter.lastElement()) {
pivotDoc._prevFilterIndex = --prevFilterIndex;
pivotDoc["_prevDocFilter" + prevFilterIndex] = undefined;
pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = undefined;
pivotDoc["_prevPivotFields" + prevFilterIndex] = undefined;
}
- });
+ }));
}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 41970eb96..e809bfbce 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -27,6 +27,7 @@ import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import { TreeView } from "./TreeView";
import React = require("react");
+import { FieldViewProps } from "../nodes/FieldView";
const _global = (window /* browser */ || global /* node */) as any;
export type collectionTreeViewProps = {
@@ -37,8 +38,18 @@ export type collectionTreeViewProps = {
treeViewSkipFields?: string[]; // prevents specific fields from being displayed (see LinkBox)
onCheckedClick?: () => ScriptField;
onChildClick?: () => ScriptField;
+ // TODO: [AL] add these fields
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ hierarchyIndex?: number[];
};
+export enum TreeViewType {
+ outline = "outline",
+ fileSystem = "fileSystem",
+ default = "default"
+}
+
@observer
export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() {
private _treedropDisposer?: DragManager.DragDropDisposer;
@@ -54,8 +65,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
@computed get dataDoc() { return this.props.DataDoc || this.doc; }
@computed get treeViewtruncateTitleWidth() { return NumCast(this.doc.treeViewTruncateTitleWidth, this.panelWidth()); }
@computed get treeChildren() { TraceMobx(); return this.props.childDocuments || this.childDocs; }
- @computed get outlineMode() { return this.doc.treeViewType === "outline"; }
- @computed get fileSysMode() { return this.doc.treeViewType === "fileSystem"; }
+ @computed get outlineMode() { return this.doc.treeViewType === TreeViewType.outline; }
+ @computed get fileSysMode() { return this.doc.treeViewType === TreeViewType.fileSystem; }
@computed get dashboardMode() { return this.doc === Doc.UserDoc().myDashboards; }
@observable _explainerHeight = 0; // height of the description of the tree view
@@ -180,13 +191,20 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
height={"auto"}
GetValue={() => StrCast(this.dataDoc.title)}
SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
- if (enter && this.props.Document.treeViewType === "outline") this.makeTextCollection(this.treeChildren);
+ if (enter && this.props.Document.treeViewType === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
this.dataDoc.title = value;
return true;
})} />;
}
+ onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ if (this.outlineMode && e.key === "Enter") {
+ e.stopPropagation();
+ this.makeTextCollection(this.treeChildren);
+ return true;
+ }
+ }
get documentTitle() {
return <FormattedTextBox
{...this.props}
@@ -199,6 +217,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
PanelWidth={this.documentTitleWidth}
PanelHeight={this.documentTitleHeight}
scaling={returnOne}
+ onKey={this.onKey}
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
@@ -250,20 +269,18 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, "boolean", null),
this.observeHeight,
this.unobserveHeight,
- this.childContextMenuItems()
+ this.childContextMenuItems(),
+ //TODO: [AL] add these
+ this.props.AddToMap,
+ this.props.RemFromMap,
+ this.props.hierarchyIndex,
);
}
@computed get titleBar() {
return this.dataDoc === null ? (null) :
<div className="collectionTreeView-titleBar" key={this.doc[Id]}
style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}}
- ref={r => this._titleRef = r}
- onKeyDown={e => {
- if (this.outlineMode) {
- e.stopPropagation();
- e.key === "Enter" && this.makeTextCollection(this.treeChildren);
- }
- }}>
+ ref={r => this._titleRef = r}>
{this.outlineMode ? this.documentTitle : this.editableTitle}
</div>;
}
@@ -296,7 +313,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
renderDepth={this.props.renderDepth + 1}
focus={emptyFunction}
styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ee2c28b5f..965f0a352 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -6,7 +6,7 @@ import { Doc, DocListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { ObjectField } from '../../../fields/ObjectField';
import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { returnEmptyString } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
@@ -79,6 +79,10 @@ export interface CollectionViewProps extends FieldViewProps {
childIgnoreNativeSize?: boolean;
childClickScript?: ScriptField;
childDoubleClickScript?: ScriptField;
+ //TODO: [AL] add these fields
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
}
@observer
export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps>() {
@@ -237,6 +241,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
bodyPanelWidth = () => this.props.PanelWidth();
+ childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles);
+ childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle);
childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
@computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); }
@@ -258,10 +264,12 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
ScreenToLocalTransform: this.screenToLocalTransform,
childLayoutTemplate: this.childLayoutTemplate,
childLayoutString: this.childLayoutString,
+ childHideResizeHandles: this.childHideResizeHandles,
+ childHideDecorationTitle: this.childHideDecorationTitle,
CollectionView: this,
};
return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
- style={{ pointerEvents: this.props.layerProvider?.(this.rootDoc) === false ? "none" : undefined }}>
+ style={{ pointerEvents: this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? "none" : undefined }}>
{this.showIsTagged()}
{this.renderSubView(this.collectionViewType, props)}
</div>);
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 78b125e07..dab3d490d 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -9,6 +9,7 @@ import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
import { DataSym, Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
import { FieldId } from "../../../fields/RefField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
@@ -24,16 +25,16 @@ import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { Colors, Shadows } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
+import { MainView } from '../MainView';
import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import { PinProps, PresBox, PresMovement } from '../nodes/trails';
-import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider';
+import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
import { CollectionDockingView } from './CollectionDockingView';
import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionView, CollectionViewType } from './CollectionView';
import "./TabDocView.scss";
import React = require("react");
-import { List } from '../../../fields/List';
const _global = (window /* browser */ || global /* node */) as any;
interface TabDocViewProps {
@@ -65,6 +66,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
get stack() { return (this.props as any).glContainer.parent.parent; }
get tab() { return (this.props as any).glContainer.tab; }
get view() { return this._view; }
+ _lastTab: any;
+ _lastView: DocumentView | undefined;
@action
init = (tab: any, doc: Opt<Doc>) => {
@@ -73,7 +76,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
tab._disposers = {} as { [name: string]: IReactionDisposer };
tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
tab.DashDoc = doc;
- CollectionDockingView.Instance.tabMap.add(tab);
const iconType: IconProp = Doc.toIcon(doc);
// setup the title element and set its size according to the # of chars in the title. Show the full title when clicked.
const titleEle = tab.titleElement[0];
@@ -83,6 +85,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
titleEle.size = StrCast(doc.title).length + 3;
titleEle.value = doc.title;
+ titleEle.onkeydown = (e: KeyboardEvent) => {
+ e.stopPropagation();
+ };
titleEle.onchange = undoBatch(action((e: any) => {
titleEle.size = e.currentTarget.value.length + 3;
Doc.GetProto(doc).title = e.currentTarget.value;
@@ -94,26 +99,13 @@ export class TabDocView extends React.Component<TabDocViewProps> {
if (tab.element[0].children[1].children.length === 1) {
- const toggle = document.createElement("div");
- toggle.style.width = "10px";
- toggle.style.height = "calc(100% - 2px)";
- toggle.style.left = "-2px";
- toggle.style.bottom = "1px";
- toggle.style.borderTopRightRadius = "7px";
- toggle.style.position = "relative";
- toggle.style.display = "inline-block";
- toggle.style.background = "transparent";
- toggle.onclick = (e: MouseEvent) => {
- if (tab.contentItem === tab.header.parent.getActiveContentItem()) {
- tab.DashDoc.activeLayer = tab.DashDoc.activeLayer ? undefined : StyleLayers.Background;
- }
- };
iconWrap.className = "lm_iconWrap";
iconWrap.id = "lm_iconWrap";
closeWrap.className = "lm_iconWrap";
closeWrap.id = "lm_closeWrap";
closeWrap.onclick = (e: MouseEvent) => {
tab.header.parent.contentItem.remove();
+ Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", tab.DashDoc);
Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true);
};
const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
@@ -189,11 +181,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
tab.closeElement.off('click') //unbind the current click handler
.click(function () {
Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
- Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true);
SelectionManager.DeselectAll();
UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab");
});
}
+ CollectionDockingView.Instance.tabMap.add(tab);
}
/**
@@ -246,9 +238,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
const dview = CollectionDockingView.Instance.props.Document;
const fieldKey = CollectionDockingView.Instance.props.fieldKey;
- const sublists = DocListCast(dview[fieldKey]);
- const tabs = Cast(sublists[0], Doc, null);
- const tabdocs = await DocListCastAsync(tabs?.data);
+ const tabdocs = await DocListCastAsync(dview[fieldKey]);
runInAction(() => {
if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) {
tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs
@@ -256,7 +246,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
PresBox.Instance?._selectedArray.clear();
pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
- DocumentManager.Instance.jumpToDocument(doc, false, undefined);
+ DocumentManager.Instance.jumpToDocument(doc, false, undefined, []);
batch.end();
});
}
@@ -275,9 +265,13 @@ export class TabDocView extends React.Component<TabDocViewProps> {
// ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
// { fireImmediately: true });
}
+ componentDidUpdate() {
+ this._view && DocumentManager.Instance.AddView(this._view);
+ }
componentWillUnmount() {
this._tabReaction?.();
+ this._view && DocumentManager.Instance.RemoveView(this._view);
this.tab && CollectionDockingView.Instance.tabMap.delete(this.tab);
this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
@@ -307,7 +301,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
- case "lightbox": return LightboxView.AddDocTab(doc, location);
+ // case "lightbox": {
+ // // TabDocView.PinDoc(doc, { hidePresBox: true });
+ // return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab);
+ // }
+ case "lightbox": return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab);
case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack);
case "inPlace":
case "add":
@@ -346,7 +344,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
}
}
active = () => this._isActive;
+ @observable _forceInvalidateScreenToLocal = 0;
ScreenToLocalTransform = () => {
+ this._forceInvalidateScreenToLocal;
const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY);
}
@@ -357,19 +357,22 @@ export class TabDocView extends React.Component<TabDocViewProps> {
disableMinimap = () => !this._document || (this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._viewType !== CollectionViewType.Freeform);
hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.hideMinimap);
- @computed get layerProvider() { return this._document && DefaultLayerProvider(this._document); }
@computed get docView() {
return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? (null) :
- <><DocumentView key={this._document[Id]} ref={action((r: DocumentView) => this._view = r)}
+ <><DocumentView key={this._document[Id]} ref={action((r: DocumentView) => {
+ this._lastView && DocumentManager.Instance.RemoveView(this._lastView);
+ this._view = r;
+ this._lastView = this._view;
+ })}
renderDepth={0}
Document={this._document}
DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
+ onBrowseClick={MainView.Instance.exploreMode}
isContentActive={returnTrue}
PanelWidth={this.PanelWidth}
PanelHeight={this.PanelHeight}
- layerProvider={this.layerProvider}
styleProvider={DefaultStyleProvider}
docFilters={CollectionDockingView.Instance.childDocFilters}
docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters}
@@ -416,8 +419,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
height: "100%", width: "100%"
}} ref={ref => {
if (this._mainCont = ref) {
+ if (this._lastTab) {
+ console.log("DUP tab")
+ this._view && DocumentManager.Instance.RemoveView(this._view);
+ CollectionDockingView.Instance.tabMap.delete(this._lastTab);
+ }
+ this._lastTab = this.tab;
(this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
+ new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref);
}
}} >
{this.docView}
@@ -520,7 +530,6 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
whenChildContentsActiveChanged={emptyFunction}
focus={DocUtils.DefaultFocus}
styleProvider={TabMinimapView.miniStyleProvider}
- layerProvider={undefined}
addDocTab={this.props.addDocTab}
pinToPres={TabDocView.PinDoc}
docFilters={CollectionDockingView.Instance.childDocFilters}
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index b91737a1d..f587dbbf6 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -21,7 +21,7 @@
}
.treeView-bulletIcons {
- // width: $TREE_BULLET_WIDTH;
+ // width: $TREE_BULLET_WIDTH;
width: 100%;
height: 100%;
@@ -39,16 +39,17 @@
&:hover {
.treeView-expandIcon {
- display: unset;
+ display: unset;
}
}
}
+
.treeView-bulletIcons:hover img {
left: 14px;
position: absolute;
transform-origin: center left;
transform: scale(6);
- pointer-events:none;
+ pointer-events: none;
}
.bullet {
@@ -56,7 +57,7 @@
width: $TREE_BULLET_WIDTH;
color: $medium-gray;
margin-top: 3px;
- // transform: scale(1.3, 1.3); // bcz: why was this here? It makes displaying images in the treeView for documents that have icons harder.
+ // transform: scale(1.3, 1.3); // bcz: why was this here? It makes displaying images in the treeView for documents that have icons harder.
border: #80808030 1px solid;
border-radius: 4px;
}
@@ -70,11 +71,12 @@
border-radius: 4px;
font-size: 10px;
}
+
.treeView-container-active {
cursor: default;
}
-.treeView-container-outline-active
-.treeView-container-active {
+
+.treeView-container-outline-active .treeView-container-active {
z-index: 100;
position: relative;
pointer-events: all;
@@ -139,6 +141,7 @@
align-items: center;
margin-left: 0.25rem;
opacity: 0.75;
+ pointer-events: all;
cursor: pointer;
>svg {
@@ -173,6 +176,7 @@
}
.treeView-rightButtons {
+
>svg,
.styleProvider-treeView-icon {
display: inherit;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 647476784..23b6a7f72 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,6 +1,6 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
@@ -14,7 +14,7 @@ import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { DocumentManager } from '../../util/DocumentManager';
+import { DocumentManager, DocFocusOrOpen } from '../../util/DocumentManager';
import { DragManager, dropActionType } from "../../util/DragManager";
import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -26,11 +26,12 @@ import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFun
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import { StyleProp } from '../StyleProvider';
-import { CollectionTreeView } from './CollectionTreeView';
+import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
import { CollectionView, CollectionViewType } from './CollectionView';
import "./TreeView.scss";
import React = require("react");
import { KeyValueBox } from '../nodes/KeyValueBox';
+import { FieldViewProps } from '../nodes/FieldView';
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -63,11 +64,20 @@ export interface TreeViewProps {
onChildClick?: () => ScriptField;
skipFields?: string[];
firstLevel: boolean;
+ // TODO: [AL] add these
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+ hierarchyIndex?: number[];
}
const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("px", "")); };
-@observer
+export enum TreeSort {
+ Up = "up",
+ Down = "down",
+ Zindex = "z",
+ None = "none"
+}
/**
* Renders a treeView of a collection of documents
*
@@ -75,11 +85,11 @@ const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("p
* treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden
* treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
*/
+@observer
export class TreeView extends React.Component<TreeViewProps> {
static _editTitleOnLoad: Opt<{ id: string, parent: TreeView | CollectionTreeView | undefined }>;
static _openTitleScript: Opt<ScriptField | undefined>;
static _openLevelScript: Opt<ScriptField | undefined>;
- static _sortUIMap = [["up", "crimson", "↑"], ["down", "blue", "↓"], ["z", "green", "z"], ["x", "darkgray", '\u00A0\u00A0\u00A0']];
private _header: React.RefObject<HTMLDivElement> = React.createRef();
private _tref = React.createRef<HTMLDivElement>();
@observable _docRef: Opt<DocumentView>;
@@ -103,7 +113,7 @@ export class TreeView extends React.Component<TreeViewProps> {
get defaultExpandedView() {
return this.doc.viewType === CollectionViewType.Docking ? this.fieldKey :
this.props.treeView.dashboardMode ? this.fieldKey :
- this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") :
+ this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") : // for displaying
this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.UserDoc().noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields");
}
@@ -121,6 +131,16 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get selected() { return SelectionManager.IsSelected(this._docRef); }
// SelectionManager.Views().lastElement()?.props.Document === this.props.document; }
+ @observable _presTimer!: NodeJS.Timeout;
+ @observable _presKeyEventsActive: boolean = false;
+
+ @observable _selectedArray: ObservableMap = new ObservableMap<Doc, any>();
+ // the selected item's index
+ @computed get itemIndex() { return NumCast(this.doc._itemIndex); }
+ // the item that's active
+ @computed get activeItem() { return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined; }
+ @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); }
+
childDocList(field: string) {
const layout = Cast(Doc.LayoutField(this.doc), Doc, null);
return (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
@@ -163,7 +183,7 @@ export class TreeView extends React.Component<TreeViewProps> {
this.treeViewOpen = !this.treeViewOpen;
} else {
// choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias
- const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
+ const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail);
this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), "lightbox");
}
@@ -192,6 +212,13 @@ export class TreeView extends React.Component<TreeViewProps> {
this._treeEle && this.props.unobserveHeight(this._treeEle);
document.removeEventListener("pointermove", this.onDragMove, true);
document.removeEventListener("pointermove", this.onDragUp, true);
+ // TODO: [AL] add these
+ this.props.hierarchyIndex !== undefined && this.props.RemFromMap?.(this.doc, this.props.hierarchyIndex);
+ }
+
+ componentDidMount() {
+ // TODO: [AL] add these
+ this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
}
onDragUp = (e: PointerEvent) => {
@@ -236,7 +263,7 @@ export class TreeView extends React.Component<TreeViewProps> {
layout: CollectionView.LayoutString("data"),
title: "-title-",
treeViewExpandedViewLock: true, treeViewExpandedView: "data",
- _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline",
+ _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: TreeViewType.outline,
x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _width: 1000, _height: 10
});
Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
@@ -266,7 +293,8 @@ export class TreeView extends React.Component<TreeViewProps> {
@undoBatch
treeDrop = (e: Event, de: DragManager.DropEvent) => {
const pt = [de.x, de.y];
- const rect = this._header.current!.getBoundingClientRect();
+ if (!this._header.current) return;
+ const rect = this._header.current.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length);
if (de.complete.linkDragData) {
@@ -358,7 +386,12 @@ export class TreeView extends React.Component<TreeViewProps> {
this.props.dropAction, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive,
this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields,
[...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged,
- this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems());
+ this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(),
+ // TODO: [AL] Add these
+ this.props.AddToMap,
+ this.props.RemFromMap,
+ this.props.hierarchyIndex
+ );
} else {
contentElement = <EditableView key="editableView"
contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
@@ -410,17 +443,18 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get renderContent() {
TraceMobx();
const expandKey = this.treeViewExpandedView;
+ const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string, label: string } };
if (["links", "annotations", "aliases", this.fieldKey].includes(expandKey)) {
- const sorting = StrCast(this.doc.treeViewSortCriterion);
- const color = (TreeView._sortUIMap.find((sortUIfields) => sortUIfields[0] === sorting) ?? TreeView._sortUIMap.lastElement())[1];
- const label = (TreeView._sortUIMap.find((sortUIfields) => sortUIfields[0] === sorting) ?? TreeView._sortUIMap.lastElement())[2];
+ const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
+ const sortKeys = Object.keys(sortings);
+ const curSortIndex = Math.max(0, sortKeys.findIndex(val => val === sorting));
const key = (expandKey === "annotations" ? `${this.fieldKey}-` : "") + expandKey;
const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
// if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't),
// then the modification would be done here
const ordering = StrCast(this.doc.treeViewSortCriterion);
- if (ordering === "z") {
+ if (ordering === TreeSort.Zindex) {
const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering);
doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000;
docs.push(doc);
@@ -433,17 +467,15 @@ export class TreeView extends React.Component<TreeViewProps> {
const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
const docs = expandKey === "aliases" ? this.childAliases : expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs;
let downX = 0, downY = 0;
- const sortings = this.props.treeView.rootDoc === Doc.UserDoc().myFilesystem ? ["up", "down", "x"] : ["up", "down", "z", "x"];
- const curSort = Math.max(0, sortings.indexOf(Cast(this.doc.treeViewSortCriterion, "string", null)));
return <>
- {!docs?.length ? (null) : <div className={'treeView-sorting'} style={{ background: color }} >
- {label}
+ {!docs?.length ? (null) : <div className={'treeView-sorting'} style={{ background: sortings[sorting]?.color }} >
+ {sortings[sorting]?.label}
</div>}
<ul key={expandKey + "more"} title="click to change sort order" className={this.doc.treeViewHideTitle ? "no-indent" : ""}
onPointerDown={e => { downX = e.clientX; downY = e.clientY; e.stopPropagation(); }}
onClick={(e) => {
if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
- !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortings[(curSort + 1) % sortings.length]);
+ !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
e.stopPropagation();
}
}}>
@@ -453,7 +485,11 @@ export class TreeView extends React.Component<TreeViewProps> {
StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform,
this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields,
[...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged,
- this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems())}
+ this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(),
+ // TODO: [AL] add these
+ this.props.AddToMap,
+ this.props.RemFromMap,
+ this.props.hierarchyIndex)}
</ul >
</>;
} else if (this.treeViewExpandedView === "fields") {
@@ -463,7 +499,7 @@ export class TreeView extends React.Component<TreeViewProps> {
</div>
</ul>;
}
- return <ul onPointerDown={e => { e.preventDefault(); e.stopPropagation(); }}>{this.renderEmbeddedDocument(false, returnFalse)}</ul>; // "layout"
+ return <ul onPointerDown={e => { e.preventDefault(); e.stopPropagation(); }}>{this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)}</ul>; // "layout"
}
get onCheckedClick() { return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); }
@@ -490,10 +526,15 @@ export class TreeView extends React.Component<TreeViewProps> {
return <div className={`bullet${this.props.treeView.outlineMode ? "-outline" : ""}`} key={"bullet"}
title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"}
onClick={this.bulletClick}
- style={this.props.treeView.outlineMode ? { opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) } : {
- color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"),
- opacity: checked === "unchecked" || typeof iconType !== "string" ? undefined : 0.4
- }}>
+ style={this.props.treeView.outlineMode ?
+ {
+ opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity)
+ } :
+ {
+ pointerEvents: this.props.isContentActive() ? "all" : undefined,
+ opacity: checked === "unchecked" || typeof iconType !== "string" ? undefined : 0.4,
+ color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"),
+ }}>
{this.props.treeView.outlineMode ?
!(this.doc.text as RichTextField)?.Text ? (null) :
<FontAwesomeIcon size="sm" icon={[this.childDocs?.length && !this.treeViewOpen ? "fas" : "far", "circle"]} /> :
@@ -564,7 +605,12 @@ export class TreeView extends React.Component<TreeViewProps> {
const icons = StrListCast(this.doc.childContextMenuIcons);
return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
}
- onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
+
+ // TODO: currently doc focus works, but can't seem to edit title
+ // onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
+ onChildClick = () => {
+ return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.());
+ }
onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
@@ -595,24 +641,29 @@ export class TreeView extends React.Component<TreeViewProps> {
if (property.startsWith(StyleProp.Decorations)) return (null);
return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
}
- onKeyDown = (e: React.KeyboardEvent) => {
+ onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
if (this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) {
switch (e.key) {
case "Tab":
- e.stopPropagation();
- e.preventDefault();
+ e.stopPropagation?.();
+ e.preventDefault?.();
setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
- return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab");
+ UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab");
+ return true;
case "Backspace":
- e.stopPropagation();
- e.preventDefault();
- return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc);
+ if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) {
+ e.stopPropagation?.();
+ e.preventDefault?.();
+ return true;
+ }
+ break;
case "Enter":
- e.stopPropagation();
- e.preventDefault();
+ e.stopPropagation?.();
+ e.preventDefault?.();
return UndoManager.RunInBatch(this.makeTextCollection, "bullet");
}
}
+ return false;
}
titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth()));
@@ -659,7 +710,6 @@ export class TreeView extends React.Component<TreeViewProps> {
hideDecorationTitle={this.props.treeView.outlineMode}
hideResizeHandles={this.props.treeView.outlineMode}
styleProvider={this.titleStyleProvider}
- layerProvider={returnTrue}
docViewPath={returnEmptyDoclist}
treeViewDoc={this.props.treeView.props.Document}
addDocument={undefined}
@@ -748,15 +798,16 @@ export class TreeView extends React.Component<TreeViewProps> {
fitContentsToDoc={returnTrue}
hideDecorationTitle={this.props.treeView.outlineMode}
hideResizeHandles={this.props.treeView.outlineMode}
+ onClick={this.onChildClick}
focus={this.refocus}
ContentScaling={returnOne}
+ onKey={this.onKeyDown}
hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
ScreenToLocalTransform={this.docTransform}
renderDepth={this.props.renderDepth + 1}
treeViewDoc={this.props.treeView?.props.Document}
rootSelected={returnTrue}
- layerProvider={returnTrue}
docViewPath={this.props.treeView.props.docViewPath}
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
@@ -791,9 +842,9 @@ export class TreeView extends React.Component<TreeViewProps> {
}
@computed get renderBorder() {
- const sorting = StrCast(this.doc.treeViewSortCriterion);
- const color = (TreeView._sortUIMap.find((sortUIfields) => sortUIfields[0] === sorting) ?? TreeView._sortUIMap.lastElement())[1];
- return <div className={`treeView-border${this.props.treeView.outlineMode ? "outline" : ""}`} style={{ borderColor: color }}>
+ const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
+ const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string, label: string } };
+ return <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ""}`} style={{ borderColor: sortings[sorting]?.color }}>
{!this.treeViewOpen ? (null) : this.renderContent}
</div>;
}
@@ -815,8 +866,9 @@ export class TreeView extends React.Component<TreeViewProps> {
<div className={`treeView-container${this.props.isContentActive() ? "-active" : ""}`}
ref={this.createTreeDropTarget}
onDrop={this.onTreeDrop}
- //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
- onKeyDown={this.onKeyDown}>
+ //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
+ // onKeyDown={this.onKeyDown}
+ >
<li className="collection-child">
{hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader ? // should test for prop 'treeViewRenderDocWithBulletAsHeader"
this.renderEmbeddedDocument(false, returnFalse) :
@@ -827,11 +879,11 @@ export class TreeView extends React.Component<TreeViewProps> {
public static sortDocs(childDocs: Doc[], criterion: string | undefined) {
const docs = childDocs.slice();
- if (criterion) {
+ if (criterion !== TreeSort.None) {
const sortAlphaNum = (a: string, b: string): 0 | 1 | -1 => {
const reN = /[0-9]*$/;
- const aA = a.replace(reN, ""); // get rid of trailing numbers
- const bA = b.replace(reN, "");
+ const aA = a.replace(reN, "") ? a.replace(reN, "") : +a; // get rid of trailing numbers
+ const bA = b.replace(reN, "") ? b.replace(reN, "") : +b;
if (aA === bA) { // if header string matches, then compare numbers numerically
const aN = parseInt(a.match(reN)![0], 10);
const bN = parseInt(b.match(reN)![0], 10);
@@ -841,10 +893,10 @@ export class TreeView extends React.Component<TreeViewProps> {
}
};
docs.sort(function (d1, d2): 0 | 1 | -1 {
- const a = (criterion === "up" ? d2 : d1);
- const b = (criterion === "down" ? d1 : d2);
- const first = a[criterion === "z" ? "zIndex" : "title"];
- const second = b[criterion === "z" ? "zIndex" : "title"];
+ const a = (criterion === TreeSort.Up ? d2 : d1);
+ const b = (criterion === TreeSort.Up ? d1 : d2);
+ const first = a[criterion === TreeSort.Zindex ? "zIndex" : "title"];
+ const second = b[criterion === TreeSort.Zindex ? "zIndex" : "title"];
if (typeof first === 'number' && typeof second === 'number') return (first - second) > 0 ? 1 : -1;
if (typeof first === 'string' && typeof second === 'string') return sortAlphaNum(first, second);
return criterion ? 1 : -1;
@@ -881,14 +933,18 @@ export class TreeView extends React.Component<TreeViewProps> {
dontRegisterView: boolean | undefined,
observerHeight: (ref: any) => void,
unobserveHeight: (ref: any) => void,
- contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[])
+ contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[]),
+ // TODO: [AL] add these
+ AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
+ RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
+ hierarchyIndex?: number[],
) {
const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
}
- const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion));
+ const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None));
const rowWidth = () => panelWidth() - treeBulletWidth();
const treeViewRefs = new Map<Doc, TreeView | undefined>();
return docs.filter(child => child instanceof Doc).map((child, i) => {
@@ -922,6 +978,10 @@ export class TreeView extends React.Component<TreeViewProps> {
dataDoc={pair.data}
containerCollection={containerCollection}
prevSibling={docs[i]}
+ // TODO: [AL] add these
+ hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
+ AddToMap={AddToMap}
+ RemFromMap={RemFromMap}
treeView={treeView}
indentDocument={indent}
outdentDocument={outdent}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 16c7df311..9de2cfcf9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -147,7 +147,7 @@ export function computePivotLayout(
const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
let nonNumbers = 0;
- const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField);
+ const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || "author";
childPairs.map(pair => {
const lval = pivotFieldKey === "#" || pivotFieldKey === "tags" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f5a5492e3..5f890c810 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { Doc, Field } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
import { List } from "../../../../fields/List";
-import { NumCast } from "../../../../fields/Types";
+import { Cast, NumCast } from "../../../../fields/Types";
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
import { LinkManager } from "../../../util/LinkManager";
import { SelectionManager } from "../../../util/SelectionManager";
@@ -30,7 +30,14 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
componentWillUnmount() { this._anchorDisposer?.(); }
@action timeout = action(() => (Date.now() < this._start++ + 1000) && (this._timeout = setTimeout(this.timeout, 25)));
componentDidMount() {
- this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform()],
+ this._anchorDisposer = reaction(() => [
+ this.props.A.props.ScreenToLocalTransform(),
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?.scrollTop,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?._highlights,
+ this.props.B.props.ScreenToLocalTransform(),
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?.scrollTop,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?._highlights,
+ ],
action(() => {
this._start = Date.now();
this._timeout && clearTimeout(this._timeout);
@@ -45,14 +52,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return;
setTimeout(action(() => this._opacity = 0.75), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
- const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- const adiv = acont.length ? acont[0] : A.ContentDiv;
- const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
- const a = adiv.getBoundingClientRect();
- const b = bdiv.getBoundingClientRect();
- const { left: aleft, top: atop, width: awidth, height: aheight } = adiv.parentElement!.getBoundingClientRect();
- const { left: bleft, top: btop, width: bwidth, height: bheight } = bdiv.parentElement!.getBoundingClientRect();
+ const a = A.ContentDiv.getBoundingClientRect();
+ const b = B.ContentDiv.getBoundingClientRect();
+ const { left: aleft, top: atop, width: awidth, height: aheight } = A.ContentDiv.parentElement!.getBoundingClientRect();
+ const { left: bleft, top: btop, width: bwidth, height: bheight } = B.ContentDiv.parentElement!.getBoundingClientRect();
const apt = Utils.closestPtBetweenRectangles(aleft, atop, awidth, aheight, bleft, btop, bwidth, bheight, a.left + a.width / 2, a.top + a.height / 2);
const bpt = Utils.closestPtBetweenRectangles(bleft, btop, bwidth, bheight, aleft, atop, awidth, aheight, apt.point.x, apt.point.y);
@@ -75,6 +78,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const mpy = mp[1] / A.props.PanelHeight();
if (mpx >= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100;
if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100;
+ if (getComputedStyle(targetAhyperlink).fontSize === "0px") linkDoc.opacity = 0;
+ else linkDoc.opacity = 1;
}
if (!targetBhyperlink) {
if (linkDoc.linkAutoMove) {
@@ -88,6 +93,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const mpy = mp[1] / B.props.PanelHeight();
if (mpx >= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100;
if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100;
+ if (getComputedStyle(targetBhyperlink).fontSize === "0px") linkDoc.opacity = 0;
+ else linkDoc.opacity = 1;
}
}
@@ -154,25 +161,6 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
this.toggleProperties();
}
- // componentToHex = (c: number) => {
- // let hex = c.toString(16);
- // return hex.length == 1 ? "0" + hex : hex;
- // }
-
- // rgbToHex = (rgbString: string) => {
- // if (rgbString != "black") {
- // const splitString = rgbString.split(/rgb|\(|\)|,| /)
- // let values: number[] = []
- // splitString.forEach(elt => {
- // if (elt) {
- // values.push(parseInt(elt))
- // }
- // })
- // return "#" + this.componentToHex(values[0]) + this.componentToHex(values[1]) + this.componentToHex(values[2]);
- // }
- // return "#000000"
- // }
-
@computed.struct get renderData() {
this._start; SnappingManager.GetIsDragging();
const { A, B, LinkDocs } = this.props;
@@ -190,32 +178,18 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
if (!a.width || !b.width) return undefined;
const aDocBounds = (A.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
const bDocBounds = (B.props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
- const acentX = (a.left + a.right) / 2;
- const acentY = (a.top + a.bottom) / 2;
- const bcentX = (b.left + b.right) / 2;
- const bcentY = (b.top + b.bottom) / 2;
- const pt1Arc = ((acentX - aDocBounds.left) > 0.1 && (aDocBounds.right - acentX) > 0.1) ||
- ((acentY - aDocBounds.top) > 0.1 && (aDocBounds.bottom - acentY) > 0.1);
- const pt2Arc = ((bcentX - bDocBounds.left) > 0.1 && (bDocBounds.right - bcentX) > 0.1) ||
- ((bcentY - bDocBounds.top) > 0.1 && (bDocBounds.bottom - bcentY) > 0.1);
- const atop2 = this.visibleY(adiv);
- const btop2 = this.visibleY(bdiv);
const aleft = this.visibleX(adiv);
const bleft = this.visibleX(bdiv);
const clipped = aleft !== a.left || atop !== a.top || bleft !== b.left || btop !== b.top;
const pt1 = [aleft + a.width / 2, atop + a.height / 2];
const pt2 = [bleft + b.width / 2, btop + b.width / 2];
- const pt1vec = [pt1[0] - (aDocBounds.left + aDocBounds.right) / 2, pt1[1] - (aDocBounds.top + aDocBounds.bottom) / 2];
- const pt2vec = [pt2[0] - (bDocBounds.left + bDocBounds.right) / 2, pt2[1] - (bDocBounds.top + bDocBounds.bottom) / 2];
+ const pt1vec = [(bDocBounds.left + bDocBounds.right) / 2 - pt1[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt1[1]];
+ const pt2vec = [(aDocBounds.left + aDocBounds.right) / 2 - pt2[0], (aDocBounds.top + aDocBounds.bottom) / 2 - pt2[1]];
const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1]));
const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1]));
const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
- const pt1norm = clipped ? [0, 0] : !pt1Arc ? [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen] :
- Math.abs(acentY - aDocBounds.top) < 0.01 ||
- Math.abs(acentY - aDocBounds.bottom) < 0.01 ? [0, (pt2[1] - pt1[1]) / 2] : [(pt2[0] - pt1[0]) / 2, 0];
- const pt2norm = clipped ? [0, 0] : !pt2Arc ? [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen] :
- Math.abs(bcentY - bDocBounds.top) < 0.01 ||
- Math.abs(bcentY - bDocBounds.bottom) < 0.01 ? [0, (pt1[1] - pt2[1]) / 2] : [(pt1[0] - pt2[0]) / 2, 0];
+ const pt1norm = clipped ? [0, 0] : [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
+ const pt2norm = clipped ? [0, 0] : [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1;
const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
@@ -253,7 +227,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
this.props.LinkDocs[0].displayArrow = false;
}
- return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
+ return this.props.LinkDocs[0].opacity === 0 || !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
<defs>
<marker id="arrowhead" markerWidth="4" markerHeight="3"
refX="0" refY="1.5" orient="auto">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5d1e1892c..12ad6b02b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -3,16 +3,16 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { DateField } from "../../../../fields/DateField";
-import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { ObjectField } from "../../../../fields/ObjectField";
import { RichTextField } from "../../../../fields/RichTextField";
-import { ImageField } from "../../../../fields/URLField";
import { createSchema, listSpec } from "../../../../fields/Schema";
import { ScriptField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
+import { ImageField } from "../../../../fields/URLField";
import { TraceMobx } from "../../../../fields/util";
import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils";
@@ -26,6 +26,7 @@ import { DragManager, dropActionType } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
import { InteractionUtils } from "../../../util/InteractionUtils";
import { LinkManager } from "../../../util/LinkManager";
+import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
import { SearchUtil } from "../../../util/SearchUtil";
import { SelectionManager } from "../../../util/SelectionManager";
import { ColorScheme } from "../../../util/SettingsManager";
@@ -44,15 +45,17 @@ import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { PresBox } from "../../nodes/trails/PresBox";
import { VideoBox } from "../../nodes/VideoBox";
import { CreateImage } from "../../nodes/WebBoxRenderer";
-import { StyleLayers, StyleProp } from "../../StyleProvider";
+import { StyleProp } from "../../StyleProvider";
import { CollectionDockingView } from "../CollectionDockingView";
import { CollectionSubView } from "../CollectionSubView";
+import { TreeViewType } from "../CollectionTreeView";
import { CollectionViewType } from "../CollectionView";
import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
+import { FieldView, FieldViewProps } from "../../nodes/FieldView";
export const panZoomSchema = createSchema({
_panX: "number",
@@ -75,6 +78,7 @@ export type collectionFreeformViewProps = {
scaleField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
+ dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
// However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
};
@@ -101,6 +105,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _lastTap = 0;
private _batch: UndoManager.Batch | undefined = undefined;
+ // private isWritingMode: boolean = true;
+ // private writingModeDocs: Doc[] = [];
+
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
@@ -117,12 +124,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeRef = React.createRef<HTMLDivElement>();
+ @observable _marqueeViewRef = React.createRef<MarqueeView>();
@observable _keyframeEditing = false;
@observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
@computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
- @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); }
- @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && this.props.isContentActive(); }
@computed get fitToContentVals() {
return {
bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
@@ -155,6 +161,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set;
getKeyFrameEditing = () => this._keyframeEditing;
+ onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
@@ -221,7 +228,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
- if (!de.embedKey && !this.ChildDrag && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
+ if (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false;
if (!super.onInternalDrop(e, de)) return false;
const refDoc = docDragData.droppedDocuments[0];
const [xpo, ypo] = this.getContainerTransform().transformPoint(de.x, de.y);
@@ -250,7 +257,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
layoutDoc._width = NumCast(layoutDoc._width, 300);
layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300);
- !StrListCast(d._layerTags).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+ (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
(docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
@@ -417,8 +424,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
styleProp = colors[cluster % colors.length];
const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
// override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
- set?.filter(s => !StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
- set?.filter(s => StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
+ set?.map(s => styleProp = StrCast(s.backgroundColor));
}
} //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray";
return styleProp;
@@ -435,6 +441,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@action
+ onPenUp = (e: PointerEvent): void => {
+ if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
+ document.removeEventListener("pointerup", this.onPenUp);
+ const currentCol = DocListCast(this.rootDoc.currentInkDoc)
+ const rootDocList = DocListCast(this.rootDoc.data);
+ currentCol.push(rootDocList[rootDocList.length - 1]);
+ console.log(currentCol);
+
+ this._batch?.end();
+ }
+ }
+
+ @action
onPointerDown = (e: React.PointerEvent): void => {
this._downX = this._lastX = e.pageX;
this._downY = this._lastY = e.pageY;
@@ -445,7 +464,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
switch (CurrentUserUtils.SelectedTool) {
case InkTool.Highlighter:
- case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
+ break;
+ // TODO: nda - this where we want to create the new "writingDoc" collection that we add strokes to
+ case InkTool.Write:
+ break;
+ case InkTool.Pen:
+ break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
case InkTool.Eraser:
document.addEventListener("pointermove", this.onEraserMove);
document.addEventListener("pointerup", this.onEraserUp);
@@ -491,6 +515,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
+ public unprocessedDocs: Doc[] = [];
+ public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>();
@undoBatch
onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
switch (ge.gesture) {
@@ -499,6 +525,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
{ title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() });
+ if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+ this.unprocessedDocs.push(inkDoc);
+ CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
+ }
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -623,7 +653,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
onClick = (e: React.MouseEvent) => {
- if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
+ if (this.onBrowseClickHandler()) {
+ if (this.props.DocumentView?.()) {
+ this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY });
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ else if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
if (e.shiftKey) {
if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a shift + double click
this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
@@ -678,6 +715,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onPointerMove = (e: PointerEvent): void => {
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return;
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
+ Doc.UserDoc().activeInkTool = InkTool.None;
if (this.props.isContentActive(true)) e.stopPropagation();
} else if (!e.cancelBubble) {
if (this.tryDragCluster(e, this._hitCluster)) {
@@ -808,6 +846,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
document.removeEventListener("pointerup", this.onPointerUp);
return;
}
+ // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected'
this.pan(myTouches[0]);
}
}
@@ -950,7 +989,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.layoutDoc._lockedTransform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === "outline") return;
+ if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
e.stopPropagation();
}
@@ -1016,7 +1055,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
bringToFront = (doc: Doc, sendToBack?: boolean) => {
- if (sendToBack || StrListCast(doc._layerTags).includes(StyleLayers.Background)) {
+ if (sendToBack) {
doc.zIndex = 0;
} else if (doc.isInkMask) {
doc.zIndex = 5000;
@@ -1068,21 +1107,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.props.focus(doc, options);
} else {
const xfToCollection = options?.docTransform ?? Transform.Identity();
- const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey] };
+ const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoom ? this.Document[this.scaleFieldKey] : undefined };
const newState = HistoryUtil.getState();
- const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+ const cantTransform = /*this.props.isAnnotationOverlay ||*/ ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
if (!cantTransform) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
HistoryUtil.pushState(newState);
}
// focus on the document in the collection
- const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== undefined);
+ const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
const focusSpeed = options?.instant ? 0 : didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
// glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
if (didMove) {
- this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
scale && (this.Document[this.scaleFieldKey] = scale);
+ this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
}
const startTime = Date.now();
@@ -1119,23 +1158,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
const layoutdoc = Doc.Layout(doc);
- const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
- const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1);
const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
- const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] };
- const cx = NumCast(this.layoutDoc._panX);
- const cy = NumCast(this.layoutDoc._panY);
- const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
+ const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
if (scale) {
- const maxZoom = 2; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
+ const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
+ const newScale = Math.min(maxZoom, 1 / (this.contentScaling || 1) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
return {
- panX: (bounds.left + bounds.right) / 2,
- panY: (bounds.top + bounds.bot) / 2,
- scale: Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])))
+ panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
+ panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
+ scale: newScale
};
}
+ const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
+ const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1);
+ const cx = NumCast(this.layoutDoc._panX);
+ const cy = NumCast(this.layoutDoc._panY);
+ const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
if ((screen.right - screen.left) < (bounds.right - bounds.left) ||
(screen.bot - screen.top) < (bounds.bot - bounds.top)) {
return {
@@ -1152,10 +1192,37 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ @undoBatch
+ @action
+ onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+ const docView = fieldProps.DocumentView?.();
+ if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ["Tab", "Enter"].includes(e.key)) {
+ e.stopPropagation?.();
+ const below = !e.altKey && e.key !== "Tab";
+ const layoutKey = StrCast(docView.LayoutFieldKey);
+ const newDoc = Doc.MakeCopy(docView.rootDoc, true);
+ const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
+ newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+ if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10;
+ else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10;
+ if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) {
+ newDoc[layoutKey] = docView.rootDoc[layoutKey];
+ }
+ Doc.GetProto(newDoc).text = undefined;
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ return this.addDocument?.(newDoc);
+ }
+ }
+ pointerEvents = () => {
+ const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ const pointerEvents = this.props.isContentActive() === false ? "none" :
+ this.props.childPointerEvents ? "all" :
+ (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents?.();
+ return pointerEvents;
+ }
getChildDocView(entry: PoolData) {
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
- const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
return <CollectionFreeFormDocumentView key={childLayout[Id] + (entry.replica || "")}
DataDoc={childData}
Document={childLayout}
@@ -1171,15 +1238,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
rootSelected={childData ? this.rootSelected : returnFalse}
onClick={this.onChildClickHandler}
+ onKey={this.onKeyDown}
onDoubleClick={this.onChildDoubleClickHandler}
+ onBrowseClick={this.onBrowseClickHandler}
ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
PanelWidth={childLayout[WidthSym]}
PanelHeight={childLayout[HeightSym]}
docFilters={this.childDocFilters}
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
isContentActive={emptyFunction}
- isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
focus={this.focusDocument}
addDocTab={this.addDocTab}
addDocument={this.props.addDocument}
@@ -1189,16 +1258,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
docViewPath={this.props.docViewPath}
styleProvider={this.getClusterColor}
- layerProvider={this.props.layerProvider}
dataProvider={this.childDataProvider}
sizeProvider={this.childSizeProvider}
- freezeDimensions={this.props.childFreezeDimensions}
+ freezeDimensions={BoolCast(this.props.Document.childFreezeDimensions, this.props.childFreezeDimensions)}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
bringToFront={this.bringToFront}
showTitle={this.props.childShowTitle}
+ dontScaleFilter={this.props.dontScaleFilter}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
- pointerEvents={this.props.isContentActive() === false ? "none" : this.backgroundActive || this.props.childPointerEvents ? "all" :
- (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents}
+ pointerEvents={this.pointerEvents}
jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
//fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
/>;
@@ -1423,7 +1491,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
- if (oldDiv.childNodes) {
+ if (oldDiv.childNodes && newDiv) {
for (let i = 0; i < oldDiv.childNodes.length; i++) {
this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
}
@@ -1541,12 +1609,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
this.props.ContainingCollectionView &&
appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" });
+
+ this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: "Ink to text", event: () => this.transcribeStrokes(false), icon: "font" });
+
+ // this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" });
+
!Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
!appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
const viewctrls = ContextMenu.Instance.findByDescription("UI Controls...");
const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : [];
-
!Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null;
!Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null;
!viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" });
@@ -1558,7 +1630,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
if (!Doc.UserDoc().noviceMode) {
optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
- optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
}
!options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
const mores = ContextMenu.Instance.findByDescription("More...");
@@ -1600,6 +1671,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
input.click();
}
+ @undoBatch
+ @action
+ transcribeStrokes = (math: boolean) => {
+ if (this.props.Document._isGroup && this.props.Document.transcription) {
+ if (!math) {
+ const text = StrCast(this.props.Document.transcription);
+
+ const lines = text.split("\n");
+ const height = 30 + 15 * lines.length;
+
+ this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
+ }
+ }
+ }
+
@action
setupDragLines = (snapToDraggedDoc: boolean = false) => {
const activeDocs = this.getActiveDocuments();
@@ -1609,7 +1695,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => intersectRect(docDims(doc), rect);
const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) };
- let snappableDocs = activeDocs.filter(doc => !StrListCast(doc._layerTags).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
+ let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
!snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to
!snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs
@@ -1662,6 +1748,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
TraceMobx();
return <MarqueeView
{...this.props}
+ ref={this._marqueeViewRef}
ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined}
nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
addDocTab={this.addDocTab}
@@ -1734,12 +1821,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onContextMenu={this.onContextMenu}
style={{
pointerEvents: this.props.Document.type === DocumentType.MARKER ? "none" : // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
- this.backgroundEvents ? "all" : this.props.pointerEvents as any,
+ (SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())) ? "all" : this.props.pointerEvents?.() as any,
transform: `scale(${this.contentScaling || 1})`,
width: `${100 / (this.contentScaling || 1)}%`,
height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
}}>
- {this._firstRender || (this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0) ?
+ {this._firstRender ?
this.placeholder : this.marqueeView}
{this.props.noOverlay ? (null) : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
@@ -1762,7 +1849,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
</svg>
</div>}
- {this.props.Document._isGroup && SnappingManager.GetIsDragging() && (this.ChildDrag || this.props.layerProvider?.(this.props.Document) === false) ?
+ {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ?
<div className="collectionFreeForm-groupDropper" ref={this.createGroupEventsTarget} style={{
width: this.ChildDrag ? "10000" : "100%",
height: this.ChildDrag ? "10000" : "100%",
@@ -1971,4 +2058,21 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
}
}} />;
}
-} \ No newline at end of file
+}
+
+export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
+ SelectionManager.DeselectAll();
+ dv.props.focus(dv.props.Document, {
+ willZoom: true, afterFocus: async (didMove) => {
+ if (!didMove) {
+ const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
+ const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || "_viewScale"] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
+ ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5);
+ }
+ return ViewAdjustment.doNothing;
+ }
+ });
+ Doc.linkFollowHighlight(dv?.props.Document, false);
+}
+ScriptingGlobals.add(CollectionBrowseClick); \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 1f59f9732..8a8b528f6 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -14,7 +14,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction;
public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
- public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public showMarquee: () => void = unimplementedFunction;
public hideMarquee: () => void = unimplementedFunction;
public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
@@ -64,16 +63,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
</button>
</Tooltip>,
];
- if (false && !SelectionManager.Views().some(v => v.props.Document.type !== DocumentType.INK)) {
- buttons.push(
- <Tooltip key="inkToText" title={<div className="dash-tooltip">Change to Text</div>} placement="bottom">
- <button
- className="antimodeMenu-button"
- onPointerDown={this.inkToText}>
- <FontAwesomeIcon icon="font" size="lg" />
- </button>
- </Tooltip>);
- }
return this.getElement(buttons);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5f303b428..7865f2381 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,19 +1,19 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc";
+import { AclAdmin, AclAugment, AclEdit, DataSym, Doc, DocListCastAsync, Opt } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { RichTextField } from "../../../../fields/RichTextField";
import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
+import { ImageField } from "../../../../fields/URLField";
import { GetEffectiveAcl } from "../../../../fields/util";
-import { Utils, intersectRect, returnFalse } from "../../../../Utils";
+import { intersectRect, returnFalse, Utils } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents";
import { DocumentType } from "../../../documents/DocumentTypes";
import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { DocumentManager } from "../../../util/DocumentManager";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
@@ -21,18 +21,15 @@ import { ContextMenu } from "../../ContextMenu";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { PresBox } from "../../nodes/trails/PresBox";
import { PresMovement } from "../../nodes/trails/PresEnums";
+import { VideoBox } from "../../nodes/VideoBox";
+import { pasteImageBitmap } from "../../nodes/WebBoxRenderer";
import { PreviewCursor } from "../../PreviewCursor";
import { CollectionDockingView } from "../CollectionDockingView";
import { SubCollectionViewProps } from "../CollectionSubView";
-import { CollectionView } from "../CollectionView";
+import { TreeView } from "../TreeView";
import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
import "./MarqueeView.scss";
import React = require("react");
-import { StyleLayers } from "../../StyleProvider";
-import { TreeView } from "../TreeView";
-import { VideoBox } from "../../nodes/VideoBox";
-import { ImageField, WebField } from "../../../../fields/URLField";
-import { pasteImageBitmap } from "../../nodes/WebBoxRenderer";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -54,13 +51,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@observable _lastY: number = 0;
@observable _downX: number = 0;
@observable _downY: number = 0;
- @observable _visible: boolean = false;
+ @observable _visible: boolean = false; // selection rentangle for marquee selection/free hand lasso is visible
@observable _lassoPts: [number, number][] = [];
@observable _lassoFreehand: boolean = false;
@computed get Transform() { return this.props.getTransform(); }
@computed get Bounds() {
+ // nda - ternary argument to transformPoint is returning the lower of the downX/Y and lastX/Y and passing in as args x,y
const topLeft = this.Transform.transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
+ // nda - args to transformDirection is just x and y diff btw downX/Y and lastX/Y
const size = this.Transform.transformDirection(this._lastX - this._downX, this._lastY - this._downY);
return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
}
@@ -209,8 +208,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downY = this._lastY = e.clientY;
if (!(e.nativeEvent as any).marqueeHit) {
(e.nativeEvent as any).marqueeHit = true;
- // allow marquee if right click OR alt+left click
- if (e.button === 2 || (e.button === 0 && e.altKey)) {
+ // allow marquee if right click OR alt+left click OR in adding presentation slide & left key drag mode
+ if (e.button === 2 || (e.button === 0 && e.altKey) || (PresBox.startMarquee && e.button === 0)) {
// if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
this.setPreviewCursor(e.clientX, e.clientY, true, false);
// (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
@@ -241,6 +240,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this
}
e.altKey && e.preventDefault();
+ if (PresBox.startMarquee) {
+ e.stopPropagation();
+ }
}
@action
@@ -261,11 +263,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
document.removeEventListener("pointerdown", hideMarquee);
document.removeEventListener("wheel", hideMarquee);
};
- if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
+ if (PresBox.startMarquee) {
+ this.pinMarqueeView();
+ PresBox.startMarquee = false;
+ }
+ if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100) && !PresBox.startMarquee) {
MarqueeOptionsMenu.Instance.createCollection = this.collection;
MarqueeOptionsMenu.Instance.delete = this.delete;
MarqueeOptionsMenu.Instance.summarize = this.summary;
- MarqueeOptionsMenu.Instance.inkToText = this.syntaxHighlight;
MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
@@ -348,7 +353,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
- getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, layers: string[], makeGroup: Opt<boolean>) => {
+ getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt<boolean>) => {
const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => {
Doc.GetProto(doc).data = new List<Doc>(selected);
Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform";
@@ -357,7 +362,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return doc;
})(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
newCollection.system = undefined;
- newCollection._layerTags = new List<string>(layers);
newCollection._width = this.Bounds.width;
newCollection._height = this.Bounds.height;
newCollection._isGroup = makeGroup;
@@ -383,23 +387,81 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@undoBatch
@action
- pinWithView = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ pinMarqueeView = async () => {
const doc = this.props.Document;
const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
if (curPres) {
+ console.log(curPres);
if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; }
const pinDoc = Doc.MakeAlias(doc);
pinDoc.presentationTargetDoc = doc;
pinDoc.presMovement = PresMovement.Zoom;
pinDoc.groupWithUp = false;
pinDoc.context = curPres;
- pinDoc.title = doc.title + " - Slide";
+ pinDoc.title = doc.title + " - Slide";// these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time
+ pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
+ pinDoc.treeViewHeaderWidth = "100%"; // forces the header to grow to be the same size as its largest sibling.
+ pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
+ pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
+ pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
+ pinDoc.treeViewGrowsHorizontally = true;// the document expands horizontally when displayed as a tree view header
+ pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
+
const presArray = PresBox.Instance?.sortArray();
const size = PresBox.Instance?._selectedArray.size;
const presSelected = presArray && size ? presArray[size - 1] : undefined;
Doc.AddDocToList(curPres, "data", pinDoc, presSelected);
if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
- if (!DocumentManager.Instance.getDocumentView(curPres)) {
+ const dview = CollectionDockingView.Instance.props.Document;
+ const fieldKey = CollectionDockingView.Instance.props.fieldKey;
+ const tabdocs = await DocListCastAsync(dview[fieldKey]);
+ if (!tabdocs?.includes(curPres)) {
+ tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs
+ CollectionDockingView.AddSplit(curPres, "right");
+ }
+ PresBox.Instance?._selectedArray.clear();
+ pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Updates selected array
+ const index = PresBox.Instance?.childDocs.indexOf(pinDoc);
+ index && (curPres._itemIndex = index);
+ const scale = Math.min(this.props.PanelWidth() / this.Bounds.width, this.props.PanelHeight() / this.Bounds.height);
+ pinDoc.presPinView = true;
+ pinDoc.presPinViewX = this.Bounds.left + this.Bounds.width / 2;
+ pinDoc.presPinViewY = this.Bounds.top + this.Bounds.height / 2;
+ pinDoc.presPinViewScale = scale;
+ }
+ this.hideMarquee();
+ }
+
+ @undoBatch
+ @action
+ pinWithView = async (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ const doc = this.props.Document;
+ const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+ if (curPres) {
+ if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; }
+ const pinDoc = Doc.MakeAlias(doc);
+ pinDoc.presentationTargetDoc = doc;
+ pinDoc.presMovement = PresMovement.Zoom;
+ pinDoc.groupWithUp = false;
+ pinDoc.context = curPres;
+ pinDoc.title = doc.title + " - Slide";
+ pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
+ pinDoc.treeViewHeaderWidth = "100%"; // forces the header to grow to be the same size as its largest sibling.
+ pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
+ pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
+ pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
+ pinDoc.treeViewGrowsHorizontally = true;// the document expands horizontally when displayed as a tree view header
+ pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
+
+ const presArray = PresBox.Instance?.sortArray();
+ const size = PresBox.Instance?._selectedArray.size;
+ const presSelected = presArray && size ? presArray[size - 1] : undefined;
+ Doc.AddDocToList(curPres, "data", pinDoc, presSelected);
+ if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; const dview = CollectionDockingView.Instance.props.Document;
+ const fieldKey = CollectionDockingView.Instance.props.fieldKey;
+ const tabdocs = await DocListCastAsync(dview[fieldKey]);
+ if (!tabdocs?.includes(curPres)) {
+ tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs
CollectionDockingView.AddSplit(curPres, "right");
}
PresBox.Instance?._selectedArray.clear();
@@ -437,7 +499,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}));
this.props.removeDocument?.(selected);
}
- const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined, [], group);
+ const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined, group);
this.props.addDocument?.(newCollection);
this.props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -531,7 +593,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
- const newCollection = this.getCollection([], undefined, [StyleLayers.Background], undefined);
+ const newCollection = this.getCollection([], undefined, undefined);
this.props.addDocument?.(newCollection);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
@@ -617,7 +679,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
(this.touchesLine(bounds) || this.boundingShape(bounds)) && selection.push(doc);
}
};
- this.props.activeDocuments().filter(doc => this.props.layerProvider?.(doc) !== false && !doc.z).map(selectFunc);
+ this.props.activeDocuments().filter(doc => !doc.z && !doc._lockedPosition).map(selectFunc);
if (!selection.length && selectBackgrounds) this.props.activeDocuments().filter(doc => doc.z === undefined).map(selectFunc);
if (!selection.length) this.props.activeDocuments().filter(doc => doc.z !== undefined).map(selectFunc);
return selection;
@@ -645,7 +707,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return <div className="marqueeView"
style={{
overflow: StrCast(this.props.Document._overflow),
- cursor: CurrentUserUtils.SelectedTool === InkTool.Pen ? "crosshair" : "pointer"
+ cursor: [InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool) || this._visible || PresBox.startMarquee ? "crosshair" : "pointer"
}}
onDragOver={e => e.preventDefault()}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index e3d4f86c1..c0a33a5e0 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -151,11 +151,10 @@ export class CollectionLinearView extends CollectionSubView() {
removeDocument={this.props.removeDocument}
ScreenToLocalTransform={docXf}
PanelWidth={nested ? doc[WidthSym] : this.dimension}
- PanelHeight={nested ? doc[HeightSym] : this.dimension}
+ PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension}
renderDepth={this.props.renderDepth + 1}
focus={emptyFunction}
styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -231,7 +230,7 @@ export class CollectionLinearView extends CollectionSubView() {
<span className="bottomPopup-text">
Currently playing:
{CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) =>
- <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true)}>
+ <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}>
{clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")}
</span>)}
</span>
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 6929a1cd8..e2dfb25e2 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,10 +1,10 @@
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from "react";
-import { Doc } from '../../../../fields/Doc';
+import { Doc, DocListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse } from '../../../../Utils';
+import { returnFalse } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
@@ -194,11 +194,38 @@ export class CollectionMulticolumnView extends CollectionSubView() {
@undoBatch
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- if (super.onInternalDrop(e, de)) {
+ let dropInd = -1;
+ if (de.complete.docDragData && this._mainCont) {
+ let curInd = -1;
de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
- d._dimUnit = "*";
- d._dimMagnitude = 1;
+ curInd = this.childDocs.indexOf(d);
}));
+ Array.from(this._mainCont.children).forEach((child, index) => {
+ const brect = child.getBoundingClientRect();
+ if (brect.x < de.x && brect.x + brect.width > de.x) {
+ if (curInd !== -1 && curInd === Math.floor(index / 2)) {
+ dropInd = curInd;
+ }
+ else if (child.className === "multiColumnResizer") {
+ dropInd = Math.floor(index / 2);
+ } else {
+ dropInd = Math.ceil(index / 2 + (de.x - brect.x > brect.width / 2 ? 0 : -1));
+ }
+ }
+ });
+ if (super.onInternalDrop(e, de)) {
+ de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
+ d._dimUnit = "*";
+ d._dimMagnitude = 1;
+ if (dropInd !== curInd || dropInd === -1) {
+ if (this.childDocs.includes(d)) {
+ if (dropInd > this.childDocs.indexOf(d)) dropInd--;
+ }
+ Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
+ Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+ }
+ }));
+ }
}
return false;
}
@@ -214,24 +241,30 @@ export class CollectionMulticolumnView extends CollectionSubView() {
}
return this.props.addDocTab(doc, where);
}
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isChildContentActive = () => this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false;
getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
return <DocumentView
Document={layout}
DataDoc={layout.resolvedDataDoc as Doc}
styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={this.props.docViewPath}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
freezeDimensions={this.props.childFreezeDimensions}
renderDepth={this.props.renderDepth + 1}
- isContentActive={emptyFunction}
+ isContentActive={this.isChildContentActive}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ hideResizeHandles={this.props.childHideResizeHandles?.()}
+ hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+ fitContentsToDoc={this.props.fitContentsToDoc}
PanelWidth={width}
PanelHeight={height}
rootSelected={this.rootSelected}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
+ suppressSetHeight={true}
ScreenToLocalTransform={dxf}
focus={this.props.focus}
docFilters={this.childDocFilters}
@@ -290,13 +323,13 @@ export class CollectionMulticolumnView extends CollectionSubView() {
render(): JSX.Element {
return (
- <div className={"collectionMulticolumnView_contents"}
+ <div className={"collectionMulticolumnView_contents"} ref={this.createDashEventsTarget}
style={{
width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin),
marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin)
- }} ref={this.createDashEventsTarget}>
+ }} >
{this.contents}
</div>
);
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 7e2b83230..3010e36aa 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,10 +1,10 @@
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from "react";
-import { Doc } from '../../../../fields/Doc';
+import { Doc, DocListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse } from '../../../../Utils';
+import { returnFalse } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
@@ -190,14 +190,42 @@ export class CollectionMultirowView extends CollectionSubView() {
return Transform.Identity(); // type coersion, this case should never be hit
}
+
@undoBatch
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- if (super.onInternalDrop(e, de)) {
+ let dropInd = -1;
+ if (de.complete.docDragData && this._mainCont) {
+ let curInd = -1;
de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
- d._dimUnit = "*";
- d._dimMagnitude = 1;
+ curInd = this.childDocs.indexOf(d);
}));
+ Array.from(this._mainCont.children).forEach((child, index) => {
+ const brect = child.getBoundingClientRect();
+ if (brect.y < de.y && brect.y + brect.height > de.y) {
+ if (curInd !== -1 && curInd === Math.floor(index / 2)) {
+ dropInd = curInd;
+ }
+ else if (child.className === "multiColumnResizer") {
+ dropInd = Math.floor(index / 2);
+ } else {
+ dropInd = Math.ceil(index / 2 + (de.y - brect.y > brect.height / 2 ? 0 : -1));
+ }
+ }
+ });
+ if (super.onInternalDrop(e, de)) {
+ de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
+ d._dimUnit = "*";
+ d._dimMagnitude = 1;
+ if (dropInd !== curInd || dropInd === -1) {
+ if (this.childDocs.includes(d)) {
+ if (dropInd > this.childDocs.indexOf(d)) dropInd--;
+ }
+ Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
+ Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+ }
+ }));
+ }
}
return false;
}
@@ -213,12 +241,13 @@ export class CollectionMultirowView extends CollectionSubView() {
}
return this.props.addDocTab(doc, where);
}
- getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+ isChildContentActive = () => this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false;
+ getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => {
return <DocumentView
Document={layout}
DataDoc={layout.resolvedDataDoc as Doc}
styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={this.props.docViewPath}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
@@ -231,9 +260,13 @@ export class CollectionMultirowView extends CollectionSubView() {
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
+ isContentActive={this.isChildContentActive}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ hideResizeHandles={this.props.childHideResizeHandles?.()}
+ hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+ fitContentsToDoc={this.props.fitContentsToDoc}
focus={this.props.focus}
docFilters={this.childDocFilters}
- isContentActive={emptyFunction}
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index c2bb3b3ac..adcd9e1e3 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -199,7 +199,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc);
const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
// Jump to the this document
- DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext,
+ DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [],
undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc));
}
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
index dc35b5749..0875c80b3 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
@@ -278,6 +278,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
@undoBatch
onKeyDown = (e: React.KeyboardEvent): void => {
if (e.key === "Enter") {
+ e.stopPropagation();
if (this._searchTerm.includes(":")) {
const colpos = this._searchTerm.indexOf(":");
const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
@@ -490,7 +491,9 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
<div className="keys-dropdown" style={{ zIndex: 1, width: this.props.width, maxWidth: this.props.width }}>
<input className="keys-search" style={{ width: "100%" }}
- ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown}
+ ref={this._inputRef} type="text"
+ value={this._searchTerm} placeholder="Column key"
+ onKeyDown={this.onKeyDown}
onChange={e => this.onChange(e.target.value)}
onClick={(e) => { e.stopPropagation(); this._inputRef.current?.focus(); }}
onFocus={this.onFocus} ></input>
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index a93762ea4..b731479a5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -417,7 +417,6 @@ export class CollectionSchemaView extends CollectionSubView() {
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
docViewPath={returnEmptyDoclist}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx
index 605481ddf..bea5b3be6 100644
--- a/src/client/views/collections/collectionSchema/SchemaTable.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx
@@ -573,7 +573,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
Document={this._showDoc}
DataDoc={this._showDataDoc}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
docViewPath={returnEmptyDoclist}
freezeDimensions={true}
focus={DocUtils.DefaultFocus}
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index 520ac9357..a14634bdc 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -10,7 +10,7 @@ $black: #000000;
$light-blue: #bdddf5;
$light-blue-transparent: #bdddf590;
$medium-blue: #4476f7;
-$medium-blue-alt: #4476f73d;
+$medium-blue-alt: #0047ff54;
$pink: #e0217d;
$yellow: #f5d747;
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index abd413f57..1d6496d3c 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -60,6 +60,24 @@
}
}
+.linkEditor-zoomFollow {
+ padding-left: 26px;
+ padding-right: 6.5px;
+ padding-bottom: 3.5px;
+ display: flex;
+
+ .linkEditor-zoomFollow-label {
+ text-decoration-color: black;
+ color: black;
+ line-height: 1.7;
+ }
+
+ .linkEditor-zoomFollow-input {
+ display: block;
+ width: 20px;
+ }
+}
+
.linkEditor-description {
padding-left: 26px;
padding-right: 6.5px;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index db331bb75..1414bfdf7 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -9,7 +9,6 @@ import { undoBatch } from "../../util/UndoManager";
import './LinkEditor.scss';
import { LinkRelationshipSearch } from "./LinkRelationshipSearch";
import React = require("react");
-import { ToString } from "../../../fields/FieldSymbols";
interface LinkEditorProps {
@@ -23,6 +22,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@observable description = Field.toString(LinkManager.currentLink?.description as any as Field);
@observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
+ @observable zoomFollow = StrCast(this.props.sourceDoc.followLinkZoom);
@observable openDropdown: boolean = false;
@observable showInfo: boolean = false;
@computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
@@ -114,6 +114,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
this.setDescripValue(this.description);
document.getElementById('input')?.blur();
}
+ e.stopPropagation();
}
onRelationshipKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
@@ -121,6 +122,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
this.setRelationshipValue(this.relationship);
document.getElementById('input')?.blur();
}
+ e.stopPropagation();
}
onDescriptionDown = () => this.setDescripValue(this.description);
@@ -143,9 +145,9 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@action
handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
@action
- handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this.relationship = e.target.value;
- }
+ handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.relationship = e.target.value; }
+ @action
+ handleZoomFollowChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.props.sourceDoc.followLinkZoom = e.target.checked; }
@action
handleRelationshipSearchChange = (result: string) => {
this.setRelationshipValue(result);
@@ -183,6 +185,22 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
</div>
</div>;
}
+ @computed
+ get editZoomFollow() {
+ //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
+ return <div className="linkEditor-zoomFollow">
+ <div className="linkEditor-zoomFollow-label">Zoom To Link Target:</div>
+ <div className="linkEditor-zoomFollow-input">
+ <div className="linkEditor-zoomFollow-editing">
+ <input
+ style={{ width: "100%" }}
+ type="checkbox"
+ value={this.zoomFollow}
+ onChange={this.handleZoomFollowChange} />
+ </div>
+ </div>
+ </div >;
+ }
@computed
get editDescription() {
@@ -279,9 +297,8 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
render() {
const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
-
return !destination ? (null) : (
- <div className="linkEditor">
+ <div className="linkEditor" tabIndex={0} onKeyDown={e => e.stopPropagation()}>
<div className="linkEditor-info">
<Tooltip title={<><div className="dash-tooltip">Return to link menu</div></>} placement="top">
<button className="linkEditor-button-back"
@@ -303,6 +320,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
{this.editDescription}
{this.editRelationship}
+ {this.editZoomFollow}
{this.followingDropdown}
</div>
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index c8be9069c..4b33ef8ae 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -67,7 +67,7 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
<input defaultValue={""} autoComplete="off" type="text" placeholder="Search for Document..." id="search-input"
className="linkPopup-searchBox searchBox-input" /> */}
- <SearchBox
+ <SearchBox
Document={CurrentUserUtils.MySearchPanelDoc}
DataDoc={CurrentUserUtils.MySearchPanelDoc}
linkFrom={linkDoc}
@@ -83,7 +83,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
pinToPres={emptyFunction}
rootSelected={returnTrue}
styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
PanelWidth={this.getPWidth}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index e28e5b453..787ad939d 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -84,12 +84,12 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed get miniPlayer() { return this.props.PanelHeight() < 50; } // used to collapse timeline when node is shrunk
@computed get links() { return DocListCast(this.dataDoc.links); }
@computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct recording time
- @computed get mediaState() { return this.layoutDoc.mediaState as media_state; }
+ @computed get mediaState() { return this.dataDoc.mediaState as media_state; }
@computed get path() { // returns the path of the audio file
const path = Cast(this.props.Document[this.fieldKey], AudioField, null)?.url.href || "";
return path === nullAudio ? "" : path;
}
- set mediaState(value) { this.layoutDoc.mediaState = value; }
+ set mediaState(value) { this.dataDoc.mediaState = value; }
@computed get timeline() { return this._stackedTimeline; } // returns CollectionStackedTimeline ref
@@ -237,9 +237,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.dataDoc[this.fieldKey + "-recordingStart"] = new DateField();
DocUtils.ActiveRecordings.push(this);
this._recorder.ondataavailable = async (e: any) => {
- console.log("Data available", e);
const [{ result }] = await Networking.UploadFilesToServer(e.data);
- console.log("Data result", result);
if (!(result instanceof Error)) {
this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
}
@@ -350,13 +348,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
2 * NumCast(this.layoutDoc._height)
);
Doc.GetProto(newDoc).recordingSource = this.dataDoc;
- Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(
- `self.recordingSource["${this.fieldKey}-recordingStart"]`
- );
- Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(
- "self.recordingSource.mediaState"
- );
- this.props.addDocument?.(newDoc);
+ Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}-recordingStart"]`);
+ Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState");
+ const overlayDoc = Doc.UserDoc().myOverlayDocs as Doc;
+ if (DocListCast(overlayDoc[Doc.LayoutFieldKey(overlayDoc)]).includes(this.rootDoc)) {
+ newDoc.x = this.rootDoc.x;
+ newDoc.y = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
+ Doc.AddDocToList(overlayDoc, undefined, newDoc);
+ } else {
+ this.props.addDocument?.(newDoc);
+ }
e.stopPropagation();
}
@@ -611,7 +612,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
renderDepth={this.props.renderDepth + 1}
startTag={"_timecodeToShow" /* audioStart */}
endTag={"_timecodeToHide" /* audioEnd */}
- focus={DocUtils.DefaultFocus}
bringToFront={emptyFunction}
CollectionView={undefined}
playFrom={this.playFrom}
@@ -654,7 +654,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
className="audiobox-container"
onContextMenu={this.specificContextMenu}
onClick={!this.path && !this._recorder ? this.recordAudioAnnotation : undefined}
- style={{ pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined }}
+ style={{ pointerEvents: this.layoutDoc._lockedPosition ? "none" : undefined }}
>
{!this.path ? this.recordingControls : this.playbackControls}
</div>;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3f16d3c49..5a0ab9110 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -21,7 +21,6 @@ import React = require("react");
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
- layerProvider: ((doc: Doc, assign?: boolean) => boolean) | undefined;
renderCutoffProvider: (doc: Doc) => boolean;
zIndex?: number;
highlight?: boolean;
@@ -38,7 +37,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
@observable _contentView: DocumentView | undefined | null;
get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; }
- get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.props.jitterRotation}deg)`; }
+ get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`; }
get X() { return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x); }
get Y() { return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y); }
get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); }
@@ -159,7 +158,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
...this.props,
CollectionFreeFormDocumentView: this.returnThis,
styleProvider: this.styleProvider,
- layerProvider: this.props.layerProvider,
ScreenToLocalTransform: this.screenToLocalTransform,
PanelWidth: this.panelWidth,
PanelHeight: this.panelHeight,
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 78608b681..5ea6d567a 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -3,7 +3,7 @@ import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc, Opt } from '../../../fields/Doc';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, OmitKeys, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch } from '../../util/UndoManager';
@@ -76,7 +76,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
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, `compareBox-${which}`)}>
+ onClick={e => this.clearDoc(e, which)}>
<FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
</div>;
};
@@ -87,7 +87,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
return whichDoc ? <>
<DocumentView
ref={(r) => {
- whichDoc !== targetDoc && r?.focus(targetDoc);
+ whichDoc !== targetDoc && r?.focus(whichDoc);
}}
{...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
isContentActive={returnFalse}
@@ -96,7 +96,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
Document={targetDoc}
DataDoc={undefined}
hideLinkButton={true}
- pointerEvents={"none"} />
+ pointerEvents={returnNone} />
{clearButton(which)}
</> : // placeholder image if doc is missing
<div className="placeholder">
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index a9c998757..56de2d1fc 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -52,7 +52,7 @@ export class DocumentIconContainer extends React.Component {
};
},
getVars() {
- const docs = DocumentManager.Instance.DocumentViews;
+ const docs = Array.from(DocumentManager.Instance.DocumentViews);
const capturedVariables: { [name: string]: Field } = {};
usedDocuments.forEach(index => capturedVariables[`d${index}`] = docs[index].props.Document);
return { capturedVariables };
@@ -60,6 +60,6 @@ export class DocumentIconContainer extends React.Component {
};
}
render() {
- return DocumentManager.Instance.DocumentViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
+ return Array.from(DocumentManager.Instance.DocumentViews).map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 7f69adf6c..78d35ab99 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -17,7 +17,6 @@ import { DocumentView } from "./DocumentView";
import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
import { TaskCompletionBox } from "./TaskCompletedBox";
import React = require("react");
-import { DocumentType } from "../../documents/DocumentTypes";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8570f6fff..073f734c0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -2,7 +2,7 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, DocListCastAsync, Field, Opt, StrListCast, WidthSym } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, StrListCast, WidthSym } from "../../../fields/Doc";
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -14,7 +14,7 @@ import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from "../../.
import { AudioField } from "../../../fields/URLField";
import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, lightOrDark, OmitKeys, returnEmptyString, returnTrue, returnVal, simulateMouseClick, Utils } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, lightOrDark, OmitKeys, returnEmptyString, returnTrue, returnFalse, returnVal, simulateMouseClick, Utils } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
@@ -37,7 +37,7 @@ import { DocComponent } from "../DocComponent";
import { EditableView } from '../EditableView';
import { InkingStroke } from "../InkingStroke";
import { LightboxView } from "../LightboxView";
-import { StyleLayers, StyleProp } from "../StyleProvider";
+import { StyleProp } from "../StyleProvider";
import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
import { DocumentContentsView } from "./DocumentContentsView";
import { DocumentLinksButton } from './DocumentLinksButton';
@@ -50,6 +50,7 @@ import { ScriptingBox } from "./ScriptingBox";
import { PresBox } from './trails/PresBox';
import React = require("react");
import { DocServer } from "../../DocServer";
+import { FieldViewProps } from "./FieldView";
const { Howl } = require('howler');
interface Window {
@@ -122,8 +123,9 @@ export interface DocumentViewSharedProps {
PanelWidth: () => number;
PanelHeight: () => number;
docViewPath: () => DocumentView[];
+ childHideDecorationTitle?: () => boolean;
+ childHideResizeHandles?: () => boolean;
dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt
- layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean);
styleProvider: Opt<StyleProviderFunc>;
focus: DocFocusFunc;
fitWidth?: (doc: Doc) => boolean;
@@ -148,7 +150,7 @@ export interface DocumentViewSharedProps {
ignoreAutoHeight?: boolean;
forceAutoHeight?: boolean;
disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
- pointerEvents?: string;
+ pointerEvents?: () => Opt<string>;
scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
createNewFilterDoc?: () => void;
updateFilterDoc?: (doc: Doc) => void;
@@ -168,6 +170,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
radialMenu?: String[];
LayoutTemplateString?: string;
dontCenter?: "x" | "y" | "xy";
+ dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling
ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
NativeWidth?: () => number;
NativeHeight?: () => number;
@@ -177,6 +180,8 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
onDoubleClick?: () => ScriptField;
onPointerDown?: () => ScriptField;
onPointerUp?: () => ScriptField;
+ onBrowseClick?: () => (ScriptField | undefined);
+ onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined);
}
// these props are only available in DocumentViewIntenral
@@ -232,12 +237,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); }
@computed get nativeWidth() { return this.props.NativeWidth(); }
@computed get nativeHeight() { return this.props.NativeHeight(); }
- @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
+ @computed get onClickHandler() { return this.props.onClick?.() ?? (this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null))); }
@computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); }
@computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); }
@computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); }
-
componentWillUnmount() { this.cleanupHandlers(true); }
componentDidMount() { this.setupHandlers(); }
//componentDidUpdate() { this.setupHandlers(); }
@@ -249,6 +253,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
}
}
+ @action
cleanupHandlers(unbrush: boolean) {
this._dropDisposer?.();
this._multiTouchDisposer?.();
@@ -424,6 +429,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const dragData = new DragManager.DocumentDragData([this.props.Document]);
const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0);
dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top);
+ dragData.offset[0] = Math.min(this.rootDoc[WidthSym](), dragData.offset[0]);
+ dragData.offset[1] = Math.min(this.rootDoc[HeightSym](), dragData.offset[1]);
dragData.dropAction = dropAction;
dragData.treeViewDoc = this.props.treeViewDoc;
dragData.removeDocument = this.props.removeDocument;
@@ -472,7 +479,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
let stopPropagate = true;
let preventDefault = true;
- !StrListCast(this.props.Document._layerTags).includes(StyleLayers.Background) && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
+ const isScriptBox = () => StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name);
+ (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
if (this._timeout) {
clearTimeout(this._timeout);
@@ -492,12 +500,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click");
} else if (!Doc.IsSystem(this.rootDoc)) {
UndoManager.RunInBatch(() =>
- LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.())
+ LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.(), this.props.addDocTab)
, "double tap");
SelectionManager.DeselectAll();
Doc.UnBrushDoc(this.props.Document);
}
- } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+ } else if (this.onClickHandler?.script && !isScriptBox()) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
const { clientX, clientY, shiftKey } = e;
const func = () => this.onClickHandler.script.run({
this: this.layoutDoc,
@@ -513,7 +521,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
runInAction(() => this._pendingDoubleClick = true);
this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 350);
} else clickFunc();
- } else if (this.allLinks && this.Document.type !== DocumentType.LINK && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+ } else if (this.allLinks && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey);
} else {
if ((this.layoutDoc.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
@@ -531,8 +539,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
});
onPointerDown = (e: React.PointerEvent): void => {
+ if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.SelectedTool === InkTool.Eraser) return;
// continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
- if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) {
+ if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
@@ -546,6 +555,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
// if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
!(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) &&
+ !this.props.onBrowseClick?.() &&
!this.Document.ignoreClick &&
!e.ctrlKey &&
(e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
@@ -565,7 +575,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onPointerMove = (e: PointerEvent): void => {
if (e.cancelBubble) return;
- if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) return;
+ if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) return;
if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
@@ -599,7 +609,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
@undoBatch @action
- toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
+ toggleFollowLink = (location: Opt<string>, zoom?: boolean, setPushpin?: boolean): void => {
this.Document.ignoreClick = false;
if (setPushpin) {
this.Document.isPushpin = !this.Document.isPushpin;
@@ -608,7 +618,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this.Document._isLinkButton = !this.Document._isLinkButton;
}
if (this.Document._isLinkButton && !this.onClickHandler) {
- this.Document.followLinkZoom = zoom;
+ zoom !== undefined && (this.Document.followLinkZoom = zoom);
this.Document.followLinkLocation = location;
} else if (this.Document._isLinkButton && this.onClickHandler) {
this.Document._isLinkButton = false;
@@ -663,7 +673,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document;
- de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y]);
+ de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]);
}
}
}
@@ -739,7 +749,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const alias = Doc.MakeAlias(this.rootDoc);
alias.layout = FormattedTextBox.LayoutString("newfield");
alias.title = "newfield";
- alias._yMargin = 10;
alias._height = 35;
alias._width = 100;
alias.syncLayoutFieldWithTitle = true;
@@ -856,8 +865,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this.props.isContentActive()) ? true : undefined;
}
@observable _retryThumb = 1;
- thumbShown = () => !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
- !this._componentView?.isAnyChildContentActive?.() ? true : false
+ thumbShown = () => {
+ return !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
+ !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
+ !Doc.isBrushedHighlightedDegree(this.props.Document) &&
+ !this._componentView?.isAnyChildContentActive?.() ? true : false;
+ }
@computed get contents() {
TraceMobx();
const audioView = !this.layoutDoc._showAudio ? (null) :
@@ -869,11 +882,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return <div className="documentView-contentsView"
style={{
- pointerEvents: this.props.pointerEvents as any ?? this.rootDoc.layoutKey === "layout_icon" ? "none" : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"),
+ pointerEvents: this.props.pointerEvents?.() as any ?? this.rootDoc.layoutKey === "layout_icon" ? "none" :
+ this.props.contentPointerEvents as any ? this.props.contentPointerEvents as any :
+ this.rootDoc.type !== DocumentType.INK && this.isContentActive() ? "all" :
+ "none",
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
{!this._retryThumb || !this.thumbShown() ? (null) :
- <img style={{ background: "white", top: 0, position: "absolute" }} src={this.thumb} // + '?d=' + (new Date()).getTime()}
+ <img style={{ background: "white", top: 0, position: "relative" }} src={this.thumb} // + '?d=' + (new Date()).getTime()}
width={this.props.PanelWidth()} height={this.props.PanelHeight()}
onError={(e: any) => {
setTimeout(action(() => this._retryThumb = 0), 0);
@@ -945,6 +961,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return filtered.map((link, i) =>
<div className="documentView-anchorCont" key={link[Id]}>
<DocumentView {...this.props}
+ isContentActive={returnFalse}
Document={link}
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
@@ -1107,10 +1124,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
render() {
TraceMobx();
- const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
- const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange"][highlightIndex];
- const highlightStyle = ["solid", "dashed", "solid", "solid"][highlightIndex];
- const excludeTypes = !this.props.treeViewDoc && highlightIndex < 3 ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
+ const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : Doc.DocBrushStatus.unbrushed) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
+ const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange", "lightBlue"][highlightIndex];
+ const highlightStyle = ["solid", "dashed", "solid", "solid", "solid"][highlightIndex];
+ const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
@@ -1125,8 +1142,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onKeyDown={this.onKeyDown}
onPointerDown={this.onPointerDown}
onClick={this.onClick}
- onPointerEnter={e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)}
- onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)}
+ onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))}
+ onPointerLeave={action(e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document))}
style={{
display: this.hidden ? "inline" : undefined,
borderRadius: this.borderRounding,
@@ -1206,7 +1223,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
@computed get shouldNotScale() {
return (this.fitWidth && !this.nativeWidth) ||
- this.props.ContainingCollectionView?.collectionViewType === CollectionViewType.Time ||
+ this.props.dontScaleFilter?.(this.Document) ||
this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
}
@computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
@@ -1235,7 +1252,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options);
getBounds = () => {
- if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
+ if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
return undefined;
}
const xf = (this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling)).inverse();
@@ -1290,7 +1307,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
ContentScale = () => this.nativeScaling;
selfView = () => this;
screenToLocalTransform = () => {
- return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling);
+ const oshift = this.fitWidth && this.ComponentView instanceof FormattedTextBox;
+ const shift = oshift ? -(this.props.PanelHeight() - this.rootDoc[HeightSym]()) / 2 : 0;
+ return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).translate(0, shift).scale(1 / this.nativeScaling);
}
componentDidMount() {
this._disposers.reactionScript = reaction(
@@ -1324,6 +1343,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
transition: this.props.dataTransition,
position: this.props.Document.isInkMask ? "absolute" : undefined,
transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
+ margin: this.fitWidth ? "auto" : undefined,
width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
`${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss
index 6c9d53d10..c6a497831 100644
--- a/src/client/views/nodes/EquationBox.scss
+++ b/src/client/views/nodes/EquationBox.scss
@@ -1,3 +1,5 @@
+@import "../global/globalCssVariables.scss";
+
.equationBox-cont {
transform-origin: top left;
} \ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ae9acfe3f..79c1f1c40 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -2,10 +2,11 @@ import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
import { DateField } from "../../../fields/DateField";
-import { Doc, Field, FieldResult } from "../../../fields/Doc";
+import { Doc, Field, FieldResult, Opt } from "../../../fields/Doc";
import { List } from "../../../fields/List";
import { WebField } from "../../../fields/URLField";
-import { DocumentViewSharedProps } from "./DocumentView";
+import { DocumentView, DocumentViewSharedProps } from "./DocumentView";
+import { ScriptField } from "../../../fields/ScriptField";
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -23,9 +24,11 @@ export interface FieldViewProps extends DocumentViewSharedProps {
isSelected: (outsideReaction?: boolean) => boolean;
scaling?: () => number;
setHeight?: (height: number) => void;
+ onBrowseClick?: () => (ScriptField | undefined);
+ onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined);
// properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
- pointerEvents?: string;
+ pointerEvents?: () => Opt<string>;
fontSize?: number;
height?: number;
width?: number;
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index ba65acee0..28834a202 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -96,17 +96,15 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
runInAction(() => this._loaded = true);
}, { fireImmediately: true });
}
- @observable _allDocs = [] as Doc[];
+
@computed get allDocs() {
// trace();
+ const allDocs = new Set<Doc>();
const targetDoc = FilterBox.targetDoc;
if (this._loaded && targetDoc) {
- const allDocs = new Set<Doc>();
- const activeTabs = FilterBox.targetDocChildren;
- SearchBox.foreachRecursiveDoc(activeTabs, (depth, doc) => allDocs.add(doc));
- setTimeout(action(() => this._allDocs = Array.from(allDocs)));
+ SearchBox.foreachRecursiveDoc(FilterBox.targetDocChildren, (depth, doc) => allDocs.add(doc));
}
- return this._allDocs;
+ return Array.from(allDocs);
}
@computed get _allFacets() {
@@ -208,7 +206,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
facetValues.strings.map(val => {
const num = val ? Number(val) : Number.NaN;
if (Number.isNaN(num)) {
- nonNumbers++;
+ num && nonNumbers++;
} else {
minVal = Math.min(num, minVal);
maxVal = Math.max(num, maxVal);
@@ -218,8 +216,9 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (facetHeader === "text" || facetValues.rtFields / allCollectionDocs.length > 0.1) {
newFacet = Docs.Create.TextDocument("", {
title: facetHeader, system: true, target: targetDoc, _width: 100, _height: 25, _stayInCollection: true,
- treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, ignoreClick: true
+ treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, ignoreClick: true,
});
+ Doc.GetProto(newFacet).forceActive = true; // required for FormattedTextBox to be able to gain focus since it will never be 'selected'
Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
newFacet._textBoxPaddingX = newFacet._textBoxPaddingY = 4;
const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
@@ -240,6 +239,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal;
const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`;
newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
+ newFacet.data = ComputedField.MakeFunction(`readNumFacetData(self.target, self, "${FilterBox.targetDocChildKey}", "${facetHeader}")`);
} else {
newFacet = new Doc();
newFacet.system = true;
@@ -386,6 +386,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
options={options}
isMulti={false}
onChange={val => this.facetClick((val as UserOptions).value)}
+ onKeyDown={e => e.stopPropagation()}
value={null}
closeMenuOnSelect={true}
/>
@@ -402,6 +403,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
+ childDocumentsActive={returnTrue}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
ContainingCollectionView={this.props.ContainingCollectionView}
PanelWidth={this.props.PanelWidth}
@@ -420,10 +422,10 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
whenChildContentsActiveChanged={returnFalse}
treeViewHideTitle={true}
focus={returnFalse}
+ onCheckedClick={this.scriptField}
treeViewHideHeaderFields={false}
dontRegisterView={true}
styleProvider={this.FilterStyleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={this.props.docViewPath}
scriptContext={this.props.scriptContext}
moveDocument={returnFalse}
@@ -480,6 +482,32 @@ ScriptingGlobals.add(function determineCheckedState(layoutDoc: Doc, facetHeader:
}
return undefined;
});
+ScriptingGlobals.add(function readNumFacetData(layoutDoc: Doc, facetDoc: Doc, childKey: string, facetHeader: string) {
+ const allCollectionDocs = new Set<Doc>();
+ const activeTabs = DocListCast(layoutDoc[childKey]);
+ SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
+ const set = new Set<string>();
+ if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key)));
+ else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
+ const facetValues = Array.from(set).filter(v => v);
+
+ let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
+ facetValues.map(val => {
+ const num = val ? Number(val) : Number.NaN;
+ if (!Number.isNaN(num)) {
+ minVal = Math.min(num, minVal);
+ maxVal = Math.max(num, maxVal);
+ }
+ });
+ const newFacetField = Doc.LayoutFieldKey(facetDoc);
+ const ranged = Doc.readDocRangeFilter(layoutDoc, facetHeader);
+ const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * .1));
+ const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * .05)));
+ facetDoc[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0];
+ facetDoc[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1];
+ Doc.GetProto(facetDoc)[newFacetField + "-minThumb"] = extendedMinVal;
+ Doc.GetProto(facetDoc)[newFacetField + "-maxThumb"] = extendedMaxVal;
+})
ScriptingGlobals.add(function readFacetData(layoutDoc: Doc, childKey: string, facetHeader: string) {
const allCollectionDocs = new Set<Doc>();
const activeTabs = DocListCast(layoutDoc[childKey]);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d061cdd0b..ab4ed6b33 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,7 +1,7 @@
import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from "mobx-react";
import { extname } from 'path';
-import { DataSym, Doc, DocListCast, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, DocListCast, Opt, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -49,6 +49,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
private _dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
+ private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
@observable _curSuffix = "";
@observable _uploadIcon = uploadIcons.idle;
@@ -62,7 +63,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
getAnchor = () => {
- const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations);
+ const anchor = this._getAnchor?.(this._savedAnnotations);
anchor && this.addDocument(anchor);
return anchor ?? this.rootDoc;
}
@@ -356,7 +357,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
}
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
setupMoveUpEvents(this, e, action(e => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
@@ -366,9 +367,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@action
finishMarquee = () => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this._marqueeing = undefined;
this.props.select(false);
}
+ savedAnnotations = () => this._savedAnnotations;
render() {
TraceMobx();
@@ -378,7 +381,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
style={{
width: this.props.PanelWidth() ? undefined : `100%`,
height: this.props.PanelWidth() ? undefined : `100%`,
- pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
+ pointerEvents: this.layoutDoc._lockedPosition ? "none" : undefined,
borderRadius
}} >
<CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
@@ -408,7 +411,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
docView={this.props.docViewPath().slice(-1)[0]}
addDocument={this.addDocument}
finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
+ savedAnnotations={this.savedAnnotations}
annotationLayer={this._annotationLayer.current}
mainCont={this._mainCont.current}
anchorMenuCrop={this.crop}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 881cbf2bb..80def3025 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -60,7 +60,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
docRangeFilters: returnEmptyFilter,
searchFilterDocs: returnEmptyDoclist,
styleProvider: DefaultStyleProvider,
- layerProvider: undefined,
docViewPath: returnEmptyDoclist,
ContainingCollectionView: undefined,
ContainingCollectionDoc: undefined,
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index d0d61fd79..b0b050cea 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -23,8 +23,8 @@ export interface LabelBoxProps {
@observer
export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxProps)>() {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); }
- public static LayoutStringWithTitle(fieldType: { name: string }, fieldStr: string, label: string) {
- return `<${fieldType.name} fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+ public static LayoutStringWithTitle(fieldStr: string, label?: string) {
+ return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
}
private dropDisposer?: DragManager.DragDropDisposer;
private _timeout: any;
@@ -79,17 +79,18 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
@computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; }
fitTextToBox = (r: any): any => {
+ const singleLine = BoolCast(this.rootDoc._singleLine, true);
const params = {
rotateText: null,
fontSizeFactor: 1,
- minimumFontSize: NumCast(this.rootDoc._minFontSize, 2),
+ minimumFontSize: NumCast(this.rootDoc._minFontSize, 8),
maximumFontSize: NumCast(this.rootDoc._maxFontSize, 1000),
limitingDimension: "both",
horizontalAlign: "center",
verticalAlign: "center",
textAlign: "center",
- singleLine: BoolCast(this.rootDoc._singleLine),
- whiteSpace: this.rootDoc._singleLine ? "nowrap" : "pre-wrap"
+ singleLine,
+ whiteSpace: singleLine ? "nowrap" : "pre-wrap"
};
this._timeout = undefined;
if (!r) return params;
@@ -99,15 +100,16 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
parentStyle.display = "";
parentStyle.alignItems = "";
r.setAttribute("style", "");
- r.style.width = this.rootDoc._singleLine ? "" : "100%";
+ r.style.width = singleLine ? "" : "100%";
r.style.textOverflow = "ellipsis";
r.style.overflow = "hidden";
BigText(r, params);
+ return params;
}
// (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
render() {
- this.fitTextToBox(null);// this causes mobx to trigger re-render when data changes
+ const boxParams = this.fitTextToBox(null);// this causes mobx to trigger re-render when data changes
const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
const missingParams = params?.filter(p => !this.paramsDoc[p]);
params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
@@ -130,9 +132,9 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
paddingBottom: NumCast(this.rootDoc._yPadding),
width: this.props.PanelWidth(),
height: this.props.PanelHeight(),
- whiteSpace: this.rootDoc._singleLine ? "pre" : "pre-wrap"
+ whiteSpace: boxParams.singleLine ? "pre" : "pre-wrap"
}} >
- <span style={{ width: this.layoutDoc._singleLine ? "" : "100%" }} ref={action((r: any) => this.fitTextToBox(r))}>
+ <span style={{ width: boxParams.singleLine ? "" : "100%" }} ref={action((r: any) => this.fitTextToBox(r))}>
{label.startsWith("#") ? (null) : label.replace(/([^a-zA-Z])/g, "$1\u200b")}
</span>
</div>
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 437d29f39..7fd289a97 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -89,7 +89,6 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
openLinkTargetOnRight = (e: React.MouseEvent) => {
const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
alias._isLinkButton = undefined;
- alias._layerTags = undefined;
alias.layoutKey = "layout";
this.props.addDocTab(alias, "add:right");
}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index a9d33f161..ccac66996 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -54,6 +54,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
}}>
<input className="linkDescriptionPopup-input"
+ onKeyDown={e => e.stopPropagation()}
onKeyPress={e => e.key === "Enter" && this.onDismiss(true)}
placeholder={"(Optional) Enter link description..."}
onChange={(e) => this.descriptionChanged(e)}>
diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss
index 06ae466f0..3febbcecb 100644
--- a/src/client/views/nodes/LinkDocPreview.scss
+++ b/src/client/views/nodes/LinkDocPreview.scss
@@ -1,4 +1,4 @@
- .linkDocPreview {
+.linkDocPreview {
position: absolute;
pointer-events: all;
background-color: lightblue;
@@ -8,11 +8,14 @@
border-bottom: 8px solid white;
border-right: 8px solid white;
z-index: 2004;
+
.linkDocPreview-inner {
background-color: white;
width: 100%;
height: 100%;
pointer-events: none;
+ display: flex;
+ flex-direction: column;
.linkDocPreview-info {
height: 37px;
@@ -21,6 +24,7 @@
.linkDocPreview-buttonBar {
float: right;
}
+
.linkDocPreview-title {
padding-right: 4px;
float: left;
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 375434933..152288d8b 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -93,7 +93,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
} else {
this._linkSrc = anchor;
const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
- this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+ this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
}
this._toolTipText = "";
}
@@ -108,8 +108,11 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
}
nextHref = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => {
- this._linkDoc = undefined;
- this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+ const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+ if (nextHrefInd !== this._hrefInd) {
+ this._linkDoc = undefined;
+ this._hrefInd = nextHrefInd;
+ }
}), true);
}
@@ -179,7 +182,6 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
moveDocument={returnFalse}
rootSelected={returnFalse}
styleProvider={this.props.docProps?.styleProvider}
- layerProvider={this.props.docProps?.layerProvider}
docViewPath={returnEmptyDoclist}
ScreenToLocalTransform={Transform.Identity}
isDocumentActive={returnFalse}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 59e39e051..9f1c019fe 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -471,7 +471,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
onMarqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
setupMoveUpEvents(this, e, action(e => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
@@ -489,13 +489,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return this.addDocument(doc, annotationKey);
}
+ pointerEvents = () => {
+ return this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ?
+ "all" :
+ SnappingManager.GetIsDragging() ?
+ undefined : "none";
+ }
@computed get annotationLayer() {
- const pe = this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" :
- SnappingManager.GetIsDragging() ? undefined : "none";
return <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
{this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
<Annotation key={`${anno[Id]}-annotation`} {...this.props}
- fieldKey={this.annotationKey} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />)}
+ fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />)}
</div>;
}
@@ -537,7 +541,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
infoWidth = () => this.props.PanelWidth() / 5;
infoHeight = () => this.props.PanelHeight() / 5;
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-
+ savedAnnotations = () => this._savedAnnotations;
render() {
const renderAnnotations = (docFilters?: () => string[]) => (null);
// bcz: commmented this out. Otherwise, any documents that are rendered with an InfoWindow of a marker
@@ -618,7 +622,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
addDocument={this.addDocumentWrapper}
docView={this.props.docViewPath().lastElement()}
finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
+ savedAnnotations={this.savedAnnotations}
annotationLayer={this._annotationLayer.current}
mainCont={this._mainCont.current} />}
</div>
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 0d5fedb7b..db7dcb09d 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { Doc } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
-import { emptyFunction, OmitKeys, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, OmitKeys, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
@@ -79,7 +79,7 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
addDocument={(doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, "data", d), true as boolean)}
renderDepth={this.props.renderDepth + 1}
viewType={CollectionViewType.Stacking}
- pointerEvents="all"
+ pointerEvents={returnAll}
/>
</div>
<hr />
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 69662d53a..f2ca6c96e 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -2,28 +2,30 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
-import { CreateImage } from "../nodes/WebBoxRenderer";
import "pdfjs-dist/web/pdf_viewer.css";
import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Cast, NumCast, StrCast, ImageCast } from '../../../fields/Types';
+import { Id } from '../../../fields/FieldSymbols';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField, PdfField } from "../../../fields/URLField";
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
import { KeyCodes } from '../../util/KeyCodes';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
import { Colors } from '../global/globalEnums';
+import { CreateImage } from "../nodes/WebBoxRenderer";
import { AnchorMenu } from '../pdf/AnchorMenu';
import { PDFViewer } from "../pdf/PDFViewer";
import { SidebarAnnos } from '../SidebarAnnos';
import { FieldView, FieldViewProps } from './FieldView';
+import { ImageBox } from './ImageBox';
import "./PDFBox.scss";
-import React = require("react");
-import { Id } from '../../../fields/FieldSymbols';
import { VideoBox } from './VideoBox';
+import React = require("react");
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -61,10 +63,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
}
}
+
+ if (oldDiv.className === "pdfBox-ui" ||
+ oldDiv.className === "pdfViewerDash-overlay-inking") {
+ newDiv.style.display = "none";
+ }
+ if (newDiv && newDiv.style) newDiv.style.overflow = "hidden";
if (oldDiv instanceof HTMLCanvasElement) {
const canvas = oldDiv;
const img = document.createElement('img'); // create a Image Element
- img.src = canvas.toDataURL(); //image source
+ img.src = canvas.toDataURL(); //image sourcez
img.style.width = canvas.style.width;
img.style.height = canvas.style.height;
const newCan = newDiv as HTMLCanvasElement;
@@ -74,7 +82,72 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
}
+ crop = (region: Doc | undefined, addCrop?: boolean) => {
+ if (!region) return;
+ const cropping = Doc.MakeCopy(region, true);
+ Doc.GetProto(region).lockedPosition = true;
+ Doc.GetProto(region).title = "region:" + this.rootDoc.title;
+ Doc.GetProto(region).isPushpin = true;
+ this.addDocument(region);
+
+ const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+ const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
+ newDiv.style.width = (this.layoutDoc[WidthSym]()).toString();
+ newDiv.style.height = (this.layoutDoc[HeightSym]()).toString();
+ this.replaceCanvases(docViewContent, newDiv);
+ const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
+
+ const anchx = NumCast(cropping.x);
+ const anchy = NumCast(cropping.y);
+ const anchw = cropping[WidthSym]() * (this.props.scaling?.() || 1);
+ const anchh = cropping[HeightSym]() * (this.props.scaling?.() || 1);
+ const viewScale = 1;
+ cropping.title = "crop: " + this.rootDoc.title;
+ cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
+ cropping.y = NumCast(this.rootDoc.y);
+ cropping._width = anchw;
+ cropping._height = anchh;
+ cropping.isLinkButton = undefined;
+ const croppingProto = Doc.GetProto(cropping);
+ croppingProto.annotationOn = undefined;
+ croppingProto.isPrototype = true;
+ croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ croppingProto.type = DocumentType.IMG;
+ croppingProto.layout = ImageBox.LayoutString("data");
+ croppingProto.data = new ImageField(Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png"));
+ croppingProto["data-nativeWidth"] = anchw;
+ croppingProto["data-nativeHeight"] = anchh;
+ if (addCrop) {
+ DocUtils.MakeLink({ doc: region }, { doc: cropping }, "cropped image", "");
+ }
+ this.props.bringToFront(cropping);
+
+ CreateImage(
+ "",
+ document.styleSheets,
+ htmlString,
+ anchw,
+ anchh,
+ NumCast(region.y) * this.props.PanelWidth() / NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]),
+ NumCast(region.x) * this.props.PanelWidth() / NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]),
+ 4
+ ).then
+ ((data_url: any) => {
+ VideoBox.convertDataUri(data_url, region[Id]).then(
+ returnedfilename => setTimeout(action(() => {
+ croppingProto.data = new ImageField(returnedfilename);
+ }), 500));
+ })
+ .catch(function (error: any) {
+ console.error('oops, something went wrong!', error);
+ });
+
+
+ return cropping;
+ }
+
updateIcon = () => {
+ return; // currently we render pdf icons as text labels
const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
newDiv.style.width = (this.layoutDoc[WidthSym]()).toString();
@@ -125,7 +198,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
getAnchor = () => {
const anchor =
- AnchorMenu.Instance?.GetAnchor() ??
+ this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ??
Docs.Create.TextanchorDocument({
title: StrCast(this.rootDoc.title + "@" + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)),
y: NumCast(this.layoutDoc._scrollTop),
@@ -241,13 +314,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
</>;
const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`;
const curPage = NumCast(this.Document._curPage) || 1;
- return !this.props.isContentActive() ? (null) :
+ return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? (null) :
<div className="pdfBox-ui" onKeyDown={e => [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true}
onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}>
<div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<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)} />
+ onKeyDown={e => {
+ e.stopPropagation();
+ e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey);
+ }} />
<button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" />
</button>
@@ -269,6 +345,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div className="pdfBox-pageNums">
<input value={curPage} style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }}
onChange={e => this.Document._curPage = Number(e.currentTarget.value)}
+ onKeyDown={e => e.stopPropagation()}
onClick={action(() => this._pageControls = !this._pageControls)} />
{this._pageControls ? pageBtns : (null)}
</div>
@@ -327,7 +404,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
transform: `scale(${scale})`,
position: "absolute",
transformOrigin: "top left",
- top: 0
+ top: 0,
}}>
<PDFViewer {...this.props}
rootDoc={this.rootDoc}
@@ -343,7 +420,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- startupLive={true}
+ crop={this.crop}
ContentScaling={returnOne}
/>
</div>
@@ -385,4 +462,5 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
return this.renderTitleBox;
}
-} \ No newline at end of file
+}
+
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index b97e90bd9..c7108d290 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -15,6 +15,7 @@
width: 100%;
height: 100%;
position: relative;
+
.videoBox-viewer {
display: flex;
flex-direction: column;
@@ -23,6 +24,7 @@
opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
background: $dark-gray;
}
+
.inkingCanvas-paths-markers {
opacity: 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround
}
@@ -52,17 +54,20 @@
width: 100%;
z-index: -1; // 0; // logically this should be 0 (or unset) which would give us transparent brush strokes over videos. However, this makes Chrome crawl to a halt
position: absolute;
+
video {
width: auto;
- height: 100%;
- display: flex;
+ height: 100%;
+ display: flex;
margin: auto;
}
}
-.videoBox-content, .videoBox-content-interactive, .videoBox-content-fullScreen {
+.videoBox-content,
+.videoBox-content-interactive,
+.videoBox-content-fullScreen {
width: 100%;
- height: 100%;
+ height: 100%;
left: 0px;
}
@@ -148,6 +153,7 @@
pointer-events: all;
padding-right: 5px;
cursor: pointer;
+
&:hover {
background-color: $medium-gray;
}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a98cd8cf4..5fe1ad5ed 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -153,6 +153,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// plays video
@action public Play = (update: boolean = true) => {
+ if (this._playRegionTimer) return;
this._playing = true;
const eleTime = this.player?.currentTime || 0;
if (this.timeline) {
@@ -204,7 +205,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._youtubePlayer && SelectionManager.DeselectAll(); // if we don't deselect the player, then we get an annoying YouTube spinner I guess telling us we're paused.
this._playTimer = undefined;
this.updateTimecode();
- if (!this._finished) clearTimeout(this._playRegionTimer); // if paused in the middle of playback, prevents restart on next play
+ if (!this._finished) {
+ clearTimeout(this._playRegionTimer); // if paused in the middle of playback, prevents restart on next play
+ }
+ this._playRegionTimer = undefined;
}
// toggles video full screen
@@ -339,7 +343,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect;
if (Number.isFinite(this.player!.duration)) {
this.rawDuration = this.player!.duration;
- }
+ } else this.rawDuration = NumCast(this.dataDoc[this.fieldKey + "-duration"]);
});
@@ -584,6 +588,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
clearTimeout(this._playRegionTimer);
+ this._playRegionTimer = undefined;
if (Number.isNaN(this.player?.duration)) {
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
}
@@ -810,7 +815,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
onPointerDown={(e) => { e.stopPropagation(); this.toggleMute(); }}>
<FontAwesomeIcon icon={this._muted ? "volume-mute" : "volume-up"} />
</div>
- <input type="range" step="0.1" min="0" max="1" value={this._muted ? 0 : this._volume}
+ <input type="range" style={{ width: `min(25%, 50px)` }} step="0.1" min="0" max="1" value={this._muted ? 0 : this._volume}
className="toolbar-slider volume"
onPointerDown={(e: React.PointerEvent) => e.stopPropagation()}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setVolume(Number(e.target.value))}
@@ -856,7 +861,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
playLink={this.playLink}
PanelHeight={this.timelineHeight}
rawDuration={this.rawDuration}
- vref={this._videoRef}
/>
</div>;
}
@@ -866,12 +870,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return <div className="videoBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
}
+ savedAnnotations = () => this._savedAnnotations;
render() {
const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad;
return (<div className="videoBox" onContextMenu={this.specificContextMenu} ref={this._mainCont}
style={{
- pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
+ pointerEvents: this.layoutDoc._lockedPosition ? "none" : undefined,
borderRadius,
overflow: this.props.docViewPath?.().slice(-1)[0].fitWidth ? "auto" : undefined
}} onWheel={e => { e.stopPropagation(); e.preventDefault(); }}>
@@ -913,7 +918,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
containerOffset={this.marqueeOffset}
addDocument={this.addDocWithTimecode}
finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
+ savedAnnotations={this.savedAnnotations}
annotationLayer={this._annotationLayer.current}
mainCont={this._mainCont.current}
/>}
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index ff38e37dd..d8dd074a5 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -5,17 +5,18 @@
height: 100%;
position: relative;
display: flex;
- .webBox-sideResizer {
+
+ .webBox-sideResizer {
position: absolute;
width: 100%;
height: 100%;
cursor: ew-resize;
background: darkgray;
}
- .webBox-background {
+
+ .webBox-background {
width: 100%;
height: 100%;
- background: lightGray;
}
.webBox-ui {
@@ -120,7 +121,7 @@
box-shadow: $standard-box-shadow;
transition: 0.2s;
- &:hover{
+ &:hover {
filter: brightness(0.85);
}
}
@@ -155,11 +156,15 @@
position: absolute;
top: 0;
left: 0;
+ cursor: text;
+ padding: 15px;
+ height: 100%
}
.webBox-cont {
pointer-events: none;
}
+
.webBox-cont,
.webBox-cont-interactive {
padding: 0vw;
@@ -193,6 +198,7 @@
top: 0;
left: 0;
overflow: auto;
+
.webBox-innerContent {
position: relative;
}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 5c72417cc..10974ca03 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -15,7 +15,6 @@ import { TraceMobx } from "../../../fields/util";
import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { KeyCodes } from "../../util/KeyCodes";
import { ScriptingGlobals } from "../../util/ScriptingGlobals";
import { SnappingManager } from "../../util/SnappingManager";
import { undoBatch } from "../../util/UndoManager";
@@ -41,7 +40,6 @@ import React = require("react");
const { CreateImage } = require("./WebBoxRenderer");
const _global = (window /* browser */ || global /* node */) as any;
const htmlToText = require("html-to-text");
-
@observer
export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
@@ -53,7 +51,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _disposers: { [name: string]: IReactionDisposer } = {};
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _keyInput = React.createRef<HTMLInputElement>();
- private _initialScroll: Opt<number>;
+ private _initialScroll: Opt<number> = NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop));
+ private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
private _sidebarRef = React.createRef<SidebarAnnos>();
private _searchRef = React.createRef<HTMLInputElement>();
private _searchString = "";
@@ -69,19 +68,22 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private _iframeClick: HTMLIFrameElement | undefined = undefined;
@observable private _iframe: HTMLIFrameElement | null = null;
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight, 1500);
+ @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight);
@computed get _url() { return this.webField?.toString() || ""; }
@computed get _urlHash() { return this._url ? WebBox.urlHash(this._url) + "" : ""; }
- @computed get scrollHeight() { return this._scrollHeight; }
+ @computed get scrollHeight() { return Math.max(this.layoutDoc[HeightSym](), this._scrollHeight); }
@computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); }
@computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
@computed get webField() { return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; }
- @computed get webThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; }
+ @computed get webThumb() {
+ return this.props.thumbShown?.() &&
+ ImageCast(this.layoutDoc["thumb-frozen"],
+ ImageCast(this.layoutDoc.thumbScrollTop === this.layoutDoc._scrollTop && this.layoutDoc.thumbNativeWidth === NumCast(this.layoutDoc.nativeWidth) &&
+ this.layoutDoc.thumbNativeHeight === NumCast(this.layoutDoc.nativeHeight) ? this.layoutDoc.thumb : undefined))?.url;
+ }
constructor(props: any) {
super(props);
- Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
- Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
runInAction(() => this._webUrl = this._url); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
}
@@ -103,14 +105,52 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
return true;
}
+ @action
setScrollPos = (pos: number) => {
if (!this._outerRef.current || this._outerRef.current.scrollHeight < pos) {
- setTimeout(() => this.setScrollPos(pos), 250);
+ if (this._webPageHasBeenRendered) setTimeout(() => this.setScrollPos(pos), 250);
} else {
this._outerRef.current.scrollTop = pos;
this._initialScroll = undefined;
}
}
+
+ lockout = false;
+ updateThumb = async () => {
+ const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href;
+ const scrollTop = NumCast(this.layoutDoc._scrollTop);
+ const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
+ const nativeHeight = nativeWidth * this.props.PanelHeight() / this.props.PanelWidth();
+ if (!this.lockout && this._iframe && !imageBitmap && (scrollTop !== this.layoutDoc.thumbScrollTop || nativeWidth !== this.layoutDoc.thumbNativeWidth || nativeHeight !== this.layoutDoc.thumbNativeHeight)) {
+ var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
+ if (!htmlString) {
+ htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
+ }
+ this.layoutDoc.thumb = undefined;
+ this.lockout = true; // lock to prevent multiple thumb updates.
+ CreateImage(
+ this._webUrl.endsWith("/") ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl,
+ this._iframe.contentDocument?.styleSheets ?? [],
+ htmlString,
+ nativeWidth,
+ nativeHeight,
+ scrollTop
+ ).then
+ ((data_url: any) => {
+ VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
+ returnedfilename => setTimeout(action(() => {
+ this.lockout = false;
+ this.layoutDoc.thumb = new ImageField(returnedfilename);
+ this.layoutDoc.thumbScrollTop = scrollTop;
+ this.layoutDoc.thumbNativeWidth = nativeWidth;
+ this.layoutDoc.thumbNativeHeight = nativeHeight;
+ }), 500));
+ })
+ .catch(function (error: any) {
+ console.error('oops, something went wrong!', error);
+ });
+ }
+ }
async componentDidMount() {
this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
@@ -120,45 +160,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`);
this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`);
});
- reaction(() => this.props.isSelected(),
+ reaction(() => this.props.isSelected() || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document),
async (selected) => {
if (selected) {
this._webPageHasBeenRendered = true;
- setTimeout(action(() => {
- this._scrollHeight = Math.max(this.scrollHeight, this._iframe?.contentDocument?.body.scrollHeight || 0);
- if (this._initialScroll !== undefined) {
- this.setScrollPos(this._initialScroll);
- }
- }));
} else if ((!this.props.isContentActive() || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail)
!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty
LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty.
- const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href;
- if (this._iframe && !imageBitmap) {
- var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
- if (!htmlString) {
- htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
- }
- this.layoutDoc.thumb = undefined;
- const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
- CreateImage(
- this._webUrl.endsWith("/") ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl,
- this._iframe.contentDocument?.styleSheets ?? [],
- htmlString,
- nativeWidth,
- nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
- NumCast(this.layoutDoc._scrollTop)
- ).then
- ((data_url: any) => {
- VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
- returnedfilename => setTimeout(action(() => this.layoutDoc.thumb = new ImageField(returnedfilename)), 500));
- })
- .catch(function (error: any) {
- console.error('oops, something went wrong!', error);
- });
- }
+ this.updateThumb();
}
- }, { fireImmediately: this.props.isSelected() });
+ }, { fireImmediately: this.props.isSelected() || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegree(this.props.Document) ? true : false) });
this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight,
autoHeight => {
@@ -186,21 +197,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
}
- var quickScroll = true;
this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop),
(scrollTop) => {
- if (quickScroll) this._initialScroll = scrollTop;
- else {
- const viewTrans = StrCast(this.Document._viewTransition);
- const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
- const durationSecStr = viewTrans.match(/([0-9.]*)s/);
- const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
- this.goTo(scrollTop, duration);
- }
+ const viewTrans = StrCast(this.Document._viewTransition);
+ const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+ const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+ const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+ this.goTo(scrollTop, duration);
},
{ fireImmediately: true }
);
- quickScroll = false;
}
@action componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
@@ -209,8 +215,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
@action
- createTextAnnotation = (sel: Selection, selRange: Range) => {
- if (this._mainCont.current) {
+ createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
+ if (this._mainCont.current && selRange) {
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
@@ -231,6 +237,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// clear selection
if (sel.empty) sel.empty();// Chrome
else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox
+ return this._savedAnnotations;
}
menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected
@@ -243,21 +250,22 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
if (doc !== this.rootDoc && this._outerRef.current) {
const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
- const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1, this.getScrollHeight());
- if (scrollTo !== undefined) {
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1,
+ Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight()));
+ if (scrollTo !== undefined && this._initialScroll === undefined) {
const focusSpeed = smooth ? 500 : 0;
- this._initialScroll !== undefined && (this._initialScroll = scrollTo);
this.goTo(scrollTo, focusSpeed);
return focusSpeed;
+ } else if (!this._webPageHasBeenRendered || !this.getScrollHeight() || this._initialScroll !== undefined) {
+ this._initialScroll = scrollTo;
}
}
- this._initialScroll = NumCast(this.layoutDoc._scrollTop);
- return 0;
+ return undefined;
}
getAnchor = () => {
const anchor =
- AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ??
+ this._getAnchor(this._savedAnnotations) ??
Docs.Create.WebanchorDocument(this._url, {
title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop),
y: NumCast(this.layoutDoc._scrollTop),
@@ -267,15 +275,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return anchor;
}
+ _textAnnotationCreator: (() => ObservableMap<number, HTMLDivElement[]>) | undefined;
+ savedAnnotationsCreator: (() => ObservableMap<number, HTMLDivElement[]>) = () => this._textAnnotationCreator?.() || this._savedAnnotations;
+
@action
iframeUp = (e: PointerEvent) => {
+ this._textAnnotationCreator = undefined;
this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
const sel = this._iframe.contentWindow.getSelection();
if (sel) {
- this.createTextAnnotation(sel, sel.getRangeAt(0));
+ this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX,
e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale);
}
@@ -283,6 +295,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
@action
iframeDown = (e: PointerEvent) => {
+ const sel = this._iframe?.contentWindow?.getSelection?.();
const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
@@ -320,6 +333,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
iframeLoaded = (e: any) => {
const iframe = this._iframe;
+ if (this._initialScroll !== undefined) {
+ this.setScrollPos(this._initialScroll);
+ }
let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend("") + "/corsProxy/", "") ?? this._url.toString());
if (requrlraw !== this._url.toString()) {
if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
@@ -338,8 +354,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (iframe?.contentDocument) {
iframe.contentDocument.addEventListener("pointerup", this.iframeUp);
iframe.contentDocument.addEventListener("pointerdown", this.iframeDown);
- this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
- setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
+ this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument.body.scrollHeight);
+ setTimeout(action(() => this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
iframe.setAttribute("enable-annotation", "true");
iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => {
let href = "";
@@ -370,29 +386,32 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
setDashScrollTop = (scrollTop: number, timeout: number = 250) => {
- const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
- timeout = scrollTop > iframeHeight ? 0 : timeout;
+ const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
this._scrollTimer && clearTimeout(this._scrollTimer);
this._scrollTimer = setTimeout(action(() => {
this._scrollTimer = undefined;
- if (!LinkDocPreview.LinkInfo && this._outerRef.current &&
+ const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
+ if (!LinkDocPreview.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop &&
(!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
- this.layoutDoc._scrollTop = this._outerRef.current.scrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
- }
+ this.layoutDoc.thumb = undefined;
+ this.layoutDoc.thumbScrollTop = undefined;
+ this.layoutDoc.thumbNativeWidth = undefined;
+ this.layoutDoc.thumbNativeHeight = undefined;
+ this.layoutDoc.scrollTop = this._outerRef.current.scrollTop = newScrollTop;
+ } else if (this._outerRef.current) this._outerRef.current.scrollTop = newScrollTop;
}), timeout);
}
goTo = (scrollTop: number, duration: number) => {
if (this._outerRef.current) {
- const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
- scrollTop = scrollTop > iframeHeight + 50 ? iframeHeight : scrollTop;
+ const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
if (duration) {
smoothScroll(duration, [this._outerRef.current], scrollTop);
this.setDashScrollTop(scrollTop, duration);
} else {
this.setDashScrollTop(scrollTop);
}
- }
+ } else this._initialScroll = scrollTop;
}
forward = (checkAvailable?: boolean) => {
@@ -452,6 +471,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (url && !preview) {
this.dataDoc[this.fieldKey + "-history"] = new List<string>([...(history || []), url]);
this.layoutDoc._scrollTop = 0;
+ if (this._webPageHasBeenRendered) {
+ this.layoutDoc.thumb = undefined;
+ this.layoutDoc.thumbScrollTop = undefined;
+ this.layoutDoc.thumbNativeWidth = undefined;
+ this.layoutDoc.thumbNativeHeight = undefined;
+ }
future && (future.length = 0);
}
if (!preview) {
@@ -534,7 +559,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
onMarqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
setupMoveUpEvents(this, e, action(e => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
@@ -543,9 +568,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
}
@action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this._marqueeing = undefined;
this._isAnnotating = false;
this._iframeClick = undefined;
+ const sel = this._iframe?.contentDocument?.getSelection();
+ if (sel?.empty) sel.empty();// Chrome
+ else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
if (x !== undefined && y !== undefined) {
this._setPreviewCursor?.(x, y, false, false);
ContextMenu.Instance.closeMenu();
@@ -559,10 +588,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get urlContent() {
if (this._hackHide || (this.webThumb && (!this._webPageHasBeenRendered && LightboxView.LightboxDoc !== this.rootDoc))) return (null);
+ this.props.thumbShown?.();
const field = this.dataDoc[this.props.fieldKey];
let view;
if (field instanceof HtmlField) {
- view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
+ view = <span className="webBox-htmlSpan" contentEditable onPointerDown={e => e.stopPropagation()} dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._webUrl) : this._webUrl;
view = <iframe className="webBox-iframe" enable-annotation={"true"}
@@ -576,7 +606,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
style={{ pointerEvents: this._scrollTimer ? "none" : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly
ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={"https://crossorigin.me/https://cs.brown.edu"} />;
}
- setTimeout(action(() => this._webPageHasBeenRendered = true));
+ setTimeout(action(() => {
+ this._scrollHeight = Math.max(this._scrollHeight, this._iframe && this._iframe.contentDocument && this._iframe.contentDocument.body ? this._iframe.contentDocument.body.scrollHeight : 0);
+ if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
+ this.setScrollPos(NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop)));
+ }
+ this._webPageHasBeenRendered = true;
+ }));
return view;
}
@@ -610,7 +646,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable _previewNativeWidth: Opt<number> = undefined;
@observable _previewWidth: Opt<number> = undefined;
toggleSidebar = action((preview: boolean = false) => {
- const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
+ var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
+ if (!nativeWidth) {
+ const defaultNativeWidth = this.dataDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[WidthSym]();
+ Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
+ Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * defaultNativeWidth);
+ nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
+ }
const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth + WebBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth;
const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
@@ -620,9 +662,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._showSidebar = true;
}
else {
- this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
+ this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar;
this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * pdfratio / curNativeWidth;
- this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
+ if (!this.layoutDoc._showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) {
+ this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + "-nativeWidth"] = undefined;
+ } else {
+ this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
+ }
}
});
sidebarWidth = () => !this.SidebarShown ? 0 :
@@ -630,8 +676,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
(NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth))
@computed get content() {
- const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
+ const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
+ onKeyDown={e => e.stopPropagation()}
style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
{this.urlContent}
</div>;
@@ -639,10 +686,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get annotationLayer() {
TraceMobx();
- const pe = this.pointerEvents();
return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
{this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
+ <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
</div>;
}
@@ -654,7 +700,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div className="webBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="webBox-overlayButton" title={"search"} />
<input className="webBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged}
- onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey)} />
+ onKeyDown={e => { e.key === "Enter" && this.search(this._searchString, e.shiftKey); e.stopPropagation(); }} />
<button className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" />
</button>
@@ -684,9 +730,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
return this.props.styleProvider?.(doc, props, property);
}
- pointerEvents = () => !this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
+ pointerEvents = () => !this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
+ annotationPointerEvents = () => this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none";
render() {
- const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents ? this.props.pointerEvents as any : undefined;
+ const pointerEvents = this.layoutDoc._lockedPosition ? "none" : this.props.pointerEvents?.() as any;
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const scale = previewScale * (this.props.scaling?.() || 1);
const renderAnnotations = (docFilters?: () => string[]) =>
@@ -709,14 +756,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
- addDocument={this.sidebarAddDocument}
+ addDocument={this.addDocument}
styleProvider={this.childStyleProvider}
childPointerEvents={this.props.isContentActive() ? "all" : undefined}
- pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
+ pointerEvents={this.annotationPointerEvents} />;
return (
<div className="webBox" ref={this._mainCont}
style={{ pointerEvents: this.pointerEvents(), display: this.props.thumbShown?.() ? "none" : undefined }} >
- <div className="webBox-background" />
+ <div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor) }} />
<div className="webBox-container" style={{
width: `calc(${100 / scale}% - ${!this.SidebarShown ? 0 : (this.sidebarWidth() - WebBox.sidebarResizerWidth) / scale * (this._previewWidth ? scale : 1)}px)`,
transform: `scale(${scale})`,
@@ -731,7 +778,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
onPointerDown={this.onMarqueeDown}
>
- <div className={"webBox-innerContent"} style={{ height: NumCast(this.scrollHeight, 50), pointerEvents }}>
+ <div className={"webBox-innerContent"} style={{ height: this._webPageHasBeenRendered ? NumCast(this.scrollHeight, 50) : "100%", pointerEvents }}>
{this.content}
<div style={{ mixBlendMode: "multiply" }}>
{renderAnnotations(this.transparentFilter)}
@@ -752,7 +799,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
addDocument={this.addDocumentWrapper}
docView={this.props.docViewPath().lastElement()}
finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
+ savedAnnotations={this.savedAnnotationsCreator}
annotationLayer={this._annotationLayer.current}
mainCont={this._mainCont.current} /> </div>}
</div >
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index d2b701cf9..d9524dd6e 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -176,7 +176,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
*
* @returns {Promise<String>}
*/
- const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll) {
+ const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll, xoff) {
return new Promise(async function (resolve, reject) {
@@ -240,7 +240,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
// build SVG string
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}'>
- <foreignObject x='0' y='${-scroll}' width='${width}' height='${scroll + height}'>
+ <foreignObject x='${-xoff}' y='${-scroll}' width='${xoff + width}' height='${scroll + height}'>
${contentRootElemString}
</foreignObject>
</svg>`;
@@ -259,11 +259,11 @@ var ForeignHtmlRenderer = function (styleSheets) {
*
* @return {Promise<Image>}
*/
- this.renderToImage = async function (webUrl, html, width, height, scroll) {
+ this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) {
return new Promise(async function (resolve, reject) {
const img = new Image();
console.log("BUILDING SVG for:" + webUrl);
- img.src = await buildSvgDataUri(webUrl, html, width, height, scroll);
+ img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff);
img.onload = function () {
console.log("IMAGE SVG created:" + webUrl);
@@ -279,16 +279,16 @@ var ForeignHtmlRenderer = function (styleSheets) {
*
* @return {Promise<Image>}
*/
- this.renderToCanvas = async function (webUrl, html, width, height, scroll) {
+ this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) {
return new Promise(async function (resolve, reject) {
- const img = await self.renderToImage(webUrl, html, width, height, scroll);
+ const img = await self.renderToImage(webUrl, html, width, height, scroll, xoff);
const canvas = document.createElement('canvas');
- canvas.width = img.width;
- canvas.height = img.height;
+ canvas.width = img.width * oversample;
+ canvas.height = img.height * oversample;
const canvasCtx = canvas.getContext('2d');
- canvasCtx.drawImage(img, 0, 0, img.width, img.height);
+ canvasCtx.drawImage(img, 0, 0, img.width * oversample, img.height * oversample);
resolve(canvas);
});
@@ -301,9 +301,9 @@ var ForeignHtmlRenderer = function (styleSheets) {
*
* @return {Promise<String>}
*/
- this.renderToBase64Png = async function (webUrl, html, width, height, scroll) {
+ this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) {
return new Promise(async function (resolve, reject) {
- const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll);
+ const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample);
resolve(canvas.toDataURL('image/png'));
});
};
@@ -311,8 +311,8 @@ var ForeignHtmlRenderer = function (styleSheets) {
};
-export function CreateImage(webUrl, styleSheets, html, width, height, scroll) {
- const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/docView-hack/g, 'documentView-hack').replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), width, height, scroll);
+export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xoff = 0, oversample = 1) {
+ const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/docView-hack/g, 'documentView-hack').replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), width, height, scroll, xoff, oversample);
return val;
}
diff --git a/src/client/views/nodes/button/ButtonScripts.ts b/src/client/views/nodes/button/ButtonScripts.ts
index f3731b8f9..b4a382faf 100644
--- a/src/client/views/nodes/button/ButtonScripts.ts
+++ b/src/client/views/nodes/button/ButtonScripts.ts
@@ -1,5 +1,6 @@
import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
import { SelectionManager } from "../../../util/SelectionManager";
+import { Colors } from "../../global/globalEnums";
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function changeView(view: string) {
@@ -8,7 +9,8 @@ ScriptingGlobals.add(function changeView(view: string) {
});
// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function toggleOverlay() {
+ScriptingGlobals.add(function toggleOverlay(readOnly?: boolean) {
const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+ if (readOnly) return selected?.Document.z ? Colors.MEDIUM_BLUE : "transparent";
selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("failed");
}); \ No newline at end of file
diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss
index e3e2be515..6cd56f84e 100644
--- a/src/client/views/nodes/button/FontIconBox.scss
+++ b/src/client/views/nodes/button/FontIconBox.scss
@@ -18,12 +18,12 @@
.fontIconBox-label {
color: $white;
- position: relative;
+ bottom: 0;
+ position: absolute;
text-align: center;
font-size: 7px;
letter-spacing: normal;
background-color: inherit;
- margin-top: 5px;
border-radius: 8px;
padding: 0;
width: 100%;
@@ -40,8 +40,10 @@
height: 80%;
}
- &.clickBtn {
+ &.clickBtn,
+ &.clickBtnLabel {
cursor: pointer;
+ flex-direction: column;
&:hover {
background-color: rgba(0, 0, 0, 0.3) !important;
@@ -53,6 +55,12 @@
}
}
+ &.clickBtnLabel {
+ svg {
+ margin-top: -4px;
+ }
+ }
+
&.textBtn {
display: grid;
/* grid-row: auto; */
@@ -64,12 +72,14 @@
justify-items: center;
&:hover {
- filter:brightness(0.85) !important;
+ filter: brightness(0.85) !important;
}
}
- &.tglBtn {
+ &.tglBtn,
+ &.tglBtnLabel {
cursor: pointer;
+ flex-direction: column;
&.switch {
//TOGGLE
@@ -146,10 +156,19 @@
}
}
- &.toolBtn {
+ &.tglBtnLabel {
+ svg {
+ margin-top: -4px;
+ }
+ }
+
+ &.toolBtn,
+ &.toolBtnLabel {
cursor: pointer;
width: 100%;
border-radius: 100%;
+ flex-direction: column;
+ margin-top: -4px;
svg {
width: 60% !important;
@@ -157,6 +176,12 @@
}
}
+ &.toolBtnLabel {
+ svg {
+ margin-top: -4px;
+ }
+ }
+
&.menuBtn {
cursor: pointer !important;
border-radius: 0px;
@@ -166,15 +191,16 @@
width: 45% !important;
height: 45%;
}
-
- &:hover{
+
+ &:hover {
filter: brightness(0.85);
}
}
- &.colorBtn {
+ &.colorBtn,
+ &.colorBtnLabel {
color: black;
cursor: pointer;
flex-direction: column;
@@ -204,6 +230,12 @@
}
}
+ &.colorBtnLabel {
+ svg {
+ margin-top: -4px;
+ }
+ }
+
&.drpdownList {
width: 100%;
display: grid;
@@ -278,10 +310,12 @@
background-color: #e3e3e3;
box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
border-radius: $standard-border-radius;
+
input[type=range]::-webkit-slider-runnable-track {
background: gray;
height: 3px;
}
+
input[type=range]::-webkit-slider-thumb {
box-shadow: 1px 1px 1px #000000;
border: 1px solid #000000;
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index ca13590de..42c650a09 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -5,17 +5,19 @@ import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorState, SketchPicker } from 'react-color';
-import { Doc, StrListCast } from '../../../../fields/Doc';
+import { Doc, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { createSchema } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { WebField } from '../../../../fields/URLField';
-import { Utils } from '../../../../Utils';
+import { aggregateBounds, Utils } from '../../../../Utils';
import { DocumentType } from '../../../documents/DocumentTypes';
+import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
import { SelectionManager } from '../../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { CollectionViewType } from '../../collections/CollectionView';
import { ContextMenu } from '../../ContextMenu';
import { DocComponent } from '../../DocComponent';
@@ -23,6 +25,7 @@ import { EditableView } from '../../EditableView';
import { GestureOverlay } from '../../GestureOverlay';
import { Colors } from '../../global/globalEnums';
import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { InkTranscription } from '../../InkTranscription';
import { StyleProp } from '../../StyleProvider';
import { FieldView, FieldViewProps } from '.././FieldView';
import { RichTextMenu } from '../formattedText/RichTextMenu';
@@ -107,25 +110,27 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
// Script for checking the outcome of the toggle
const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0;
+ const label = !Doc.UserDoc()._showLabel ? (null) :
+ <div className="fontIconBox-label">
+ {this.label}
+ </div>;
+
if (numBtnType === NumButtonType.Slider) {
- const dropdown =
- <div
- className="menuButton-dropdownBox"
- onPointerDown={e => e.stopPropagation()}
- >
- <input type="range" step="1" min={NumCast(this.rootDoc.numBtnMin, 0)} max={NumCast(this.rootDoc.numBtnMax, 100)} value={checkResult}
- className={"menu-slider"} id="slider"
- onPointerDown={() => this._batch = UndoManager.StartBatch("presDuration")}
- onPointerUp={() => this._batch?.end()}
- onChange={e => { e.stopPropagation(); setValue(Number(e.target.value)); }}
- />
- </div>;
+ const dropdown = <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} >
+ <input type="range" step="1" min={NumCast(this.rootDoc.numBtnMin, 0)} max={NumCast(this.rootDoc.numBtnMax, 100)} value={checkResult}
+ className={"menu-slider"} id="slider"
+ onPointerDown={() => this._batch = UndoManager.StartBatch("presDuration")}
+ onPointerUp={() => this._batch?.end()}
+ onChange={e => { e.stopPropagation(); setValue(Number(e.target.value)); }}
+ />
+ </div>;
return (
<div
className={`menuButton ${this.type} ${numBtnType}`}
onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}
>
{checkResult}
+ {label}
{this.rootDoc.dropDownOpen ? dropdown : null}
</div>
);
@@ -229,6 +234,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
const script = ScriptCast(this.rootDoc.script);
+ if (!script) { return null; }
let noviceList: string[] = [];
let text: string | undefined;
@@ -277,7 +283,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
});
const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
+ <div className="fontIconBox-label" style={{ bottom: 0, position: "absolute", color: color, backgroundColor: backgroundColor }}>
{this.label}
</div>;
@@ -331,7 +337,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? "transparent";
const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}>
+ <div className="fontIconBox-label" style={{ color, backgroundColor }}>
{this.label}
</div>;
@@ -342,7 +348,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
</div>;
setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView
return (
- <div className={`menuButton ${this.type} ${this.colorPickerClosed}`}
+ <div className={`menuButton ${this.type + (Doc.UserDoc()._showLabel ? "Label" : "")} ${this.colorPickerClosed}`}
style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
onClick={action(() => this.colorPickerClosed = !this.colorPickerClosed)}
onPointerDown={e => e.stopPropagation()}>
@@ -375,7 +381,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
// Button label
const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}>
+ <div className="fontIconBox-label" style={{ color, backgroundColor }}>
{this.label}
</div>;
@@ -387,13 +393,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
<input type="checkbox"
checked={backgroundColor === Colors.MEDIUM_BLUE}
/>
- <span className="slider round"></span>
+ <span className="slider round" />
</label>
</div>
);
} else {
return (
- <div className={`menuButton ${this.type}`}
+ <div className={`menuButton ${this.type + (Doc.UserDoc()._showLabel ? "Label" : "")}`}
style={{ opacity: 1, backgroundColor, color }}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
{label}
@@ -416,7 +422,8 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
style={{ backgroundColor: "transparent", borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
<div className="menuButton-wrap">
<FontAwesomeIcon className={`menuButton-icon-${this.type}`} icon={this.icon} color={"black"} size={"sm"} />
- {!this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> {this.label} </div>}
+ {!this.label || !Doc.UserDoc()._showLabel ? (null) :
+ <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> {this.label} </div>}
</div>
</div>
);
@@ -443,7 +450,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}>
+ <div className="fontIconBox-label" style={{ color, backgroundColor }}>
{this.label}
</div>;
@@ -455,7 +462,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const buttonText = StrCast(this.rootDoc.buttonText);
// TODO:glr Add label of button type
- let button = this.defaultButton;
+ let button: JSX.Element | null = this.defaultButton;
switch (this.type) {
case ButtonType.TextButton:
@@ -489,7 +496,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
break;
case ButtonType.ToolButton:
button = (
- <div className={`menuButton ${this.type}`} style={{ opacity: 1, backgroundColor, color }}>
+ <div className={`menuButton ${this.type + (Doc.UserDoc()._showLabel ? "Label" : "")}`} style={{ opacity: 1, backgroundColor, color }}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
{label}
</div>
@@ -501,7 +508,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
break;
case ButtonType.ClickButton:
button = (
- <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1 }}>
+ <div className={`menuButton ${this.type + (Doc.UserDoc()._showLabel ? "Label" : "")}`} style={{ color, backgroundColor, opacity: 1 }}>
<FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
{label}
</div>
@@ -523,9 +530,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
}
return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? button :
- <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
- {button}
- </Tooltip>;
+ button !== null ?
+ <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
+ {button}
+ </Tooltip > : null
}
}
@@ -656,13 +664,21 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo
ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- return (editorView ? RichTextMenu.Instance.fontSize : StrCast(Doc.UserDoc().fontSize, "10px")).replace("px", "");
+ return RichTextMenu.Instance.fontSize.replace("px", "");
}
if (typeof size === "number") size = size.toString();
if (size && Number(size).toString() === size) size += "px";
if (editorView) RichTextMenu.Instance.setFontSize(size);
else Doc.UserDoc()._fontSize = size;
});
+ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) {
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ return (editorView ? RichTextMenu.Instance.noAutoLink : Doc.UserDoc().noAutoLink) ? Colors.MEDIUM_BLUE : "transparent";
+ }
+ if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor();
+ else Doc.UserDoc().noAutoLink = Doc.UserDoc().noAutoLink ? true : false;
+});
ScriptingGlobals.add(function toggleBold(checkResult?: boolean) {
const editorView = RichTextMenu.Instance?.TextView?.EditorView;
@@ -694,6 +710,82 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
+export function checkInksToGroup() {
+ // console.log("getting here to inks group");
+ if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+ CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+ // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+ // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other
+ const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => {
+ // console.log(inkDoc.x, inkDoc.y);
+ });
+ });
+ }
+}
+
+export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
+ // TODO nda - if document being added to is a inkGrouping then we can just add to that group
+ if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+ CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+ // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+ const selected = ffView.unprocessedDocs;
+ // loop through selected an get the bound
+ const bounds: { x: number, y: number, width?: number, height?: number }[] = []
+
+ selected.map(action(d => {
+ const x = NumCast(d.x);
+ const y = NumCast(d.y);
+ const width = d[WidthSym]();
+ const height = d[HeightSym]();
+ bounds.push({ x, y, width, height });
+ }))
+
+ const aggregBounds = aggregateBounds(bounds, 0, 0);
+ const marqViewRef = ffView._marqueeViewRef.current;
+
+ // set the vals for bounds in marqueeView
+ if (marqViewRef) {
+ marqViewRef._downX = aggregBounds.x;
+ marqViewRef._downY = aggregBounds.y;
+ marqViewRef._lastX = aggregBounds.r;
+ marqViewRef._lastY = aggregBounds.b;
+ }
+
+ selected.map(action(d => {
+ const dx = NumCast(d.x);
+ const dy = NumCast(d.y);
+ delete d.x;
+ delete d.y;
+ delete d.activeFrame;
+ delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ // calculate pos based on bounds
+ if (marqViewRef?.Bounds) {
+ d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+ d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+ }
+ return d;
+ }));
+ ffView.props.removeDocument?.(selected);
+ // TODO: nda - this is the code to actually get a new grouped collection
+ const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+ if (newCollection) {
+ newCollection.height = newCollection[HeightSym]();
+ newCollection.width = newCollection[WidthSym]();
+ }
+
+ // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+ newCollection && ffView.props.addDocument?.(newCollection);
+ // TODO: nda - will probably need to go through and only remove the unprocessed selected docs
+ ffView.unprocessedDocs = [];
+
+ InkTranscription.Instance.transcribeInk(newCollection, selected, false, ffView);
+ });
+ }
+ CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
+}
+
+
/** INK
* setActiveInkTool
* setStrokeWidth
@@ -701,6 +793,8 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
**/
ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boolean) {
+ createInkGroup();
+
if (checkResult) {
return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ?
Colors.MEDIUM_BLUE : "transparent";
@@ -716,6 +810,10 @@ ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boole
} else if (tool) { // pen or eraser
if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) {
Doc.UserDoc().activeInkTool = InkTool.None;
+ } else if (tool == "write") {
+ // console.log("write mode selected - create groupDoc here!", tool)
+ Doc.UserDoc().activeInkTool = tool;
+ GestureOverlay.Instance.InkShape = "";
} else {
Doc.UserDoc().activeInkTool = tool;
GestureOverlay.Instance.InkShape = "";
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 364be461f..1d8e3a2cf 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -182,7 +182,6 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
removeDocument={this.removeDoc}
isDocumentActive={returnFalse}
isContentActive={this._textBox.props.isContentActive}
- layerProvider={this._textBox.props.layerProvider}
styleProvider={this._textBox.props.styleProvider}
docViewPath={this._textBox.props.docViewPath}
ScreenToLocalTransform={this.getDocTransform}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 34908e54b..bb3791f1e 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -8,35 +8,41 @@ import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
import { ComputedField } from "../../../../fields/ScriptField";
import { Cast, StrCast } from "../../../../fields/Types";
import { DocServer } from "../../../DocServer";
-import { DocUtils } from "../../../documents/Documents";
import { CollectionViewType } from "../../collections/CollectionView";
import "./DashFieldView.scss";
import { FormattedTextBox } from "./FormattedTextBox";
import React = require("react");
+import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils";
+import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
+import { Tooltip } from "@material-ui/core";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export class DashFieldView {
_fieldWrapper: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey);
+
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.textContent = node.attrs.fieldKey.startsWith("#") ? node.attrs.fieldKey : node.attrs.fieldKey + " " + strVal;
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(<DashFieldViewInternal
+ setTimeout(() => ReactDOM.render(<DashFieldViewInternal
fieldKey={node.attrs.fieldKey}
docid={node.attrs.docid}
width={node.attrs.width}
height={node.attrs.height}
hideKey={node.attrs.hideKey}
tbox={tbox}
- />, this._fieldWrapper);
+ />, this._fieldWrapper));
(this as any).dom = this._fieldWrapper;
}
destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
@@ -58,7 +64,6 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
_textBoxDoc: Doc;
_fieldKey: string;
_fieldStringRef = React.createRef<HTMLSpanElement>();
- @observable _showEnumerables: boolean = false;
@observable _dashDoc: Doc | undefined;
constructor(props: IDashFieldViewInternal) {
@@ -77,16 +82,17 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._reactionDisposer?.();
}
- multiValueDelimeter = ";";
+ public static multiValueDelimeter = ";";
+ public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) {
+ const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === "PARAMS" ? textBoxDoc[fieldKey] : "");
+ const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
+ return { boolVal: Cast(fval, "boolean", null), strVal: Field.toString(fval as Field) || "" };
+ }
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
if (this._dashDoc) {
- const dashVal = this._dashDoc[this._fieldKey] ?? this._dashDoc[DataSym][this._fieldKey] ?? (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : "");
- const fval = dashVal instanceof List ? dashVal.join(this.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
- const boolVal = Cast(fval, "boolean", null);
- const strVal = Field.toString(fval as Field) || "";
-
+ const { boolVal, strVal } = DashFieldViewInternal.fieldContent(this._textBoxDoc, this._dashDoc, this._fieldKey);
// field value is a boolean, so use a checkbox or similar widget to display it
if (boolVal === true || boolVal === false) {
return <input
@@ -109,10 +115,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
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;
- e.stopPropagation();
- }));
+ r?.addEventListener("pointerdown", action(e => e.stopPropagation()));
}} >
{strVal}
</span>;
@@ -124,7 +127,6 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
@action
fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database.
- e.ctrlKey && DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]);
this.updateText(span.textContent!, true);
e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view
}
@@ -142,7 +144,6 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
@action
updateText = (nodeText: string, forceMatch: boolean) => {
- this._showEnumerables = false;
if (nodeText) {
const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText;
@@ -154,7 +155,6 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
(options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
if (modText) {
// elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
- DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []);
Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true);
} // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
else if (nodeText.startsWith(":=")) {
@@ -166,7 +166,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true);
} else {
- const splits = newText.split(this.multiValueDelimeter);
+ const splits = newText.split(DashFieldViewInternal.multiValueDelimeter);
if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
const strVal = splits.length > 1 ? new List<string>(splits) : newText;
if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
@@ -178,18 +178,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
}
}
- // display a collection of all the enumerable values for this field
- onPointerDownEnumerables = async (e: any) => {
- e.stopPropagation();
- const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
- collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "add:right");
- }
-
-
- // clicking on the label creates a pivot view collection of all documents
- // in the same collection. The pivot field is the fieldKey of this label
- onPointerDownLabelSpan = (e: any) => {
- e.stopPropagation();
+ createPivotForField = (e: React.MouseEvent) => {
let container = this.props.tbox.props.ContainingCollectionView;
while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
container = container.props.ContainingCollectionView;
@@ -208,6 +197,16 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
}
}
+
+ // clicking on the label creates a pivot view collection of all documents
+ // in the same collection. The pivot field is the fieldKey of this label
+ onPointerDownLabelSpan = (e: any) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, (e) => {
+ DashFieldViewMenu.createFieldView = this.createPivotForField;
+ DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16);
+ });
+ }
+
render() {
return <div className="dashFieldView" style={{
width: this.props.width,
@@ -220,8 +219,40 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
{this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent}
- {!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
-
</div >;
}
+}
+@observer
+export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
+ static Instance: DashFieldViewMenu;
+ static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
+ constructor(props: any) {
+ super(props);
+ DashFieldViewMenu.Instance = this;
+ }
+ @action
+ showFields = (e: React.MouseEvent) => {
+ DashFieldViewMenu.createFieldView(e);
+ DashFieldViewMenu.Instance.fadeOut(true);
+ }
+
+ public show = (x: number, y: number) => {
+ this.jumpTo(x, y, true);
+ const hideMenu = () => {
+ this.fadeOut(true);
+ document.removeEventListener("pointerdown", hideMenu);
+ };
+ document.addEventListener("pointerdown", hideMenu);
+ }
+ render() {
+ const buttons = [
+ <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
+ <button className="antimodeMenu-button" onPointerDown={this.showFields}>
+ <FontAwesomeIcon icon="eye" size="lg" />
+ </button>
+ </Tooltip>,
+ ];
+
+ return this.getElement(buttons);
+ }
} \ 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 29117794e..7a500ac88 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -17,7 +17,8 @@ import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
import { RichTextField } from "../../../../fields/RichTextField";
import { RichTextUtils } from '../../../../fields/RichTextUtils';
-import { Cast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
+import { ComputedField } from '../../../../fields/ScriptField';
+import { Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
@@ -82,7 +83,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
- static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
+ static _globalHighlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
static _highlightStyleSheet: any = addStyleSheet();
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
@@ -244,8 +245,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- const tsel = this._editorView.state.selection.$from;
- //tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box
const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
@@ -284,6 +283,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
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
+ ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
unchanged = false;
}
this._applyingChange = "";
@@ -347,7 +347,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
autoLink = () => {
- if (this._editorView) {
+ if (this._editorView?.state.doc.textContent) {
const newAutoLinks = new Set<Doc>();
const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords);
const f = this._editorView.state.selection.from;
@@ -371,9 +371,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
const str = node.textContent;
const prefix = str.startsWith("@") ? "" : "-";
- this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : "");
- if (str.startsWith("@") && str.length > 1) {
- Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc);
+
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
+ if (!(cfield instanceof ComputedField)) {
+ this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : "");
+ if (str.startsWith("@") && str.length > 1) {
+ Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc);
+ }
}
}
}
@@ -388,16 +392,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
flattened1.forEach((flat, i) => {
const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, autoLinkTerm);
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
- alink = alink ?? (DocListCast(this.Document.links).find(link =>
- Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) &&
- Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target },
- LinkManager.AutoKeywords)!);
- newAutoLinks.add(alink);
const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
const sel = flattened[i];
tr = tr.addMark(sel.from, sel.to, splitter);
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)) {
+ if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
+ alink = alink ?? (DocListCast(this.Document.links).find(link =>
+ Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) &&
+ Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target },
+ LinkManager.AutoKeywords)!);
+ newAutoLinks.add(alink);
const allAnchors = [{ href: Doc.localServerPath(target), title: "a link", anchorId: this.props.Document[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: "auto term", location: "add:right" });
@@ -409,6 +413,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
return tr;
}
+ @action
+ search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
+ if (clear) this.unhighlightSearchTerms();
+ else this.highlightSearchTerms([searchString], bwd!);
+ return true;
+ }
highlightSearchTerms = (terms: string[], backward: boolean) => {
if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
@@ -545,35 +555,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
updateHighlights = () => {
+ const highlights = FormattedTextBox._globalHighlights;
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
- if (FormattedTextBox._highlights.indexOf("Audio Tags") === -1) {
+ if (highlights.indexOf("Audio Tags") === -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "audiotag", { display: "none" }, "");
}
- if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
+ if (highlights.indexOf("Text from Others") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
}
- if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
+ if (highlights.indexOf("My Text") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
}
- if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
+ if (highlights.indexOf("Todo Items") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" });
}
- if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
+ if (highlights.indexOf("Important Items") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" });
}
- if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
+ if (highlights.indexOf("Bold Text") !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror strong > span", { "font-size": "large" }, "");
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror :not(strong > span)", { "font-size": "0px" }, "");
+ }
+ if (highlights.indexOf("Disagree Items") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" });
}
- if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
+ if (highlights.indexOf("Ignore Items") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" });
}
- if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
+ if (highlights.indexOf("By Recent Minute") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
const min = Math.round(Date.now() / 1000 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
setTimeout(this.updateHighlights);
}
- if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
+ if (highlights.indexOf("By Recent Hour") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
const hr = Math.round(Date.now() / 1000 / 60 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
@@ -630,19 +645,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}), icon: icon
});
});
- !Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
const highlighting: ContextMenuProps[] = [];
- const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others"];
+ const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others", "Bold Text"];
const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"];
(Doc.UserDoc().noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
highlighting.push({
- description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+ description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
e.stopPropagation();
- if (FormattedTextBox._highlights.indexOf(option) === -1) {
- FormattedTextBox._highlights.push(option);
+ if (FormattedTextBox._globalHighlights.indexOf(option) === -1) {
+ FormattedTextBox._globalHighlights.push(option);
} else {
- FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
+ FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1);
}
+ runInAction(() => this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join(""));
this.updateHighlights();
}, icon: "expand-arrows-alt"
}));
@@ -752,6 +767,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
+ // TODO: nda -- Look at how link anchors are added
makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {
const state = this._editorView?.state;
if (state) {
@@ -859,7 +875,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on
() => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
- autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight));
+ autoHeight && this.props.setHeight?.((this.props.scaling?.() || 1) * (marginsHeight + Math.max(sidebarHeight, textHeight)));
}, { fireImmediately: true });
this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
@@ -921,6 +937,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._disposers.selected = reaction(() => this.props.isSelected(),
action(selected => {
+ this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join("") : "";
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
@@ -958,7 +975,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}, { fireImmediately: true }
);
quickScroll = undefined;
- setTimeout(this.tryUpdateScrollHeight, 10);
+ this.tryUpdateScrollHeight();
}
pushToGoogleDoc = async () => {
@@ -1189,19 +1206,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
+ const selLoadChar = FormattedTextBox.SelectOnLoadChar;
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
- if (FormattedTextBox.SelectOnLoadChar && this._editorView) {
+ if (selLoadChar && this._editorView) {
const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks);
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
- FormattedTextBox.SelectOnLoadChar = "";
- } else if (curText && !FormattedTextBox.DontSelectInitialText) {
- selectAll(this._editorView!.state, this._editorView?.dispatch);
+ } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) {
+ selectAll(this._editorView.state, this._editorView?.dispatch);
this.startUndoTypingBatch();
+ } else if (this._editorView) {
+ this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
}
FormattedTextBox.DontSelectInitialText = false;
}
@@ -1230,6 +1249,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
onPointerDown = (e: React.PointerEvent): void => {
+ if (this.Document.forceActive) e.stopPropagation();
this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
if ((e.target as any).tagName === "AUDIOTAG") {
e.preventDefault();
@@ -1466,11 +1486,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}, "titler");
}
}
+
onKeyDown = (e: React.KeyboardEvent) => {
- // single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container)
- if (this.rootDoc._singleLine && ((e.key === "Backspace" && !this.dataDoc[this.fieldKey]?.Text) || ["Tab", "Enter"].includes(e.key))) {
- return;
- }
if (e.altKey) {
e.preventDefault();
return;
@@ -1626,11 +1643,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
render() {
TraceMobx();
- const selected = this.props.isSelected();
+ const selected = this.props.isSelected() || this.Document.forceActive;
const active = this.props.isContentActive();
const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
- const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false);
+ const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);
if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
const minimal = this.props.ignoreAutoHeight;
const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
@@ -1652,7 +1669,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
<div className={`formattedTextBox-cont`} ref={this._ref}
style={{
overflow: this.autoHeight ? "hidden" : undefined,
- height: this.props.height || (this.autoHeight && this.props.renderDepth ? "max-content" : undefined),
+ height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? "max-content" : undefined),
background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index c76eda859..fb49b0698 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,20 +1,15 @@
-import { chainCommands, exitCode, joinDown, joinUp, lift, deleteSelection, joinBackward, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn, newlineInCode } from "prosemirror-commands";
-import { liftTarget } from "prosemirror-transform";
+import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands";
import { redo, undo } from "prosemirror-history";
import { Schema } from "prosemirror-model";
-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 { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
-import { Doc, DataSym, DocListCast, AclAugment, AclSelfEdit } from "../../../../fields/Doc";
-import { FormattedTextBox } from "./FormattedTextBox";
-import { Id } from "../../../../fields/FieldSymbols";
-import { Docs } from "../../../documents/Documents";
-import { Utils } from "../../../../Utils";
-import { listSpec } from "../../../../fields/Schema";
-import { List } from "../../../../fields/List";
+import { splitListItem, wrapInList } from "prosemirror-schema-list";
+import { EditorState, TextSelection, Transaction } from "prosemirror-state";
+import { liftTarget } from "prosemirror-transform";
+import { AclAugment, AclSelfEdit, Doc } from "../../../../fields/Doc";
import { GetEffectiveAcl } from "../../../../fields/util";
+import { Utils } from "../../../../Utils";
+import { Docs } from "../../../documents/Documents";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -48,29 +43,6 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
keys[key] = cmd;
}
- /// bcz; Argh!! replace with an onEnter func that conditionally handles Enter
- const addTextBox = (below: boolean, force?: boolean) => {
- if (props.Document.treeViewType === "outline") return true; // bcz: Arghh .. need to determine if this is an treeViewOutlineBox in which case Enter's are ignored..
- const layoutDoc = props.Document;
- const originalDoc = layoutDoc.rootDocument || layoutDoc;
- if (force || props.Document._singleLine) {
- const layoutKey = StrCast(originalDoc.layoutKey);
- const newDoc = Doc.MakeCopy(originalDoc, true);
- const dataField = originalDoc[Doc.LayoutFieldKey(newDoc)];
- newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
- if (below) newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
- else newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
- if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
- newDoc[layoutKey] = originalDoc[layoutKey];
- }
- Doc.GetProto(newDoc).text = undefined;
- FormattedTextBox.SelectOnLoad = newDoc[Id];
- props.addDocument(newDoc);
- return true;
- }
- return false;
- };
-
const canEdit = (state: any) => {
switch (GetEffectiveAcl(props.Document)) {
case AclAugment: return false;
@@ -108,12 +80,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//Commands for lists
bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
+ bind("Ctrl-Tab", () => props.onKey?.(event, props) ? true : true);
+ bind("Alt-Tab", () => props.onKey?.(event, props) ? true : true);
+ bind("Meta-Tab", () => props.onKey?.(event, props) ? true : true);
+ bind("Meta-Enter", () => props.onKey?.(event, props) ? true : true);
bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- /// bcz; Argh!! replace layotuTEmpalteString with a onTab prop conditionally handles Tab);
- if (props.Document._singleLine) {
- if (!props.LayoutTemplateString) return addTextBox(false, true);
- return true;
- }
+ if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
@@ -138,8 +110,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- /// bcz; Argh!! replace with a onShiftTab prop conditionally handles Tab);
- if (props.Document._singleLine) return true;
+ if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -187,14 +158,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return tx;
};
- //Command to create a text document to the right of the selected textbox
- bind("Alt-Enter", () => addTextBox(false, true));
- //Command to create a text document to the bottom of the selected textbox
- bind("Ctrl-Enter", () => addTextBox(true, true));
+ bind("Alt-Enter", () => props.onKey?.(event, props) ? true : true);
+ bind("Ctrl-Enter", () => props.onKey?.(event, props) ? true : true);
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
if (!deleteSelection(state, (tx: Transaction<S>) => {
@@ -216,8 +186,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
//command to break line
bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
- if (addTextBox(true, false)) return true;
+ if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const trange = state.selection.$from.blockRange(state.selection.$to);
@@ -276,8 +246,6 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return false;
});
- // mac && bind("Ctrl-Enter", cmd);
- // bind("Mod-Enter", cmd);
bind("Shift-Enter", cmd);
return keys;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 4814d6e9a..9bc2e5628 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,10 +1,10 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
-import { action, IReactionDisposer, observable, reaction, runInAction, computed } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { lift, wrapIn } from "prosemirror-commands";
-import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
+import { Mark, MarkType, Node as ProsNode, ResolvedPos } from "prosemirror-model";
import { wrapInList } from "prosemirror-schema-list";
import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
@@ -35,6 +35,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
public _brushMap: Map<string, Set<Mark>> = new Map();
@observable private collapsed: boolean = false;
+ @observable private _noLinkActive: boolean = false;
@observable private _boldActive: boolean = false;
@observable private _italicsActive: boolean = false;
@observable private _underlineActive: boolean = false;
@@ -79,6 +80,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this._reaction?.();
}
+ @computed get noAutoLink() { return this._noLinkActive; }
@computed get bold() { return this._boldActive; }
@computed get underline() { return this._underlineActive; }
@computed get italics() { return this._italicsActive; }
@@ -118,7 +120,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
- this._activeFontSize = !activeSizes.length ? "13px" : activeSizes[0];
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, "10px")) : activeSizes[0];
this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "...";
this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "...";
@@ -220,7 +222,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
let activeMarks: MarkType[] = [];
if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks;
- const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
+ const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
//current selection
const { empty, ranges, $to } = this.view.state.selection as TextSelection;
@@ -264,6 +266,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
if (!activeMarks) return;
+ this._noLinkActive = false;
this._boldActive = false;
this._italicsActive = false;
this._underlineActive = false;
@@ -273,6 +276,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
activeMarks.forEach(mark => {
switch (mark.name) {
+ case "noAutoLinkAnchor": this._noLinkActive = true; break;
case "strong": this._boldActive = true; break;
case "em": this._italicsActive = true; break;
case "underline": this._underlineActive = true; break;
@@ -283,6 +287,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
});
}
+ toggleNoAutoLinkAnchor = () => {
+ if (this.view) {
+ const mark = this.view.state.schema.mark(this.view.state.schema.marks.noAutoLinkAnchor);
+ this.setMark(mark, this.view.state, this.view.dispatch, false);
+ this.TextView.autoLink();
+ this.view.focus();
+ }
+ }
toggleBold = () => {
if (this.view) {
const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong);
@@ -310,10 +322,17 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
setFontSize = (fontSize: string) => {
if (this.view) {
- const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
- this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
- this.view.focus();
- this.updateMenu(this.view, undefined, this.props);
+ if (this.view.state.selection.from === 1 && this.view.state.selection.empty &&
+ (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
+ this.TextView.dataDoc.fontSize = fontSize;
+ this.view.focus();
+ this.updateMenu(this.view, undefined, this.props);
+ } else {
+ const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
+ this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+ this.view.focus();
+ this.updateMenu(this.view, undefined, this.props);
+ }
}
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 427e05edb..8851d52e4 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -348,7 +348,7 @@ export class RichTextRules {
this.Document[DataSym].tags = `${tags + "#" + tag + ':'}`;
}
const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
+ return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(" ");
}),
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 1f6ce014f..2fde5c7ba 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -48,6 +48,20 @@ export const marks: { [index: string]: MarkSpec } = {
return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
}
},
+ noAutoLinkAnchor: {
+ attrs: {},
+ inclusive: false,
+ parseDOM: [{
+ tag: "div", getAttrs(dom: any) {
+ return {
+ noAutoLink: dom.getAttribute("data-noAutoLink"),
+ };
+ }
+ }],
+ toDOM(node: any) {
+ return ["span", { "data-noAutoLink": "true" }, 0];
+ }
+ },
// :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text,
// and a title for use in menus and hover. `title`
// defaults to the empty string. Rendered and parsed as an `<a>`
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 06932d145..a0a2dd4f8 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -1078,11 +1078,13 @@
.miniPres {
cursor: grab;
position: absolute;
- right: 10;
- top: 10;
+ top: 0;
+ left: 0;
opacity: 0.1;
transition: all 0.4s;
color: $white;
+ width: 100%;
+ height: 100%;
}
.miniPres:hover {
@@ -1101,7 +1103,6 @@
align-items: center;
display: flex;
position: absolute;
- right: 10px;
transition: all 0.2s;
.presPanel-button-text {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 30ad43562..e32f2826f 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -12,7 +12,7 @@ import { PrefetchProxy } from "../../../../fields/Proxy";
import { listSpec } from "../../../../fields/Schema";
import { ScriptField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnFalse, returnOne, returnTrue } from '../../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils';
import { Docs } from "../../../documents/Documents";
import { DocumentType } from "../../../documents/DocumentTypes";
import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
@@ -47,17 +47,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* @param renderDoc
* @param layoutDoc
*/
- static renderEffectsDoc(renderDoc: any, layoutDoc: Doc) {
+ static renderEffectsDoc(renderDoc: any, layoutDoc: Doc, presDoc: Doc) {
const effectProps = {
- left: layoutDoc.presEffectDirection === PresEffect.Left,
- right: layoutDoc.presEffectDirection === PresEffect.Right,
- top: layoutDoc.presEffectDirection === PresEffect.Top,
- bottom: layoutDoc.presEffectDirection === PresEffect.Bottom,
+ left: presDoc.presEffectDirection === PresEffect.Left,
+ right: presDoc.presEffectDirection === PresEffect.Right,
+ top: presDoc.presEffectDirection === PresEffect.Top,
+ bottom: presDoc.presEffectDirection === PresEffect.Bottom,
opposite: true,
- delay: layoutDoc.presTransition,
+ delay: presDoc.presTransition,
// when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc,
};
- switch (layoutDoc.presEffect) {
+ switch (presDoc.presEffect) {
case PresEffect.Zoom: return (<Zoom {...effectProps}>{renderDoc}</Zoom>);
case PresEffect.Fade: return (<Fade {...effectProps}>{renderDoc}</Fade>);
case PresEffect.Flip: return (<Flip {...effectProps}>{renderDoc}</Flip>);
@@ -71,7 +71,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
public static EffectsProvider(layoutDoc: Doc, renderDoc: any) {
return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ?
- PresBox.renderEffectsDoc(renderDoc, layoutDoc)
+ PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex])
:
renderDoc;
}
@@ -90,13 +90,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable _expandBoolean: boolean = false;
private _disposers: { [name: string]: IReactionDisposer } = {};
+
+ @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView
@observable private transitionTools: boolean = false;
@observable private newDocumentTools: boolean = false;
@observable private progressivizeTools: boolean = false;
@observable private openMovementDropdown: boolean = false;
@observable private openEffectDropdown: boolean = false;
@observable private presentTools: boolean = false;
- @computed get childDocs() { return DocListCast(this.rootDoc[this.fieldKey]); }
+ // @computed get childDocs() { return DocListCast(this.rootDoc[this.fieldKey]); }
+ // TODO: [AL] add childDocs() and treeMap fields
+ // @computed get childDocs() { return DocListCast(this.rootDoc.presentationaLinearizedDocuments); }
+ //_treeViewMap:Map<number[], Doc>
+ @computed get childDocs() { return this.layoutDoc._viewType === CollectionViewType.Tree ? DocListCast(this.rootDoc.presentationLinearizedDocuments) : DocListCast(this.rootDoc[this.fieldKey]); }
+ @observable _treeViewMap: Map<number, Doc> = new Map<number, Doc>();
+
@computed get tagDocs() {
const tagDocs: Doc[] = [];
for (const doc of this.childDocs) {
@@ -132,6 +140,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{ field: "string", data: Doc.name, container: Doc.name });
}
this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox
+
}
@computed get selectedDocumentView() {
if (SelectionManager.Views().length) return SelectionManager.Views()[0];
@@ -212,6 +221,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
//TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
+ // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
// No more frames in current doc and next slide is defined, therefore move to next slide
nextSlide = (activeNext: Doc) => {
const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null);
@@ -297,6 +307,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.startTempMedia(targetDoc, activeItem);
}
if (targetDoc) {
+ Doc.linkFollowHighlight((targetDoc.annotationOn instanceof Doc) ? [targetDoc, targetDoc.annotationOn] : targetDoc);
targetDoc && runInAction(() => {
if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0;
else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500;
@@ -308,7 +319,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (!group) this._selectedArray.clear();
this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array
- if ([CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.layoutDoc._viewType as any) && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
+ this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
this.onHideDocument(); //Handles hide after/before
}
});
@@ -324,7 +335,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
} else if (bestTarget.type === DocumentType.COMPARISON) {
bestTarget._clipWidth = activeItem.presPinClipWidth;
} else if (bestTarget.type === DocumentType.VID) {
- bestTarget._currentTimecode = activeItem.presPinTimecode;
+ bestTarget._currentTimecode = activeItem.presStartTime;
} else {
bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
bestTarget._panX = activeItem.presPinViewX;
@@ -346,7 +357,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
navigateToElement = async (curDoc: Doc) => {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
- const srcContext = Cast(targetDoc.context, Doc, null);
+ const srcContext = Cast(targetDoc.context, Doc, null) ?? Cast(Cast(targetDoc.annotationOn, Doc, null)?.context, Doc, null);
const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined;
const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc);
@@ -374,7 +385,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
self._eleArray.splice(0, self._eleArray.length, ...eleViewCache);
});
const openInTab = (doc: Doc, finished?: () => void) => {
- collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, ":left");
+ collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, "");
this.layoutDoc.presCollection = targetDoc;
// this still needs some fixing
setTimeout(resetSelection, 500);
@@ -387,13 +398,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// If openDocument is selected then it should open the document for the user
if (activeItem.openDocument) {
LightboxView.SetLightboxDoc(targetDoc);
+ // openInTab(targetDoc);
} else if (curDoc.presMovement === PresMovement.Pan && targetDoc) {
LightboxView.SetLightboxDoc(undefined);
- await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
+ await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
} else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) {
LightboxView.SetLightboxDoc(undefined);
//awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right
+ await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right
}
// After navigating to the document, if it is added as a presPinView then it will
// adjust the pan and scale to that of the pinView when it was added.
@@ -587,27 +599,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* The method called to open the presentation as a minimized view
*/
@action
- updateMinimize = () => {
- const docView = DocumentManager.Instance.getDocumentView(this.layoutDoc);
+ updateMinimize = async () => {
if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
- console.log("case 1");
this.layoutDoc.presStatus = PresStatus.Edit;
Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
CollectionDockingView.AddSplit(this.rootDoc, "right");
- } else if ((true || this.layoutDoc.context) && docView) {
- console.log("case 2");
+ } else {
this.layoutDoc.presStatus = PresStatus.Edit;
clearTimeout(this._presTimer);
const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250);
this.rootDoc.y = pt[1] + 10;
- this.rootDoc._height = 35;
- this.rootDoc._width = 250;
- docView.props.removeDocument?.(this.layoutDoc);
+ this.rootDoc._height = 30;
+ this.rootDoc._width = 248;
Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
- } else {
- console.log("case 3");
- // TODO glr: fix this case
+ this.props.removeDocument?.(this.layoutDoc);
}
}
@@ -700,7 +706,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
panelHeight = () => this.props.PanelHeight() - 40;
- isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && this.props.layerProvider?.(this.layoutDoc) !== false) &&
+ isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && !this.layoutDoc._lockedPosition) &&
(this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
/**
@@ -1089,7 +1095,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
updateEffectDirection = (effect: any, all?: boolean) => {
const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
array.forEach((doc) => {
- const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ const tagDoc = doc;// Cast(doc.presentationTargetDoc, Doc, null);
switch (effect) {
case PresEffect.Left:
tagDoc.presEffectDirection = PresEffect.Left;
@@ -1115,7 +1121,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
updateEffect = (effect: any, all?: boolean) => {
const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
array.forEach((doc) => {
- const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ const tagDoc = doc;//Cast(doc.presentationTargetDoc, Doc, null);
switch (effect) {
case PresEffect.Bounce:
tagDoc.presEffect = PresEffect.Bounce;
@@ -1152,7 +1158,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const zoom = activeItem.presZoom ? NumCast(activeItem.presZoom) * 100 : 75;
let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2;
if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration);
- const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None';
+ const effect = this.activeItem.presEffect ? this.activeItem.presEffect : 'None';
activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom';
return (
<div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}>
@@ -1204,6 +1210,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="ribbon-property">
<input className="presBox-input"
type="number" value={transitionSpeed}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e) => this.setTransitionTime(e.target.value))} /> s
</div>
<div className="ribbon-propertyUpDown">
@@ -1243,6 +1250,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="ribbon-property">
<input className="presBox-input"
type="number" value={duration}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e) => this.setDurationTime(e.target.value))} /> s
</div>
<div className="ribbon-propertyUpDown">
@@ -1274,12 +1282,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{effect}
<FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} />
<div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? "grid" : "none" }} onPointerDown={e => e.stopPropagation()}>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.None || !targetDoc.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Flip ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>Flip</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Rotate ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>Rotate</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Bounce ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>Bounce</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Roll ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>Roll</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.None || !this.activeItem.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Flip ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>Flip</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Rotate ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>Rotate</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Bounce ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>Bounce</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Roll ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>Roll</div>
</div>
</div>
<div className="ribbon-doubleButton" style={{ display: effect === 'None' ? "none" : "inline-flex" }}>
@@ -1289,11 +1297,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</div>
<div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection === PresEffect.Center || !targetDoc.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: this.activeItem.presEffectDirection === PresEffect.Center || !this.activeItem.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip>
</div>
</div>}
<div className="ribbon-final-box">
@@ -1308,7 +1316,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get effectDirection(): string {
let effect = '';
- switch (this.targetDoc.presEffectDirection) {
+ switch (this.activeItem.presEffectDirection) {
case 'left': effect = "Enter from left"; break;
case 'right': effect = "Enter from right"; break;
case 'top': effect = "Enter from top"; break;
@@ -1324,8 +1332,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
this.updateMovement(activeItem.presMovement, true);
- this.updateEffect(targetDoc.presEffect, true);
- this.updateEffectDirection(targetDoc.presEffectDirection, true);
+ this.updateEffect(activeItem.presEffect, true);
+ this.updateEffectDirection(activeItem.presEffectDirection, true);
array.forEach((doc) => {
const curDoc = Cast(doc, Doc, null);
const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
@@ -1356,7 +1364,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
activeItem.presPinView = true;
activeItem.presPinViewScroll = scroll;
} else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
+ activeItem.presStartTime = targetDoc._currentTimecode;
+ activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1;
} else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) {
const x = targetDoc._panX;
const y = targetDoc._panY;
@@ -1378,7 +1387,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const scroll = targetDoc._scrollTop;
activeItem.presPinViewScroll = scroll;
} else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
+ activeItem.presStartTime = targetDoc._currentTimecode;
+ activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1;
} else if (targetDoc.type === DocumentType.COMPARISON) {
const clipWidth = targetDoc._clipWidth;
activeItem.presPinClipWidth = clipWidth;
@@ -1408,6 +1418,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewX)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} />
</div>
</div>
@@ -1417,6 +1428,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewY)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} />
</div>
</div>
@@ -1426,6 +1438,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewScale)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} />
</div>
</div>
@@ -1446,6 +1459,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewScroll)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} />
</div>
</div>
@@ -1492,6 +1506,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
type="number" value={NumCast(activeItem.presStartTime)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presStartTime = Number(e.target.value); })}
/>
</div>
@@ -1510,6 +1525,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<div id={"endTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
<input className="presBox-input"
+ onKeyDown={e => e.stopPropagation()}
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
type="number" value={NumCast(activeItem.presEndTime)}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presEndTime = Number(e.target.value); })}
@@ -2403,6 +2419,63 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
else this.pauseAutoPres();
}
+ @action
+ prevClicked = (e: PointerEvent) => {
+ this.back();
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
+ }
+
+ @action
+ nextClicked = (e: PointerEvent) => {
+ this.next();
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
+ }
+ @undoBatch
+ @action
+ exitClicked = (e: PointerEvent) => {
+ this.updateMinimize();
+ this.layoutDoc.presStatus = PresStatus.Edit;
+ clearTimeout(this._presTimer);
+ }
+
+ @action
+ startMarqueeCreateSlide = () => {
+ PresBox.startMarquee = true;
+ }
+
+ AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+ var indexNum = 0;
+ for (let i = 0; i < index.length; i++) {
+ indexNum += (index[i] * (10 ** (-i)));
+ }
+ this._treeViewMap.set(indexNum, treeViewDoc);
+ this.props.Document.presentationLinearizedDocuments = new List<Doc>(this.sort(this._treeViewMap)); // this is a flat array of Docs
+ return this.childDocs;
+ }
+
+ RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+ var indexNum = 0;
+ for (let i = 0; i < index.length; i++) {
+ indexNum += (index[i] * (10 ** (-i)));
+ }
+ console.log(String(index), treeViewDoc)
+ this._treeViewMap.delete(indexNum);
+ const overlay = (Doc.UserDoc().myOverlayDocs as Doc);
+ if (!DocListCast(overlay[Doc.LayoutFieldKey(overlay)]).includes(this.rootDoc)) {
+ this.props.Document.presentationLinearizedDocuments = new List<Doc>(this.sort(this._treeViewMap));
+ }
+ return this.childDocs;
+ }
+
+ // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1])
+ sort = (treeViewMap: Map<number, Doc>) => [...treeViewMap.entries()].sort().map(kv => kv[1]);
+
render() {
// calling this method for keyEvents
this.isPres;
@@ -2415,54 +2488,74 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return CurrentUserUtils.OverlayDocs.includes(this.rootDoc) ?
<div className="miniPres">
<div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
- <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.layoutDoc.presLoop = !this.layoutDoc.presLoop, false, false)}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
<div className="presPanel-divider"></div>
- <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div>
- <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip>
- <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onClick={() => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-right"} /></div>
+ <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}><FontAwesomeIcon icon={"arrow-left"} /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button"
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip>
+ <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}><FontAwesomeIcon icon={"arrow-right"} /></div>
<div className="presPanel-divider"></div>
- <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0, this.activeItem)}><b>1</b></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}><b>1</b></div></Tooltip>
<div className="presPanel-button-text">
Slide {this.itemIndex + 1} / {this.childDocs.length}
{this.playButtonFrames}
</div>
- <div className="presPanel-divider"></div>
- <div className="presPanel-button-text" onClick={undoBatch(action(() => { this.updateMinimize(); this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); }))}>EXIT</div>
+ <div className="presPanel-divider" />
+ <div className="presPanel-button-text" onPointerDown={e =>
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>EXIT</div>
</div>
- </div>
+ </div >
:
<div className="presBox-cont" style={{ minWidth: CurrentUserUtils.OverlayDocs.includes(this.layoutDoc) ? 240 : undefined }} >
{this.topPanel}
{this.toolbar}
{this.newDocumentToolbarDropdown}
<div className="presBox-listCont">
- {mode !== CollectionViewType.Invalid ?
- <CollectionView {...this.props}
- ContainingCollectionDoc={this.props.Document}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.panelHeight}
- childIgnoreNativeSize={true}
- moveDocument={returnFalse}
- childFitWidth={returnTrue}
- childOpacity={returnOne}
- childLayoutTemplate={this.childLayoutTemplate}
- filterAddDocument={this.addDocumentFilter}
- removeDocument={returnFalse}
- dontRegisterView={true}
- focus={this.selectElement}
- ScreenToLocalTransform={this.getTransform}
- />
- : (null)
+ <div className="Slide" style={{ height: `calc(100% - 30px)` }}>
+ {mode !== CollectionViewType.Invalid ?
+ <CollectionView {...this.props}
+ ContainingCollectionDoc={this.props.Document}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.panelHeight}
+ childIgnoreNativeSize={true}
+ moveDocument={returnFalse}
+ childFitWidth={returnTrue}
+ childOpacity={returnOne}
+ childLayoutTemplate={this.childLayoutTemplate}
+ filterAddDocument={this.addDocumentFilter}
+ removeDocument={returnFalse}
+ dontRegisterView={true}
+ focus={this.selectElement}
+ ScreenToLocalTransform={this.getTransform}
+ // TODO: [AL] implement AddToMap, RemFromMap functions (outside this)
+ AddToMap={this.AddToMap}
+ RemFromMap={this.RemFromMap}
+ hierarchyIndex={[]}
+ /> : (null)
+ }
+ </div>
+
+ { // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack
+ <Tooltip title={<div className="dash-tooltip">{"Click on document to pin to presentaiton or make a marquee selection to pin your desired view"}</div>}>
+ <button className="add-slide-button" onPointerDown={this.startMarqueeCreateSlide}>
+ + NEW SLIDE
+ </button>
+ </Tooltip>
}
</div>
</div>;
}
}
-ScriptingGlobals.add(function lookupPresBoxField(container: Doc, field: string, data: Doc) {
- if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data);
- if (field === 'presCollapsedHeight') return [CollectionViewType.Tree || CollectionViewType.Stacking].includes(container._viewType as any) ? 35 : 31;
- if (field === 'presStatus') return container.presStatus;
- if (field === '_itemIndex') return container._itemIndex;
- if (field === 'presBox') return container;
+// this func communicates with PresBoxElement to send information of the doc
+ScriptingGlobals.add(function lookupPresBoxField(presBoxDoc: Doc, field: string, presEleDoc: Doc) {
+ if (field === 'indexInPres') return DocListCast(presBoxDoc._viewType === CollectionViewType.Tree ? presBoxDoc.presentationLinearizedDocuments : presBoxDoc[StrCast(presBoxDoc.presentationFieldKey)]).indexOf(presEleDoc);
+ if (field === 'presCollapsedHeight') return [CollectionViewType.Tree || CollectionViewType.Stacking].includes(presBoxDoc._viewType as any) ? 35 : 31;
+ if (field === 'presStatus') return presBoxDoc.presStatus;
+ if (field === '_itemIndex') return presBoxDoc._itemIndex;
+ if (field === 'presBox') return presBoxDoc;
return undefined;
}); \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index a178be910..789a6d9f3 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -195,6 +195,47 @@ $slide-active: #5B9FDD;
box-shadow: 0 0 0px 1.5px $dark-blue;
}
+.presItem-slide.group {
+ border-radius: 5px;
+}
+
+.presItem-slide.activeGroup {
+ border-radius: 5px;
+ box-shadow: 0 0 0px 1.5px $dark-blue;
+}
+
+.presItem-groupSlideContainer {
+ position: absolute;
+ /* grid-row: 3; */
+ /* grid-column: 1/8; */
+ top: 28;
+ display: block;
+ background: #92adb9;
+ width: 100%;
+ height: 10px;
+ border-radius: 0px 0px 5px 5px;
+ height: calc(100% - 28px);
+ display: grid;
+ grid-template-rows: auto auto auto;
+ grid-template-columns: 100%;
+ justify-items: left;
+ align-items: center;
+}
+
+.presItem-groupSlide {
+ position: relative;
+ background-color: #d5dce2;
+ border-radius: 5px;
+ height: calc(100% - 7px);
+ width: calc(100% - 20px);
+ left: 15px;
+ /* height: 20px; */
+ /* width: calc(100% - 19px); */
+ display: flex;
+ grid-template-rows: 16px 10px auto;
+ grid-template-columns: max-content max-content max-content max-content auto;
+}
+
.presItem-multiDrag {
font-family: Roboto;
font-weight: 600;
@@ -230,6 +271,37 @@ $slide-active: #5B9FDD;
box-shadow: 0 0 0px 1.5px $dark-blue;
}
-// .presItem-slide:hover {
-// background: $slide-hover;
-// } \ No newline at end of file
+.expandButton {
+ cursor: pointer;
+ position: absolute;
+ border-radius: 100px;
+ bottom: 0;
+ left: -18;
+ z-index: 300;
+ width: 15px;
+ height: 15px;
+ display: flex;
+ font-size: 12px;
+ justify-self: center;
+ align-self: center;
+ background-color: #92adb9;
+ color: white;
+ justify-content: center;
+ align-items: center;
+ transition: 0.2s;
+ margin-right: 3px;
+}
+
+.expandButton:hover {
+ background-color: rgba(0, 0, 0, 1);
+ transform: scale(1.2);
+}
+
+.presItem-groupNum {
+ color: #d5dce2;
+ position: absolute;
+ left: -15px;
+ top: 1;
+ font-weight: 600;
+ font-size: 12;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index f4dc9b615..bbabcb2de 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -2,9 +2,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
-import { DataSym, Doc, Opt } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, Opt } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
-import { Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils";
import { DocUtils } from "../../../documents/Documents";
import { DocumentType } from "../../../documents/DocumentTypes";
@@ -80,7 +80,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
Document={this.targetDoc}
DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
styleProvider={this.styleProvider}
- layerProvider={this.props.layerProvider}
docViewPath={returnEmptyDoclist}
rootSelected={returnTrue}
addDocument={returnFalse}
@@ -108,6 +107,39 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>;
}
+ @computed get renderGroupSlides() {
+ const childDocs = DocListCast(this.targetDoc.data);
+ const groupSlides = childDocs.map((doc: Doc, ind: number) =>
+ <div className="presItem-groupSlide" onClick={(e) => {
+ console.log("Clicked on slide with index: ", ind);
+ e.stopPropagation();
+ e.preventDefault();
+ PresBox.Instance.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+ this.presExpandDocumentClick();
+ }}>
+ <div className="presItem-groupNum">
+ {`${ind + 1}.`}
+ </div>
+ {/* style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }} */}
+ <div className="presItem-name">
+ <EditableView
+ ref={this._titleRef}
+ editing={undefined}
+ contents={doc.title}
+ overflow={'ellipsis'}
+ GetValue={() => StrCast(doc.title)}
+ SetValue={(value: string) => {
+ doc.title = !value.trim().length ? "-untitled-" : value;
+ return true;
+ }}
+ />
+ </div>
+ </div>
+ );
+ return (
+ groupSlides
+ );
+ }
@computed get duration() {
let durationInS: number;
if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; }
@@ -290,6 +322,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presColorBool: boolean = presBoxColor ? (presBoxColor !== Colors.WHITE && presBoxColor !== "transparent") : false;
const targetDoc: Doc = this.targetDoc;
const activeItem: Doc = this.rootDoc;
+ const isGroup: boolean = BoolCast(targetDoc._isGroup);
return (
<div className={`presItem-container`}
key={this.props.Document[Id] + this.indexInPres}
@@ -304,8 +337,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
}}
onDoubleClick={action(e => {
- this.toggleProperties();
- PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true);
+ if (isGroup) {
+ this.rootDoc.presExpandGroup = !this.rootDoc.presExpandGroup;
+ } else {
+ this.toggleProperties();
+ PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true);
+ }
})}
onPointerOver={this.onPointerOver}
onPointerLeave={this.onPointerLeave}
@@ -322,7 +359,69 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presItem-number">
{`${this.indexInPres + 1}.`}
</div>}
- {miniView ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`}
+ {isGroup ?
+ <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "activeGroup" : "group"}`}
+ style={{
+ backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+ boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined
+ }}>
+ <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
+ <EditableView
+ ref={this._titleRef}
+ editing={!isSelected ? false : undefined}
+ contents={activeItem.title}
+ overflow={'ellipsis'}
+ GetValue={() => StrCast(activeItem.title)}
+ SetValue={this.onSetValue}
+ />
+ </div>
+ <div className={"presItem-slideButtons"}>
+ <Tooltip title={<><div className="dash-tooltip">{"Update view"}</div></>}>
+ <div className="slideButton"
+ onClick={() => this.updateView(targetDoc, activeItem)}
+ style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V</div>
+ </Tooltip>
+ {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{activeItem.groupWithUp ? "Ungroup" : "Group with up"}</div></>}>
+ <div className="slideButton"
+ onClick={() => activeItem.groupWithUp = !activeItem.groupWithUp}
+ style={{
+ zIndex: 1000 - this.indexInPres,
+ fontWeight: 700,
+ backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined,
+ height: activeItem.groupWithUp ? 53 : 18,
+ transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined
+ }}>
+ <div style={{ transform: activeItem.groupWithUp ? "rotate(180deg) translate(0, -17.5px)" : "rotate(0deg)" }}>
+ <FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </div>
+ </Tooltip>}
+ <Tooltip title={<><div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}</div></>}><div className={"slideButton"} onClick={e => { e.stopPropagation(); this.presExpandDocumentClick(); }}>
+ <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? "eye-slash" : "eye"} onPointerDown={e => e.stopPropagation()} />
+ </div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Remove from presentation"}</div></>}><div
+ className={"slideButton"}
+ onClick={this.removeItem}>
+ <FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} />
+ </div></Tooltip>
+ <div className="group"></div>
+ </div>
+ <div className="presItem-groupSlideContainer" style={{ top: 28, height: 'calc(100% - 28px)' }}>
+ {this.rootDoc.presExpandGroup ? this.renderGroupSlides : (null)}
+ </div>
+ <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div>
+ <div className="expandButton"
+ onClick={e => {
+ if (isGroup) {
+ this.rootDoc.presExpandGroup = !this.rootDoc.presExpandGroup;
+ }
+ }}
+ >
+ <FontAwesomeIcon icon={"caret-down"} style={{ transform: this.rootDoc.presExpandGroup ? "rotate(180deg)" : "rotate(0deg)" }} />
+ </div>
+ </div>
+ : (null)}
+ {miniView || isGroup ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`}
style={{
backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index b1d1d8293..5bdce273d 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -16,39 +16,30 @@ interface IAnnotationProps extends FieldViewProps {
dataDoc: Doc;
fieldKey: string;
showInfo: (anno: Opt<Doc>) => void;
- pointerEvents?: string;
+ pointerEvents?: () => Opt<string>;
}
@observer
export
class Annotation extends React.Component<IAnnotationProps> {
render() {
- return DocListCast(this.props.anno.textInlineAnnotations).map(a => <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />);
+ return <div>
+ {DocListCast(this.props.anno.textInlineAnnotations).map(a =>
+ <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
+ )}
+ </div>;
}
}
interface IRegionAnnotationProps extends IAnnotationProps {
document: Doc;
- pointerEvents?: string;
+ pointerEvents?: () => Opt<string>;
}
@observer
class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
- private _disposers: { [name: string]: IReactionDisposer } = {};
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- @observable _brushed: boolean = false;
@computed get annoTextRegion() { return Cast(this.props.document.annoTextRegion, Doc, null) || this.props.document; }
- componentDidMount() {
- this._disposers.brush = reaction(
- () => this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion),
- brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0)
- );
- }
-
- componentWillUnmount() {
- Object.values(this._disposers).forEach(disposer => disposer?.());
- }
-
@undoBatch
deleteAnnotation = () => {
const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]);
@@ -91,15 +82,27 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
}
render() {
- return (<div className="htmlAnnotation" onPointerEnter={() => this.props.showInfo(this.props.anno)} onPointerLeave={() => this.props.showInfo(undefined)} onPointerDown={this.onPointerDown} ref={this._mainCont}
+ const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion);
+ return (<div className="htmlAnnotation" ref={this._mainCont}
+ onPointerEnter={action(() => {
+ Doc.BrushDoc(this.props.anno);
+ this.props.showInfo(this.props.anno);
+ })}
+ onPointerLeave={action(() => {
+ Doc.UnBrushDoc(this.props.anno);
+ this.props.showInfo(undefined);
+ })}
+ onPointerDown={this.onPointerDown}
style={{
left: NumCast(this.props.document.x),
top: NumCast(this.props.document.y),
width: NumCast(this.props.document._width),
height: NumCast(this.props.document._height),
- opacity: this._brushed ? 0.5 : undefined,
- pointerEvents: this.props.pointerEvents as any,
- backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor),
+ opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
+ pointerEvents: this.props.pointerEvents?.() as any,
+ outline: brushed === Doc.DocBrushStatus.linkHighlighted ? "solid 1px lightBlue" : undefined,
+ backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? "orange" :
+ StrCast(this.props.document.backgroundColor),
}} >
</div>);
}
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 390aed1e0..822af6a68 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -1,5 +1,3 @@
-
-
.pdfViewer-content {
height: 100%;
width: 100%;
@@ -8,33 +6,42 @@
top: 0;
left: 0;
}
-.pdfViewerDash, .pdfViewerDash-interactive {
+
+.pdfViewerDash,
+.pdfViewerDash-interactive {
position: absolute;
width: 100%;
height: 100%;
top: 0;
- left:0;
+ left: 0;
position: absolute;
overflow-y: auto;
overflow-x: hidden;
transform-origin: top left;
-
+
// .canvasWrapper {
// transform: scale(0.75);
// transform-origin: top left;
// }
.textLayer {
opacity: unset;
- mix-blend-mode: multiply;// bcz: makes text fuzzy!
+ mix-blend-mode: multiply; // bcz: makes text fuzzy!
+
span {
padding-right: 5px;
padding-bottom: 4px;
}
}
- .textLayer ::selection { background: #ACCEF7; } // should match the backgroundColor in createAnnotation()
+
+ .textLayer ::selection {
+ background: #ACCEF7;
+ }
+
+ // should match the backgroundColor in createAnnotation()
.textLayer .highlight {
background-color: yellow;
}
+
.textLayer .highlight.selected {
background-color: orange;
}
@@ -43,32 +50,32 @@
position: relative;
border: unset;
}
+
.pdfViewerDash-text-selected {
- // position: relative; // bcz: this breaks auto-scrolling using the inline search box
+ // position: relative; // bcz: this breaks auto-scrolling using the inline search box
z-index: -1;
- .textLayer{
- pointer-events: all;
- user-select: text;
+
+ .textLayer {
+ pointer-events: all;
+ user-select: text;
}
}
+
.pdfViewerDash-text {
transform-origin: top left;
+
.textLayer {
will-change: transform;
}
}
- .pdfViewerDash-overlay, .pdfViewerDash-overlay-inking {
+ .pdfViewerDash-overlay {
transform-origin: left top;
position: absolute;
top: 0px;
left: 0px;
display: inline-block;
- width:100%;
- pointer-events: all;
- }
- .pdfViewerDash-overlay {
- pointer-events: none;
+ width: 100%;
}
.pdfViewerDash-overlayAnno {
@@ -81,7 +88,7 @@
border-radius: 5px;
display: block;
}
-
+
.pdfViewerDash-annotationLayer {
position: absolute;
transform-origin: left top;
@@ -90,12 +97,13 @@
pointer-events: none;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
}
+
.pdfViewerDash-waiting {
width: 70%;
height: 70%;
- margin : 15%;
+ margin: 15%;
transition: 0.4s opacity ease;
- opacity: 0.7;
+ opacity: 0.7;
position: absolute;
z-index: 10;
}
@@ -103,4 +111,4 @@
.pdfViewerDash-interactive {
pointer-events: all;
-} \ No newline at end of file
+} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b55b2cf44..f706be6c6 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,18 +1,15 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, Field, HeightSym, Opt } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
-import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
-import { PdfField } from "../../../fields/URLField";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils";
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, returnFalse, smoothScroll, Utils } from "../../../Utils";
import { DocUtils } from "../../documents/Documents";
-import { Networking } from "../../Network";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { CompiledScript, CompileScript } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
import { SharingManager } from "../../util/SharingManager";
import { SnappingManager } from "../../util/SnappingManager";
@@ -43,11 +40,11 @@ interface IViewerProps extends FieldViewProps {
fieldKey: string;
pdf: Pdfjs.PDFDocumentProxy;
url: string;
- startupLive: boolean;
loaded?: (nw: number, nh: number, np: number) => void;
setPdfViewer: (view: PDFViewer) => void;
ContentScaling?: () => number;
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+ crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
}
/**
@@ -58,12 +55,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
static _annotationStyle: any = addStyleSheet();
@observable private _pageSizes: { width: number, height: number }[] = [];
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
@observable private _marqueeing: number[] | undefined;
@observable private _textSelecting = true;
@observable private _showWaiting = true;
- @observable private _showCover = false;
- @observable private _zoomed = 1;
@observable private _overlayAnnoInfo: Opt<Doc>;
@observable private Index: number = -1;
@@ -74,18 +68,18 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
+ public _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
_mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _selectionText: string = "";
private _downX: number = 0;
private _downY: number = 0;
- private _coverPath: any;
private _lastSearch = false;
private _viewerIsSetup = false;
private _ignoreScroll = false;
private _initialScroll: Opt<number>;
private _forcedScroll = true;
-
+ @observable isAnnotating = false;
// key where data is stored
@computed get allAnnotations() {
return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined);
@@ -93,30 +87,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
@computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
componentDidMount = async () => {
- // change the address to be the file address of the PNG version of each page
- // file address of the pdf
- const { url: { href } } = Cast(this.props.dataDoc[this.props.fieldKey], PdfField)!;
- const { url: relative } = this.props;
- if (relative.includes("/pdfs/")) {
- const pathComponents = relative.split("/pdfs/")[1].split("/");
- const coreFilename = pathComponents.pop()!.split(".")[0];
- const params: any = {
- coreFilename,
- pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.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: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.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: "" };
- }
runInAction(() => this._showWaiting = true);
- this.props.startupLive && this.setupPdfJsViewer();
+ this.setupPdfJsViewer();
this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0);
this._disposers.autoHeight = reaction(() => this.props.layoutDoc._autoHeight,
@@ -182,7 +154,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
if (doc !== this.props.rootDoc && mainCont) {
const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight, NumCast(this.props.Document.scrollHeight));
- if (scrollTo !== undefined) {
+ if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
focusSpeed = 500;
if (!this._pdfViewer) this._initialScroll = scrollTo;
@@ -194,6 +166,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
return focusSpeed;
}
+ crop = (region: Doc | undefined, addCrop?: boolean) => {
+ return this.props.crop(region, addCrop);
+ }
@action
setupPdfJsViewer = async () => {
@@ -203,23 +178,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.props.setPdfViewer(this);
await this.initialLoad();
- this._disposers.filterScript = reaction(
- () => ScriptCast(this.props.Document.filterScript),
- action(scriptField => {
- const oldScript = this._script.originalScript;
- this._script = scriptField?.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
- if (this._script.originalScript !== oldScript) {
- this.Index = -1;
- }
- }),
- { fireImmediately: true });
-
this.createPdfViewer();
}
pagesinit = () => {
if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
- runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1);
+ runInAction(() => this._pdfViewer.currentScaleValue = this.props.layoutDoc._viewScale = 1);
this.gotoPage(NumCast(this.props.Document._curPage, 1));
}
document.removeEventListener("pagesinit", this.pagesinit);
@@ -228,7 +192,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
() => Math.abs(NumCast(this.props.Document._scrollTop)),
(pos) => {
if (!this._ignoreScroll) {
- (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
+ this._showWaiting && this.setupPdfJsViewer();
const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
@@ -369,10 +333,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
this._setPreviewCursor?.(e.clientX, e.clientY, true, false);
}
- if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) {
+ if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) {
this.props.select(false);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
+ this.isAnnotating = true;
if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) {
this._textSelecting = false;
document.addEventListener("pointermove", this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called
@@ -389,6 +354,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
finishMarquee = (x?: number, y?: number) => {
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+ this.isAnnotating = false;
this._marqueeing = undefined;
this._textSelecting = true;
document.removeEventListener("pointermove", this.onSelectMove);
@@ -398,6 +365,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
onSelectEnd = (e: PointerEvent): void => {
+ this.isAnnotating = false;
clearStyleSheetRules(PDFViewer._annotationStyle);
this.props.select(false);
document.removeEventListener("pointermove", this.onSelectMove);
@@ -419,13 +387,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
const rect = clientRects.item(i);
if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) {
const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
+ const pdfScale = NumCast(this.props.layoutDoc._viewScale, 1);
const annoBox = document.createElement("div");
annoBox.className = "marqueeAnnotator-annotationBox";
// transforms the positions from screen onto the pdf div
- annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / this._zoomed + this._mainCont.current.scrollTop).toString();
- annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString();
- annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString();
- annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString();
+ annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / pdfScale + this._mainCont.current.scrollTop).toString();
+ annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / pdfScale).toString();
+ annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / pdfScale).toString();
+ annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / pdfScale).toString();
this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top));
}
}
@@ -455,20 +424,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
- getCoverImage = () => {
- if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) {
- setTimeout((() => {
- this.props.Document._height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
- Doc.SetNativeWidth(this.props.Document, (Doc.NativeWidth(this.props.Document) || 0) * this._coverPath.height / this._coverPath.width);
- }).bind(this), 0);
- }
- const nativeWidth = Doc.NativeWidth(this.props.Document);
- const nativeHeight = Doc.NativeHeight(this.props.Document);
- const resolved = Utils.prepend(this._coverPath.path);
- return <img key={resolved} src={resolved} onError={action(() => this._coverPath.path = "http://www.cs.brown.edu/~bcz/face.gif")} onLoad={action(() => this._showWaiting = false)}
- style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />;
- }
-
@action
onZoomWheel = (e: React.WheelEvent) => {
if (this.props.isContentActive(true)) {
@@ -476,22 +431,21 @@ export class PDFViewer extends React.Component<IViewerProps> {
if (e.ctrlKey) {
const curScale = Number(this._pdfViewer.currentScaleValue);
this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - curScale * e.deltaY / 1000));
- this._zoomed = Number(this._pdfViewer.currentScaleValue);
+ this.props.layoutDoc._viewScale = Number(this._pdfViewer.currentScaleValue);
}
}
}
- pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
+ pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
@computed get annotationLayer() {
- const pe = this.pointerEvents();
- return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
+ return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }} ref={this._annotationLayer}>
{this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
+ <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
</div>;
}
@computed get overlayInfo() {
- return !this._overlayAnnoInfo || this._overlayAnnoInfo.author === Doc.CurrentUserEmail ? (null) :
+ return !this._overlayAnnoInfo ? (null) :
<div className="pdfViewerDash-overlayAnno" style={{ top: NumCast(this._overlayAnnoInfo.y), left: NumCast(this._overlayAnnoInfo.x) }}>
<div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}>
{this._overlayAnnoInfo.author + " " + Field.toString(this._overlayAnnoInfo.creationDate as Field)}
@@ -500,7 +454,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
- overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
+ overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1));
panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
@@ -513,62 +467,66 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
return this.props.styleProvider?.(doc, props, property);
}
+
+ renderAnnotations = (docFilters?: () => string[], dontRender?: boolean) =>
+ <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ isAnnotationOverlay={true}
+ fieldKey={this.props.fieldKey + "-annotations"}
+ setPreviewCursor={this.setPreviewCursor}
+ PanelHeight={this.panelHeight}
+ PanelWidth={this.panelWidth}
+ dropAction={"alias"}
+ select={emptyFunction}
+ ContentScaling={this.contentZoom}
+ bringToFront={emptyFunction}
+ docFilters={docFilters || this.basicFilter}
+ styleProvider={this.childStyleProvider}
+ dontRenderDocuments={dontRender}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.overlayTransform}
+ renderDepth={this.props.renderDepth + 1}
+ />
+ @computed get overlayTransparentAnnotations() { return this.renderAnnotations(this.transparentFilter, false); }
+ @computed get overlayOpaqueAnnotations() { return this.renderAnnotations(this.opaqueFilter, false); }
+ @computed get overlayClickableAnnotations() {
+ return <div style={{ height: NumCast(this.props.rootDoc.scrollHeight) }}>
+ {this.renderAnnotations(undefined, true)}
+ </div>;
+ }
@computed get overlayLayer() {
- const renderAnnotations = (docFilters?: () => string[]) =>
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- isAnnotationOverlay={true}
- fieldKey={this.props.fieldKey + "-annotations"}
- setPreviewCursor={this.setPreviewCursor}
- PanelHeight={this.panelHeight}
- PanelWidth={this.panelWidth}
- dropAction={"alias"}
- select={emptyFunction}
- ContentScaling={this.contentZoom}
- bringToFront={emptyFunction}
- docFilters={docFilters || this.basicFilter}
- styleProvider={this.childStyleProvider}
- dontRenderDocuments={docFilters ? false : true}
- CollectionView={undefined}
- ScreenToLocalTransform={this.overlayTransform}
- renderDepth={this.props.renderDepth + 1} />;
- return <div>
- <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`}
+ return <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none" }}>
+ <div className="pdfViewerDash-overlay"
style={{
- pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined,
+ pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none",
mixBlendMode: "multiply",
- transform: `scale(${this._zoomed})`
+ transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`
}}>
- {renderAnnotations(this.transparentFilter)}
+ {this.overlayTransparentAnnotations}
</div>
- <div className={`pdfViewerDash-overlay${CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`}
+ <div className="pdfViewerDash-overlay"
style={{
- pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined,
+ pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none",
mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined,
- transform: `scale(${this._zoomed})`
+ transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`
}}>
- {renderAnnotations(this.opaqueFilter)}
- {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()}
+ {this.overlayOpaqueAnnotations}
+ {this.overlayClickableAnnotations}
</div>
</div>;
}
@computed get pdfViewerDiv() {
- return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />;
+ return <div className={"pdfViewerDash-text" + (this.props.pointerEvents?.() !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />;
}
@computed get contentScaling() { return this.props.ContentScaling?.() || 1; }
- @computed get standinViews() {
- return <>
- {this._showCover ? this.getCoverImage() : (null)}
- {this._showWaiting ? <img className="pdfViewerDash-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)}
- </>;
- }
- contentZoom = () => this._zoomed;
+ contentZoom = () => NumCast(this.props.layoutDoc._viewScale, 1);
+ savedAnnotations = () => this._savedAnnotations;
render() {
TraceMobx();
return <div className="pdfViewer-content">
- <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents !== "none" ? "-interactive" : ""}`} ref={this._mainCont}
+ <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== "none" ? "-interactive" : ""}`} ref={this._mainCont}
onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
style={{
- overflowX: this._zoomed !== 1 ? "scroll" : undefined,
+ overflowX: NumCast(this.props.layoutDoc._viewScale, 1) !== 1 ? "scroll" : undefined,
height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`,
transform: `scale(${this.contentScaling})`
}} >
@@ -576,7 +534,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
{this.annotationLayer}
{this.overlayLayer}
{this.overlayInfo}
- {this.standinViews}
+ {this._showWaiting ? <img className="pdfViewerDash-waiting" src={"/assets/loading.gif"} /> : (null)}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
<MarqueeAnnotator rootDoc={this.props.rootDoc}
getPageFromScroll={this.getPageFromScroll}
@@ -586,9 +544,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
docView={this.props.docViewPath().lastElement()}
finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
+ savedAnnotations={this.savedAnnotations}
annotationLayer={this._annotationLayer.current}
- mainCont={this._mainCont.current} />}
+ mainCont={this._mainCont.current}
+ anchorMenuCrop={this._textSelecting ? undefined : this.crop}
+ />}
</div>
</div>;
}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 9257cb75e..574193614 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -108,6 +108,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false));
});
+ // TODO: nda -- Change this method to change what happens when you click on the item.
makeLink = action((linkTo: Doc) => {
if (this.props.linkFrom) {
const linkFrom = this.props.linkFrom();
@@ -127,9 +128,11 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
static foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) {
let newarray: Doc[] = [];
var depth = 0;
+ const visited: Doc[] = [];
while (docs.length > 0) {
newarray = [];
- docs.filter(d => d).forEach(d => {
+ docs.filter(d => d && !visited.includes(d)).forEach(d => {
+ visited.push(d);
const fieldKey = Doc.LayoutFieldKey(d);
const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
const data = d[annos ? fieldKey + "-annotations" : fieldKey];
@@ -339,6 +342,9 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
const query = StrCast(this._searchString);
Doc.SetSearchQuery(query);
+ Array.from(this._results.keys()).forEach(doc =>
+ DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true)
+ );
this._results.clear();
if (query) {
@@ -365,7 +371,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* or opening it in a new tab.
*/
selectElement = async (doc: Doc, finishFunc: () => void) => {
- await DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, undefined, undefined, finishFunc);
+ await DocumentManager.Instance.jumpToDocument(doc, true, undefined, [], undefined, undefined, undefined, finishFunc);
}
/**
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 0af7de6af..be248ab92 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,4 +1,6 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
+import { action } from "mobx";
import { observer } from "mobx-react";
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
@@ -9,6 +11,7 @@ import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { SettingsManager } from "../../util/SettingsManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { Borders, Colors } from "../global/globalEnums";
+import { MainView } from "../MainView";
import "./TopBar.scss";
/**
@@ -40,20 +43,28 @@ export class TopBar extends React.Component {
</select>
</div>
<div className="topbar-dashboards">
- <div className="topbar-icon" onClick={async () => {
+ <Tooltip title={<div className="dash-tooltip">Create a new dashboard </div>} placement="bottom"><div className="topbar-icon" onClick={async () => {
const batch = UndoManager.StartBatch("new dash");
await CurrentUserUtils.createNewDashboard(Doc.UserDoc());
batch.end();
}}>
{"New"}<FontAwesomeIcon icon="plus" />
</div>
- {<div className="topbar-icon" onClick={async () => {
- const batch = UndoManager.StartBatch("snapshot");
- await CurrentUserUtils.snapshotDashboard(Doc.UserDoc());
- batch.end();
- }}>
- {"Snapshot"}<FontAwesomeIcon icon="camera" />
- </div>}
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">Work on a copy of the dashboard layout</div>} placement="bottom">
+ <div className="topbar-icon" onClick={async () => {
+ const batch = UndoManager.StartBatch("snapshot");
+ await CurrentUserUtils.snapshotDashboard(Doc.UserDoc());
+ batch.end();
+ }}>
+ {"Snapshot"}<FontAwesomeIcon icon="camera" />
+ </div>
+ </Tooltip>
+ <Tooltip title={<div className="dash-tooltip">Browsing mode for directly navigating to documents</div>} placement="bottom">
+ <div className="topbar-icon" style={{ background: MainView.Instance._exploreMode ? Colors.LIGHT_BLUE : undefined }} onClick={action(() => MainView.Instance._exploreMode = !MainView.Instance._exploreMode)}>
+ {"Explore"}<FontAwesomeIcon icon="map" />
+ </div>
+ </Tooltip>
</div>
</div>
<div className="topbar-right" >
@@ -64,7 +75,6 @@ export class TopBar extends React.Component {
<div className="topbar-icon" onClick={() => SettingsManager.Instance.open()}>
{"Settings"}<FontAwesomeIcon icon="cog" />
</div>
-
</div>
</div>
</div >
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 50e5fcbc4..6abc27b23 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -821,6 +821,7 @@ export namespace Doc {
delegate[Initializing] = true;
delegate.proto = doc;
delegate.author = Doc.CurrentUserEmail;
+ Object.keys(doc).filter(key => key.startsWith("acl")).forEach(key => delegate[key] = doc[key]);
if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate);
title && (delegate.title = title);
delegate[Initializing] = false;
@@ -1016,12 +1017,28 @@ export namespace Doc {
unbrushed = 0,
protoBrushed = 1,
selfBrushed = 2,
- highlighted = 3
+ highlighted = 3,
+ linkHighlighted = 4,
}
// don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
export function IsBrushedDegreeUnmemoized(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return DocBrushStatus.unbrushed;
- return brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+ const status = brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+ if (status === DocBrushStatus.unbrushed) {
+ const lastBrushed = Array.from(brushManager.BrushedDoc.keys()).lastElement();
+ if (lastBrushed) {
+ for (const link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) {
+ const a1 = Cast(link.anchor1, Doc, null);
+ const a2 = Cast(link.anchor2, Doc, null);
+ if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) ||
+ (Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc)) ||
+ (Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc))) {
+ return DocBrushStatus.linkHighlighted;
+ }
+ }
+ }
+ }
+ return status;
}
export function IsBrushedDegree(doc: Doc) {
return computedFn(function IsBrushDegree(doc: Doc) {
@@ -1030,14 +1047,18 @@ export namespace Doc {
}
export function BrushDoc(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
- brushManager.BrushedDoc.set(doc, true);
- brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
+ runInAction(() => {
+ brushManager.BrushedDoc.set(doc, true);
+ brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
+ });
return doc;
}
export function UnBrushDoc(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
- brushManager.BrushedDoc.delete(doc);
- brushManager.BrushedDoc.delete(Doc.GetProto(doc));
+ runInAction(() => {
+ brushManager.BrushedDoc.delete(doc);
+ brushManager.BrushedDoc.delete(Doc.GetProto(doc));
+ });
return doc;
}
@@ -1052,9 +1073,9 @@ export namespace Doc {
}
let _lastDate = 0;
- export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) {
+ export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true) {
linkFollowUnhighlight();
- Doc.HighlightDoc(destDoc, dataAndDisplayDocs);
+ (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs));
document.removeEventListener("pointerdown", linkFollowUnhighlight);
document.addEventListener("pointerdown", linkFollowUnhighlight);
const lastDate = _lastDate = Date.now();
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 31024e805..114d5fc2f 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -11,7 +11,9 @@ export enum InkTool {
Pen = "pen",
Highlighter = "highlighter",
Eraser = "eraser",
- Stamp = "stamp"
+ Stamp = "stamp",
+ Write = "write",
+ PresentationPin = 'presentationpin'
}
@@ -83,7 +85,7 @@ export class InkField extends ObjectField {
}
[ToScriptString]() {
- return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}} `) + "])";
+ return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}`) + "])";
}
[ToString]() {
return "InkField";
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 60bf442d4..b15548327 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -32,6 +32,7 @@ const listHandlers: any = {
},
push: action(function (this: any, ...items: any[]) {
items = items.map(toObjectField);
+
const list = this[Self];
const length = list.__fields.length;
for (let i = 0; i < items.length; i++) {
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 4d5ae1018..e532becb5 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -44,7 +44,6 @@ export const documentSchema = createSchema({
_showCaption: "string", // whether editable caption text is overlayed at the bottom of the document
_showTitle: "string", // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";". Use :hover suffix to indicate title should be shown on hover
_showAudio: "boolean", // whether to show the audio record icon on documents
- _freeformLOD: "boolean", // whether to enable LOD switching for CollectionFreeFormViews
_pivotField: "string", // specifies which field key should be used as the timeline/pivot axis
_columnsFill: "boolean", // whether documents in a stacking view column should be sized to fill the column
_columnsSort: "string", // how a document should be sorted "ascending", "descending", undefined (none)
diff --git a/src/fields/util.ts b/src/fields/util.ts
index c708affe3..d1e565774 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -138,6 +138,7 @@ export function denormalizeEmail(email: string) {
* Copies parent's acl fields to the child
*/
export function inheritParentAcls(parent: Doc, child: Doc) {
+ return;
const dataDoc = parent[DataSym];
for (const key of Object.keys(dataDoc)) {
// if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
@@ -175,8 +176,11 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) {
* Calculates the effective access right to a document for the current user.
*/
export function GetEffectiveAcl(target: any, user?: string): symbol {
- return !target ? AclPrivate :
- target[UpdatingFromServer] ? AclAdmin : getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable)
+ if (!target) return AclPrivate;
+ if (target[UpdatingFromServer]) return AclAdmin;
+ // authored documents are private until an ACL is set.
+ if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate;
+ return getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable)
}
function getPropAcl(target: any, prop: string | symbol | number) {
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index 88221732e..418464f0e 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -96,7 +96,6 @@ export class AudioUpload extends React.Component {
isDocumentActive={returnTrue}
isContentActive={emptyFunction}
focus={emptyFunction}
- layerProvider={undefined}
styleProvider={() => "rgba(0,0,0,0)"}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index cfcc48608..78ec706d7 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -214,7 +214,6 @@ export class MobileInterface extends React.Component {
isContentActive={emptyFunction}
focus={DocUtils.DefaultFocus}
styleProvider={this.whitebackground}
- layerProvider={undefined}
docViewPath={returnEmptyDoclist}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 83a2bc79b..2c667ba3e 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -271,10 +271,10 @@ export default class UploadManager extends ApiManager {
return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
const ext = extname(savedName).toLowerCase();
const { pngs, jpgs } = AcceptableMedia;
- const resizers = !origSuffix ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" }] : [
- { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: "_s" },
- { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" },
- { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: "_l" },
+ const resizers = !origSuffix ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium }] : [
+ { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Small },
+ { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium },
+ { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Large },
];
let isImage = false;
if (pngs.includes(ext)) {
@@ -290,8 +290,10 @@ export default class UploadManager extends ApiManager {
}
if (isImage) {
resizers.forEach(resizer => {
- const path = serverPathToFile(Directory.images, filename + resizer.suffix + ext);
- createReadStream(savedName).pipe(resizer.resizer).pipe(createWriteStream(path));
+ const path = serverPathToFile(Directory.images, InjectSize(filename, resizer.suffix) + ext);
+ createReadStream(savedName).on("error", e => console.log("Resizing read:" + e))
+ .pipe(resizer.resizer)
+ .pipe(createWriteStream(path).on("error", e => console.log("Resizing write: " + e)));
});
}
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 5bfd0213c..fd000a83c 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -171,60 +171,53 @@ function registerCorsProxy(server: express.Express) {
function proxyServe(req: any, requrl: string, response: any) {
const htmlBodyMemoryStream = new (require('memorystream'))();
- req.headers.cookie = "";
- req.pipe(request(requrl))
- .on("error", (e: any) => console.log(`Malformed CORS url: ${requrl}`, e))
- .on("end", () => {
- var rewrittenHtmlBody: any = undefined;
- req.pipe(request(requrl))
- .on("response", (res: any) => {
- const headers = Object.keys(res.headers);
- const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
- headers.forEach(headerName => {
- const header = res.headers[headerName];
- if (Array.isArray(header)) {
- res.headers[headerName] = header.filter(h => !headerCharRegex.test(h));
- } else if (headerCharRegex.test(header || "")) {
- delete res.headers[headerName];
- }
- if (headerName === "content-encoding" && header.includes("gzip")) {
- try {
- const replacer = (match: any, href: string, offset: any, string: any) => {
- return `href="${resolvedServerUrl + "/corsProxy/http" + href}"`;
- };
- const zipToStringDecoder = new (require('string_decoder').StringDecoder)('utf8');
- const bodyStream = htmlBodyMemoryStream.read();
- if (bodyStream) {
- const htmlText = zipToStringDecoder.write(zlib.gunzipSync(bodyStream).toString('utf8')
- .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
- .replace(/href="https?([^"]*)"/g, replacer)
- .replace(/target="_blank"/g, ""));
- rewrittenHtmlBody = zlib.gzipSync(htmlText);
- } else {
- console.log("EMPTY body: href");
- }
- } catch (e) {
- console.log("EROR?: ", e);
- }
- }
- });
- })
- .on('data', (e: any) => {
- try {
- if (response.connection?.writable) {
- rewrittenHtmlBody && response.send(rewrittenHtmlBody);
- }
- } catch (e) {
- console.log("ERROR data : ", e);
- }
- rewrittenHtmlBody = undefined;
- })
- .on('error', (e: any) => {
- console.log("ERROR ON SERVER: ", e);
- })
- .pipe(response);
- })
- .pipe(htmlBodyMemoryStream);
+ var retrieveHTTPBody: any;
+ const sendModifiedBody = () => {
+ const header = response.headers["content-encoding"];
+ if (header && header.includes("gzip")) {
+ try {
+ const replacer = (match: any, href: string, offset: any, string: any) => {
+ return `href="${resolvedServerUrl + "/corsProxy/http" + href}"`;
+ };
+ const zipToStringDecoder = new (require('string_decoder').StringDecoder)('utf8');
+ const bodyStream = htmlBodyMemoryStream.read();
+ if (bodyStream) {
+ const htmlText = zipToStringDecoder.write(zlib.gunzipSync(bodyStream).toString('utf8')
+ .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
+ .replace(/href="https?([^"]*)"/g, replacer)
+ .replace(/target="_blank"/g, ""));
+ response.send(zlib.gzipSync(htmlText));
+ } else {
+ req.pipe(request(requrl)).pipe(response);
+ console.log("EMPTY body:" + req.url);
+ }
+ } catch (e) {
+ console.log("EROR?: ", e);
+ }
+ } else req.pipe(request(requrl)).pipe(response);
+ };
+ retrieveHTTPBody = () => {
+ req.headers.cookie = "";
+ req.pipe(request(requrl))
+ .on("error", (e: any) => console.log(`Malformed CORS url: ${requrl}`, e))
+ .on("response", (res: any) => {
+ res.headers;
+ const headers = Object.keys(res.headers);
+ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
+ headers.forEach(headerName => {
+ const header = res.headers[headerName];
+ if (Array.isArray(header)) {
+ res.headers[headerName] = header.filter(h => !headerCharRegex.test(h));
+ } else if (headerCharRegex.test(header || "")) {
+ delete res.headers[headerName];
+ } else res.headers[headerName] = header;
+ });
+ response.headers = response._headers = res.headers;
+ })
+ .on("end", sendModifiedBody)
+ .pipe(htmlBodyMemoryStream);
+ };
+ retrieveHTTPBody();
}
function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
@@ -235,9 +228,9 @@ function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
try {
const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
- const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : http:s//en.wikipedia.org/wiki/Engelbart)
+ const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart)
const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org)
- const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
+ const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
if (relativeUrl.startsWith("//")) res.redirect("http:" + relativeUrl);
else res.redirect(redirectedProxiedUrl);
} catch (e) {
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 068ac2159..bc7727426 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -8,6 +8,7 @@ declare module 'webrtc-adapter';
declare module 'bezier-curve';
declare module 'fit-curve';
declare module 'react-audio-waveform';
+declare module 'iink-js';
declare module 'reveal';
declare module 'react-reveal';