aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Foiani <sotech117@Michaels-MacBook-Pro-5.local>2022-06-10 12:04:03 -0400
committerMichael Foiani <sotech117@Michaels-MacBook-Pro-5.local>2022-06-10 12:04:03 -0400
commit91e193ef15bfc82d21653e9f150ccc25389dd6f3 (patch)
treeddca3cfebcb32be50045b8483eb3a349cf93914c /src
parenta2c3ed470c941b108aede045ac294e3b966990bc (diff)
parente08c690dbeca3e58b1bc0d387df0287f60f58b7a (diff)
merge with jenny bug fixes and fix small ui
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin8196 -> 8196 bytes
-rw-r--r--src/Utils.ts28
-rw-r--r--src/client/DocServer.ts60
-rw-r--r--src/client/documents/Documents.ts88
-rw-r--r--src/client/goldenLayout.js7
-rw-r--r--src/client/util/CurrentUserUtils.ts893
-rw-r--r--src/client/util/DocumentManager.ts70
-rw-r--r--src/client/util/DragManager.ts40
-rw-r--r--src/client/util/DropConverter.ts5
-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/RecordingApi.ts5
-rw-r--r--src/client/util/SharingManager.tsx31
-rw-r--r--src/client/views/DocComponent.tsx388
-rw-r--r--src/client/views/DocumentButtonBar.tsx53
-rw-r--r--src/client/views/DocumentDecorations.tsx1043
-rw-r--r--src/client/views/GestureOverlay.tsx34
-rw-r--r--src/client/views/GlobalKeyHandler.ts23
-rw-r--r--src/client/views/InkStrokeProperties.ts62
-rw-r--r--src/client/views/InkTangentHandles.tsx3
-rw-r--r--src/client/views/InkTranscription.scss5
-rw-r--r--src/client/views/InkTranscription.tsx334
-rw-r--r--src/client/views/InkingStroke.tsx55
-rw-r--r--src/client/views/LightboxView.tsx15
-rw-r--r--src/client/views/MainView.scss10
-rw-r--r--src/client/views/MainView.tsx1253
-rw-r--r--src/client/views/MarqueeAnnotator.tsx6
-rw-r--r--src/client/views/OverlayView.scss3
-rw-r--r--src/client/views/OverlayView.tsx20
-rw-r--r--src/client/views/Palette.tsx1
-rw-r--r--src/client/views/PropertiesButtons.tsx12
-rw-r--r--src/client/views/PropertiesView.tsx39
-rw-r--r--src/client/views/ScriptingRepl.tsx5
-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.tsx148
-rw-r--r--src/client/views/collections/CollectionDockingViewMenu.scss34
-rw-r--r--src/client/views/collections/CollectionDockingViewMenu.tsx48
-rw-r--r--src/client/views/collections/CollectionMenu.tsx24
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx17
-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.tsx164
-rw-r--r--src/client/views/collections/TreeView.scss16
-rw-r--r--src/client/views/collections/TreeView.tsx163
-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.tsx257
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx11
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx106
-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/LinkMenuItem.tsx4
-rw-r--r--src/client/views/linking/LinkPopup.tsx3
-rw-r--r--src/client/views/nodes/AudioBox.tsx91
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx8
-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.scss6
-rw-r--r--src/client/views/nodes/DocumentView.tsx2516
-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.tsx17
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx1
-rw-r--r--src/client/views/nodes/LabelBigText.js53
-rw-r--r--src/client/views/nodes/LabelBox.tsx61
-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.tsx12
-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.tsx105
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx3
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.scss4
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingView.tsx73
-rw-r--r--src/client/views/nodes/VideoBox.scss52
-rw-r--r--src/client/views/nodes/VideoBox.tsx11
-rw-r--r--src/client/views/nodes/WebBox.scss14
-rw-r--r--src/client/views/nodes/WebBox.tsx227
-rw-r--r--src/client/views/nodes/WebBoxRenderer.js30
-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.tsx1397
-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.tsx109
-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.tsx290
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss412
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx560
-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.tsx199
-rw-r--r--src/client/views/search/SearchBox.tsx10
-rw-r--r--src/client/views/topbar/TopBar.tsx30
-rw-r--r--src/fields/Doc.ts2569
-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.ts86
-rw-r--r--src/server/server_Initialization.ts112
-rw-r--r--src/typings/index.d.ts1
126 files changed, 8316 insertions, 7294 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..b87980397 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; }
@@ -671,6 +677,8 @@ export function StopEvent(e: React.PointerEvent | React.MouseEvent) {
e.preventDefault();
}
+
+
export function setupMoveUpEvents(
target: object,
e: React.PointerEvent,
@@ -686,6 +694,7 @@ export function setupMoveUpEvents(
(target as any)._lastTap = Date.now();
(target as any)._downX = (target as any)._lastX = e.clientX;
(target as any)._downY = (target as any)._lastY = e.clientY;
+ (target as any)._noClick = false;
const _moveEvent = (e: PointerEvent): void => {
if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
@@ -717,17 +726,20 @@ export function setupMoveUpEvents(
clearTimeout((target as any)._doubleTime);
(target as any)._doubleTime = undefined;
}
- clickEvent(e, (target as any)._doubleTap);
+ (target as any)._noClick = clickEvent(e, (target as any)._doubleTap);
}
document.removeEventListener("pointermove", _moveEvent);
document.removeEventListener("pointerup", _upEvent);
};
+ const _clickEvent = (e: MouseEvent): void => {
+ if ((target as any)._noClick) e.stopPropagation();
+ document.removeEventListener("click", _clickEvent, true);
+ }
if (stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
- document.removeEventListener("pointermove", _moveEvent);
- document.removeEventListener("pointerup", _upEvent);
document.addEventListener("pointermove", _moveEvent);
document.addEventListener("pointerup", _upEvent);
+ document.addEventListener("click", _clickEvent, true);
} \ No newline at end of file
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 8f92e9313..832f44571 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";
@@ -117,14 +117,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
@@ -157,6 +155,9 @@ export class DocumentOptions {
_timelineLabel?: boolean; // whether the document exists on a timeline
"_carousel-caption-xMargin"?: number;
"_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)
@@ -174,6 +175,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.
@@ -368,7 +370,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)"
}
}],
@@ -430,7 +432,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, {
@@ -487,7 +489,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 },
@@ -642,13 +644,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;
@@ -657,10 +660,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) &&
@@ -671,13 +676,13 @@ export namespace Docs {
return viewDoc;
}
- export function ImageDocument(url: string, options: DocumentOptions = {}) {
- const imgField = new ImageField(url);
- return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options });
+ export function ImageDocument(url: string|ImageField, options: DocumentOptions = {}) {
+ const imgField = url instanceof ImageField ? url : new ImageField(url);
+ return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...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) {
@@ -697,8 +702,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" }) {
@@ -753,7 +758,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;
@@ -778,6 +783,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)");
@@ -912,9 +918,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 = {}) {
@@ -1053,11 +1057,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;
});
@@ -1103,8 +1106,11 @@ export namespace DocUtils {
export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) {
broadcastEvent && runInAction(() => DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1);
- return DocUtils.ActiveRecordings.map(audio =>
- DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline"));
+ return DocUtils.ActiveRecordings.map(audio => {
+ const link = DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline");
+ link && (link.followLinkLocation = "add:right");
+ return link;
+ });
}
export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
@@ -1327,7 +1333,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);
@@ -1417,30 +1423,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/goldenLayout.js b/src/client/goldenLayout.js
index 82b10608d..012ee163c 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -458,8 +458,9 @@
if (this._bDragging === true) {
this._bDragging = false;
this.emit('dragStop', oEvent, this._nOriginalX + this._nX);
- } else if (oEvent.target) { // make title receive pointer events to allow setting insertion position or selecting texst range
- if (oEvent.target.className.includes("lm_title_wrap")) {
+ } else { // make title receive pointer events to allow setting insertion position or selecting texst range
+ const classname = typeof oEvent.target?.className === "string" ? oEvent.target.className : "";
+ if (classname.includes("lm_title_wrap")) {
oEvent.target.children[0].style.pointerEvents = "all";
oEvent.target.children[0].focus();
}
@@ -1431,7 +1432,7 @@
this.root.callDownwards('_$init');
if (config.maximisedItemId === '__glMaximised') {
- this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise();
+ this.root.getItemsById(config.maximisedItemId)[0]?.toggleMaximise();
}
},
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 3165e38a6..b1329e349 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 { ImageField, nullAudio } from "../../fields/URLField";
@@ -16,17 +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 { makeTemplate } from "./DropConverter";
+import { makeTemplate, MakeTemplate } from "./DropConverter";
import { HistoryUtil } from "./History";
import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
@@ -52,16 +52,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; }
@@ -71,204 +70,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;
@@ -276,11 +142,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 })
}));
@@ -289,113 +156,73 @@ export class CurrentUserUtils {
// setup templates for different document types when they are iconified from Document Decorations
static setupDefaultIconTemplates(doc: Doc) {
- if (doc["template-icon-view"] === undefined) {
- const iconView = Docs.Create.LabelDocument({
- title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimgray",
- _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
- });
- // Docs.Create.TextDocument("", {
- // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" })
- // });
- // Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
- iconView.isTemplateDoc = makeTemplate(iconView);
- doc["template-icon-view"] = new PrefetchProxy(iconView);
- }
- if (doc["template-icon-view-rtf"] === undefined) {
- const iconRtfView = Docs.Create.LabelDocument({
- title: "icon_" + DocumentType.RTF, _showTitle: "creationDate", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"),
- _singleLine: false, _minFontSize: 18, _maxFontSize: 24,
- _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
- });
- iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
- doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
- }
- if (doc["template-icon-view-button"] === undefined) {
- const iconBtnView = Docs.Create.FontIconDocument({
- title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30,
- _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
- });
- iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON);
- doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView);
- }
- if (doc["template-icon-view-img"] === undefined) {
- const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
- title: "data", _width: 150, isTemplateDoc: true, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
- });
- iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG);
- doc["template-icon-view-img"] = new PrefetchProxy(iconImageView);
- }
- if (doc["template-icon-view-col"] === undefined) {
- const iconColView = Docs.Create.ImageDocument("", { title: "icon", _showTitle: "title", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
- iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL);
- const proto = iconColView.proto as Doc;
- proto["icon-nativeWidth"] = 360 / 4;
- proto["icon-nativeHeight"] = 270 / 4;
- proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png");
- doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
- }
- if (doc["template-icon-view-video"] === undefined) {
- const iconVidView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
- iconVidView.isTemplateDoc = makeTemplate(iconVidView, true, "icon_" + DocumentType.VID);
- const proto = iconVidView.proto as Doc;
- proto["icon-nativeWidth"] = 360 / 4;
- proto["icon-nativeHeight"] = 270 / 4;
- proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png");
- doc["template-icon-view-video"] = new PrefetchProxy(iconVidView);
- }
- if (doc["template-icon-view-pdf"] === undefined) {
- const iconPdfView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
- iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF);
- const proto = iconPdfView.proto as Doc;
- proto["icon-nativeWidth"] = 360 / 4;
- proto["icon-nativeHeight"] = 270 / 4;
- proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png");
- doc["template-icon-view-pdf"] = new PrefetchProxy(iconPdfView);
- }
- if (doc["template-icons"] === undefined) {
- doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-video"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true }));
- } else {
- const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
- const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
- DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
- curIcons && await Promise.all(curIcons);
- requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
- });
+ const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
+
+ const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc) => {
+ const iconFieldName = "icon" + (type ? "_" + type : "");
+ if (!templateIconsDoc?.[iconFieldName]) {
+ const template = MakeTemplate(iconTemplate(), true, iconFieldName, templateField);
+ if (templateIconsDoc) {
+ templateIconsDoc[iconFieldName] = new PrefetchProxy(template);
+ Doc.AddDocToList(templateIconsDoc, "data", template);
+ } else {
+ return template;
+ }
+ }
+ };
+ const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" });
+ const labelBox = (extra: object) => Docs.Create.LabelDocument({
+ textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px",
+ _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, system: true, onClick: deiconifyScript(), ...extra,
+ });
+ const imageBox = (url: string, extra: object) => Docs.Create.ImageDocument(url, {
+ "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4,
+ _showTitle: "title", system: true, onClick: deiconifyScript(), ...extra
+ });
+ 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 });
}
- return doc["template-icons"] as Doc;
}
static creatorBtnDescriptors(doc: Doc): {
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",
@@ -416,9 +243,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 =
@@ -434,47 +260,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.WebCamDocument("", { _width: 400, _height: 200, title: "recording", system: true, cloneFieldFilter: new List<string>(["system"]) });
(doc.emptyWall as Doc).recording = 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);
@@ -484,11 +297,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 },
@@ -501,15 +313,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({
@@ -545,7 +353,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)' },
@@ -562,7 +370,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,
@@ -583,17 +391,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",
@@ -608,19 +406,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;
}
@@ -750,35 +535,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);
+ const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc);
+ const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc);
- doc["tabs-button-tools"] = undefined;
-
- 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) {
@@ -791,7 +567,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}"`);
@@ -811,8 +587,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 });
@@ -845,7 +620,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."
}));
@@ -915,7 +690,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);
@@ -931,22 +706,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) {
@@ -958,51 +733,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;
}
@@ -1018,7 +786,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,
@@ -1029,120 +797,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);
}
}
@@ -1200,7 +933,7 @@ export class CurrentUserUtils {
sharedDocs.childContextMenuIcons = new List<string>(["user-plus",]);
}
- doc.mySharedDocs = new PrefetchProxy(sharedDocs);
+ doc.mySharedDocs = new PrefetchProxy(sharedDocs as Doc);
}
}
@@ -1276,7 +1009,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);
@@ -1288,7 +1021,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)");
@@ -1305,12 +1038,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
@@ -1329,15 +1060,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) {
@@ -1462,6 +1184,25 @@ export class CurrentUserUtils {
}
public static async snapshotDashboard(userDoc: Doc) {
+ const docView = CollectionDockingView.Instance.props.DocumentView?.();
+ const content = docView?.ContentDiv;
+ if (docView && content) {
+ const _width = Number(getComputedStyle(content).width.replace("px",""));
+ const _height = Number(getComputedStyle(content).height.replace("px",""));
+ CollectionFreeFormView.UpdateIcon(
+ docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(),
+ content,
+ _width, _height,
+ _width, _height, 0,
+ (iconFile, _nativeWidth, _nativeHeight) => {
+ const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight});
+ const proto = Cast(img.proto, Doc, null)!;
+ proto["data-nativeWidth"] = _width;
+ proto["data-nativeHeight"] = _height;
+ docView.dataDoc.thumb = img;
+ });
+ }
+
const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard);
Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy);
CurrentUserUtils.openDashboard(userDoc, copy);
@@ -1487,22 +1228,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;
@@ -1513,7 +1240,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) {
@@ -1527,6 +1254,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); }
@@ -1544,26 +1272,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) {
@@ -1572,42 +1290,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) {
@@ -1624,6 +1313,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 8ac28bc89..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";
@@ -211,10 +210,6 @@ export namespace DragManager {
options?: DragOptions,
dropEvent?: () => any
) {
- // stop an 'accidental' on-click drag that may have occured if the user is in presenting mode
- // note: dragData.dropAction is only undefined when the element itself being dragged without being selected
- if (Doc.UserDoc()?.presentationMode === 'recording' && dragData.dropAction === undefined) return false;
-
const addAudioTag = (dropDoc: any) => {
dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField);
dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
@@ -282,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
@@ -317,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
@@ -454,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);
@@ -533,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/DropConverter.ts b/src/client/util/DropConverter.ts
index 082b6d8bd..076afd3a0 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -10,6 +10,11 @@ import { ImageField } from "../../fields/URLField";
import { ScriptingGlobals } from "./ScriptingGlobals";
import { listSpec } from "../../fields/Schema";
+export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined, templateField: string = "") {
+ if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated
+ doc.isTemplateDoc = makeTemplate(doc, first, rename);
+ return doc;
+}
//
// converts 'doc' into a template that can be used to render other documents.
// the title of doc is used to determine which field is being templated, so
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/RecordingApi.ts b/src/client/util/RecordingApi.ts
index 76d03156a..021feee9a 100644
--- a/src/client/util/RecordingApi.ts
+++ b/src/client/util/RecordingApi.ts
@@ -157,10 +157,7 @@ export class RecordingApi {
// set the reaction to track the movements
this.disposeFunc = reaction(
() => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }),
- (res) => {
- console.log('scale', res.scale);
- (res.x !== -1 && res.y !== -1 && this.isRecording) && this.trackMovements(res.x, res.y, res.scale);
- }
+ (res) => (res.x !== -1 && res.y !== -1 && this.isRecording) && this.trackMovements(res.x, res.y, res.scale)
)
// for now, set the most recent recordingFFView to the playFFView
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 103560a2a..80835447d 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -17,223 +17,215 @@ import { Touchable } from './Touchable';
/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
export interface DocComponentProps {
- Document: Doc;
- LayoutTemplate?: () => Opt<Doc>;
- LayoutTemplateString?: string;
+ Document: Doc;
+ LayoutTemplate?: () => Opt<Doc>;
+ LayoutTemplateString?: string;
}
export function DocComponent<P extends DocComponentProps>() {
- class Component extends Touchable<P> {
- //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
- @computed get Document() { return this.props.Document; }
- // This is the "The Document" -- it encapsulates, data, layout, and any templates
- @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
- // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
- @computed get layoutDoc() { return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); }
- // This is the data part of a document -- ie, the data that is constant across all views of the document
- @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
-
- protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- }
- return Component;
+ class Component extends Touchable<P> {
+ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
+ @computed get Document() { return this.props.Document; }
+ // This is the "The Document" -- it encapsulates, data, layout, and any templates
+ @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
+ // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
+ @computed get layoutDoc() { return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); }
+ // This is the data part of a document -- ie, the data that is constant across all views of the document
+ @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
+
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ }
+ return Component;
}
/// FieldViewBoxProps - a generic base class for field views that are not annotatable (e.g. InkingStroke, ColorBox)
interface ViewBoxBaseProps {
- Document: Doc;
- DataDoc?: Doc;
- ContainingCollectionDoc: Opt<Doc>;
- DocumentView?: () => DocumentView;
- fieldKey: string;
- layerProvider?: (doc: Doc, assign?: boolean) => boolean;
- isSelected: (outsideReaction?: boolean) => boolean;
- isContentActive: () => boolean | undefined;
- renderDepth: number;
- rootSelected: (outsideReaction?: boolean) => boolean;
+ Document: Doc;
+ DataDoc?: Doc;
+ ContainingCollectionDoc: Opt<Doc>;
+ DocumentView?: () => DocumentView;
+ fieldKey: string;
+ isSelected: (outsideReaction?: boolean) => boolean;
+ isContentActive: () => boolean | undefined;
+ renderDepth: number;
+ rootSelected: (outsideReaction?: boolean) => boolean;
}
export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
- class Component extends Touchable<P> {
- //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
- //@computed get Document(): T { return schemaCtor(this.props.Document); }
-
- // This is the "The Document" -- it encapsulates, data, layout, and any templates
- @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
- // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
- @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
- // This is the data part of a document -- ie, the data that is constant across all views of the document
- @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; }
-
- // key where data is stored
- @computed get fieldKey() { return this.props.fieldKey; }
-
- lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc }).result;
-
- isContentActive = (outsideReaction?: boolean) => (
- this.props.isContentActive?.() === false ? false :
- (CurrentUserUtils.SelectedTool !== InkTool.None ||
- (this.props.isContentActive?.() || this.props.Document.forceActive ||
- this.props.isSelected(outsideReaction) ||
- this.props.rootSelected(outsideReaction)) ? true : undefined))
- protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- }
- return Component;
+ class Component extends Touchable<P> {
+ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
+ //@computed get Document(): T { return schemaCtor(this.props.Document); }
+
+ // This is the "The Document" -- it encapsulates, data, layout, and any templates
+ @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
+ // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
+ @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
+ // This is the data part of a document -- ie, the data that is constant across all views of the document
+ @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; }
+ // key where data is stored
+ @computed get fieldKey() { return this.props.fieldKey; }
+
+ isContentActive = (outsideReaction?: boolean) => (
+ this.props.isContentActive?.() === false ? false :
+ (CurrentUserUtils.SelectedTool !== InkTool.None ||
+ (this.props.isContentActive?.() || this.props.Document.forceActive ||
+ this.props.isSelected(outsideReaction) ||
+ this.props.rootSelected(outsideReaction)) ? true : undefined))
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ }
+ return Component;
}
/// DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
export interface ViewBoxAnnotatableProps {
- Document: Doc;
- 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;
- isSelected: (outsideReaction?: boolean) => boolean;
- rootSelected: (outsideReaction?: boolean) => boolean;
- renderDepth: number;
- isAnnotationOverlay?: boolean;
+ Document: Doc;
+ 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)
+ isContentActive: () => boolean | undefined;
+ select: (isCtrlPressed: boolean) => void;
+ whenChildContentsActiveChanged: (isActive: boolean) => void;
+ isSelected: (outsideReaction?: boolean) => boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
+ renderDepth: number;
+ isAnnotationOverlay?: boolean;
}
export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() {
- class Component extends Touchable<P> {
- @observable _annotationKeySuffix = () => "annotations";
-
- @observable _isAnyChildContentActive = false;
- //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
- @computed get Document() { return this.props.Document; }
- // This is the "The Document" -- it encapsulates, data, layout, and any templates
- @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
- // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
- @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
- // This is the data part of a document -- ie, the data that is constant across all views of the document
- @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; }
-
- // key where data is stored
- @computed get fieldKey() { return this.props.fieldKey; }
-
- isAnyChildContentActive = () => this._isAnyChildContentActive;
-
- lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
-
- styleFromLayoutString = (scale: number) => {
- const style: { [key: string]: any } = {};
- const divKeys = ["width", "height", "fontSize", "transform", "left", "background", "left", "right", "top", "bottom", "pointerEvents", "position"];
- const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a property expression string: { script } into a value
- return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? "";
- };
- divKeys.map((prop: string) => {
- const p = (this.props as any)[prop];
- typeof p === "string" && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
- });
- return style;
- }
-
- protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
- @computed public get annotationKey() { return this.fieldKey + (this._annotationKeySuffix() ? "-" + this._annotationKeySuffix() : ""); }
-
- @action.bound
- removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean {
- const effectiveAcl = GetEffectiveAcl(this.dataDoc);
- const indocs = doc instanceof Doc ? [doc] : doc;
- const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
- if (docs.length) {
- setTimeout(() => docs.map(doc => { // this allows 'addDocument' to see the annotationOn field in order to create a pushin
- Doc.SetInPlace(doc, "isPushpin", undefined, true);
- doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, "annotationOn", undefined, true);
- }));
- const targetDataDoc = this.dataDoc;
- const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
- const toRemove = value.filter(v => docs.includes(v));
-
- if (toRemove.length !== 0) {
- const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
- toRemove.forEach(doc => {
- leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
- Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
- doc.context = undefined;
- if (recent) {
- Doc.RemoveDocFromList(recent, "data", doc);
- Doc.AddDocToList(recent, "data", doc, undefined, true, true);
- }
- });
- this.isAnyChildContentActive() && this.props.select(false);
- return true;
+ class Component extends Touchable<P> {
+ @observable _annotationKeySuffix = () => "annotations";
+ @observable _isAnyChildContentActive = false;
+ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
+ @computed get Document() { return this.props.Document; }
+ // This is the "The Document" -- it encapsulates, data, layout, and any templates
+ @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
+ // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
+ @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
+ // This is the data part of a document -- ie, the data that is constant across all views of the document
+ @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; }
+
+ // key where data is stored
+ @computed get fieldKey() { return this.props.fieldKey; }
+
+ isAnyChildContentActive = () => this._isAnyChildContentActive;
+
+ lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result;
+
+ styleFromLayoutString = (scale: number) => {
+ const style: { [key: string]: any } = {};
+ const divKeys = ["width", "height", "fontSize", "transform", "left", "background", "left", "right", "top", "bottom", "pointerEvents", "position"];
+ const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a property expression string: { script } into a value
+ return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? "";
+ };
+ divKeys.map((prop: string) => {
+ const p = (this.props as any)[prop];
+ typeof p === "string" && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
+ });
+ return style;
+ }
+
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+
+ @computed public get annotationKey() { return this.fieldKey + (this._annotationKeySuffix() ? "-" + this._annotationKeySuffix() : ""); }
+
+ @action.bound
+ removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean {
+ const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+ const indocs = doc instanceof Doc ? [doc] : doc;
+ const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
+ if (docs.length) {
+ setTimeout(() => docs.map(doc => { // this allows 'addDocument' to see the annotationOn field in order to create a pushin
+ Doc.SetInPlace(doc, "isPushpin", undefined, true);
+ doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, "annotationOn", undefined, true);
+ }));
+ const targetDataDoc = this.dataDoc;
+ const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
+ const toRemove = value.filter(v => docs.includes(v));
+
+ if (toRemove.length !== 0) {
+ const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
+ toRemove.forEach(doc => {
+ leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
+ Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+ doc.context = undefined;
+ if (recent) {
+ Doc.RemoveDocFromList(recent, "data", doc);
+ Doc.AddDocToList(recent, "data", doc, undefined, true, true);
}
- }
+ });
+ this.isAnyChildContentActive() && this.props.select(false);
+ return true;
+ }
+ }
- return false;
+ return false;
+ }
+ // this is called with the document that was dragged and the collection to move it into.
+ // if the target collection is the same as this collection, then the move will be allowed.
+ // otherwise, the document being moved must be able to be removed from its container before
+ // moving it into the target.
+ @action.bound
+ moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
+ if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
+ return true;
}
- // this is called with the document that was dragged and the collection to move it into.
- // if the target collection is the same as this collection, then the move will be allowed.
- // otherwise, the document being moved must be able to be removed from its container before
- // moving it into the target.
- @action.bound
- moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
- if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
- return true;
- }
- const first = doc instanceof Doc ? doc : doc[0];
- if (!first?._stayInCollection && addDocument !== returnFalse) {
- return UndoManager.RunInTempBatch(() => this.removeDocument(doc, annotationKey, true) && addDocument(doc, annotationKey));
- }
- return false;
+ const first = doc instanceof Doc ? doc : doc[0];
+ if (!first?._stayInCollection && addDocument !== returnFalse) {
+ return UndoManager.RunInTempBatch(() => this.removeDocument(doc, annotationKey, true) && addDocument(doc, annotationKey));
}
- @action.bound
- addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => {
- const docs = doc instanceof Doc ? [doc] : doc;
- if (this.props.filterAddDocument?.(docs) === false ||
- docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) {
- return false;
- }
- const targetDataDoc = this.props.Document[DataSym];
- const docList = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
- const added = docs.filter(d => !docList.includes(d));
- const effectiveAcl = GetEffectiveAcl(this.dataDoc);
-
- if (added.length) {
- if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
- return false;
- }
- else {
- if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) {
- added.forEach(d => {
- for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
- if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d);
- }
- });
- }
-
- if (effectiveAcl === AclAugment) {
- added.map(doc => {
- 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;
- if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
-
- inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
- });
- const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
- if (annoDocs instanceof List) annoDocs.push(...added);
- else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
- targetDataDoc[(annotationKey ?? this.annotationKey) + "-lastModified"] = new DateField(new Date(Date.now()));
- }
- }
- }
- return true;
+ return false;
+ }
+ @action.bound
+ addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => {
+ const docs = doc instanceof Doc ? [doc] : doc;
+ if (this.props.filterAddDocument?.(docs) === false ||
+ docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) {
+ return false;
+ }
+ const targetDataDoc = this.props.Document[DataSym];
+ const docList = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
+ const added = docs.filter(d => !docList.includes(d));
+ const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+
+ if (added.length) {
+ if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
+ return false;
+ }
+ else {
+ if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) {
+ added.forEach(d => {
+ for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
+ if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d);
+ }
+ });
+ }
+
+ if (effectiveAcl === AclAugment) {
+ added.map(doc => {
+ 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;
+ 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
+ //DocUtils.LeavePushpin(doc);
+ doc._stayInCollection = undefined;
+ doc.context = this.props.Document;
+ if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
+
+ inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
+ });
+ const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
+ if (annoDocs instanceof List) annoDocs.push(...added);
+ else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
+ targetDataDoc[(annotationKey ?? this.annotationKey) + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
+ }
}
+ return true;
+ }
- whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive));
- }
- return Component;
+ whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive));
+ }
+ return Component;
} \ No newline at end of file
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 9b95f1525..103734b9b 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>;
@@ -213,32 +213,31 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@undoBatch
@action
pinWithView = (targetDoc: Doc) => {
- if (targetDoc) {
- TabDocView.PinDoc(targetDoc);
- setTimeout(() => {
- const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
- const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking;
- const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG);
- if (scrollable) {
- const scroll = targetDoc._scrollTop;
- activeDoc.presPinView = true;
- activeDoc.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeDoc.presPinTimecode = targetDoc._currentTimecode;
- } else if (pannable) {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeDoc.presPinView = true;
- activeDoc.presPinViewX = x;
- activeDoc.presPinViewY = y;
- activeDoc.presPinViewScale = scale;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const width = targetDoc._clipWidth;
- activeDoc.presPinClipWidth = width;
- activeDoc.presPinView = true;
- }
- });
+ const activeDoc = targetDoc && TabDocView.PinDoc(targetDoc);
+ if (activeDoc) {
+ const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking;
+ const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG);
+ if (scrollable) {
+ const scroll = targetDoc._scrollTop;
+ activeDoc.presPinView = true;
+ activeDoc.presPinViewScroll = scroll;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
+ activeDoc.presPinView = true;
+ activeDoc.presStartTime = targetDoc._currentTimecode;
+ activeDoc.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1;
+ } else if (pannable) {
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeDoc.presPinView = true;
+ activeDoc.presPinViewX = x;
+ activeDoc.presPinViewY = y;
+ activeDoc.presPinViewScale = scale;
+ } else if (targetDoc.type === DocumentType.COMPARISON) {
+ const width = targetDoc._clipWidth;
+ activeDoc.presPinClipWidth = width;
+ activeDoc.presPinView = true;
+ }
}
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 1feb643df..b666b5977 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,538 +28,554 @@ 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 }> {
- static Instance: DocumentDecorations;
- private _resizeHdlId = "";
- private _keyinput = React.createRef<HTMLInputElement>();
- private _resizeBorderWidth = 16;
- private _linkBoxHeight = 20 + 3; // link button height + margin
- private _titleHeight = 20;
- private _resizeUndo?: UndoManager.Batch;
- private _offX = 0; _offY = 0; // offset from click pt to inner edge of resize border
- private _snapX = 0; _snapY = 0; // last snapped location of resize border
- private _dragHeights = new Map<Doc, { start: number, lowest: number }>();
- private _inkDragDocs: { doc: Doc, x: number, y: number, width: number, height: number }[] = [];
-
- @observable private _accumulatedTitle = "";
- @observable private _titleControlString: string = "#title";
- @observable private _edtingTitle = false;
- @observable private _hidden = false;
-
- @observable public Interacting = false;
- @observable public pushIcon: IconProp = "arrow-alt-circle-up";
- @observable public pullIcon: IconProp = "arrow-alt-circle-down";
- @observable public pullColor: string = "white";
-
- constructor(props: any) {
- super(props);
- DocumentDecorations.Instance = this;
- reaction(() => SelectionManager.Views().slice(), action(docs => this._edtingTitle = false));
- }
-
- @computed
- get Bounds() {
- const views = SelectionManager.Views();
- return views.map(dv => dv.getBounds()).reduce((bounds, rect) =>
- !rect ? bounds :
- {
- x: Math.min(rect.left, bounds.x),
- y: Math.min(rect.top, bounds.y),
- r: Math.max(rect.right, bounds.r),
- b: Math.max(rect.bottom, bounds.b),
- c: views.length === 1 ? rect.center : undefined
- },
- { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as ({ X: number, Y: number } | undefined) });
- }
-
- @action
- titleBlur = () => {
- this._edtingTitle = false;
- if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) {
- this._titleControlString = this._accumulatedTitle;
- } else if (this._titleControlString.startsWith("#")) {
- const titleFieldKey = this._titleControlString.substring(1);
- UndoManager.RunInBatch(() => titleFieldKey && SelectionManager.Views().forEach(d => {
- if (titleFieldKey === "title") {
- d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-");
- if (StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) {
- Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
- }
- if (!StrCast(d.rootDoc.title).startsWith("@") && this._accumulatedTitle.startsWith("@")) {
- Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
- }
- }
- //@ts-ignore
- const titleField = (+this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle);
- Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
-
- if (d.rootDoc.syncLayoutFieldWithTitle) {
- const title = titleField.toString();
- const curKey = Doc.LayoutFieldKey(d.rootDoc);
- if (curKey !== title && d.dataDoc[title] === undefined) {
- d.rootDoc.layout = FormattedTextBox.LayoutString(title);
- setTimeout(() => {
- const val = d.dataDoc[curKey];
- d.dataDoc[curKey] = undefined;
- d.dataDoc[title] = val;
- });
- }
- }
- }), "title blur");
- }
- }
-
- titleEntered = (e: React.KeyboardEvent) => e.key === "Enter" && (e.target as any).blur();
-
- @action onTitleDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => {
- !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith("#") ? this.selectionTitle : this._titleControlString);
- this._edtingTitle = true;
- this._keyinput.current && setTimeout(this._keyinput.current.focus);
- }));
- }
-
- onBackgroundDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
-
- @action
- onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
- const dragDocView = SelectionManager.Views()[0];
- const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
- const dragData = new DragManager.DocumentDragData(SelectionManager.Views().map(dv => dv.props.Document), dragDocView.props.dropAction);
- dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
- dragData.moveDocument = dragDocView.props.moveDocument;
- dragData.isDocDecorationMove = true;
- dragData.canEmbed = dragTitle;
- this._hidden = this.Interacting = true;
- DragManager.StartDocumentDrag(SelectionManager.Views().map(dv => dv.ContentDiv!), dragData, e.x, e.y, {
- dragComplete: action(e => {
- dragData.canEmbed && SelectionManager.DeselectAll();
- this._hidden = this.Interacting = false;
- }),
- hideSource: true
- });
- return true;
- }
-
- _deleteAfterIconify = false;
- _iconifyBatch: UndoManager.Batch | undefined;
- onCloseClick = (force = false) => {
- if (this.canDelete) {
+ static Instance: DocumentDecorations;
+ private _resizeHdlId = "";
+ private _keyinput = React.createRef<HTMLInputElement>();
+ private _resizeBorderWidth = 16;
+ private _linkBoxHeight = 20 + 3; // link button height + margin
+ private _titleHeight = 20;
+ private _resizeUndo?: UndoManager.Batch;
+ private _offX = 0; _offY = 0; // offset from click pt to inner edge of resize border
+ private _snapX = 0; _snapY = 0; // last snapped location of resize border
+ private _dragHeights = new Map<Doc, { start: number, lowest: number }>();
+ private _inkDragDocs: { doc: Doc, x: number, y: number, width: number, height: number }[] = [];
+
+ @observable private _accumulatedTitle = "";
+ @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";
+ @observable public pullColor: string = "white";
+
+ constructor(props: any) {
+ super(props);
+ DocumentDecorations.Instance = this;
+ reaction(() => SelectionManager.Views().slice(), action(docs => this._edtingTitle = false));
+ }
+
+ @computed
+ get Bounds() {
+ const views = SelectionManager.Views();
+ return views.filter(dv => dv.props.renderDepth > 0).map(dv => dv.getBounds()).reduce((bounds, rect) =>
+ !rect ? bounds :
+ {
+ x: Math.min(rect.left, bounds.x),
+ y: Math.min(rect.top, bounds.y),
+ r: Math.max(rect.right, bounds.r),
+ b: Math.max(rect.bottom, bounds.b),
+ c: views.length === 1 ? rect.center : undefined
+ },
+ { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as ({ X: number, Y: number } | undefined) });
+ }
+
+ @action
+ titleBlur = () => {
+ this._edtingTitle = false;
+ if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) {
+ this._titleControlString = this._accumulatedTitle;
+ } else if (this._titleControlString.startsWith("#")) {
+ const titleFieldKey = this._titleControlString.substring(1);
+ UndoManager.RunInBatch(() => titleFieldKey && SelectionManager.Views().forEach(d => {
+ if (titleFieldKey === "title") {
+ d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-");
+ if (StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) {
+ Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
+ }
+ if (!StrCast(d.rootDoc.title).startsWith("@") && this._accumulatedTitle.startsWith("@")) {
+ Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc);
+ }
+ }
+ //@ts-ignore
+ const titleField = (+this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle);
+ Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
+
+ if (d.rootDoc.syncLayoutFieldWithTitle) {
+ const title = titleField.toString();
+ const curKey = Doc.LayoutFieldKey(d.rootDoc);
+ if (curKey !== title && d.dataDoc[title] === undefined) {
+ d.rootDoc.layout = FormattedTextBox.LayoutString(title);
+ setTimeout(() => {
+ const val = d.dataDoc[curKey];
+ d.dataDoc[curKey] = undefined;
+ d.dataDoc[title] = val;
+ });
+ }
+ }
+ }), "title 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) => {
+ !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith("#") ? this.selectionTitle : this._titleControlString);
+ this._edtingTitle = true;
+ this._keyinput.current && setTimeout(this._keyinput.current.focus);
+ }));
+ }
+
+ onBackgroundDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
+
+ @action
+ onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
+ const dragDocView = SelectionManager.Views()[0];
+ const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
+ const dragData = new DragManager.DocumentDragData(SelectionManager.Views().map(dv => dv.props.Document), dragDocView.props.dropAction);
+ dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
+ dragData.moveDocument = dragDocView.props.moveDocument;
+ dragData.isDocDecorationMove = true;
+ dragData.canEmbed = dragTitle;
+ this._hidden = this.Interacting = true;
+ DragManager.StartDocumentDrag(SelectionManager.Views().map(dv => dv.ContentDiv!), dragData, e.x, e.y, {
+ dragComplete: action(e => {
+ dragData.canEmbed && SelectionManager.DeselectAll();
+ this._hidden = this.Interacting = false;
+ }),
+ hideSource: true
+ });
+ return true;
+ }
+
+ _deleteAfterIconify = false;
+ _iconifyBatch: UndoManager.Batch | undefined;
+ onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
+
const views = SelectionManager.Views().slice().filter(v => v);
- this._deleteAfterIconify = force || this._iconifyBatch ? true : false;
+ if (forceDeleteOrIconify === false && this._iconifyBatch) return;
+ this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
if (!this._iconifyBatch) {
- this._iconifyBatch = UndoManager.StartBatch("iconifying");
+ this._iconifyBatch = UndoManager.StartBatch("iconifying");
} else {
- force = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
+ forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
}
var iconifyingCount = views.length;
const finished = action((force?: boolean) => {
- if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
- if (this._deleteAfterIconify) {
- views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document));
- SelectionManager.DeselectAll();
- }
- this._iconifyBatch?.end();
- this._iconifyBatch = undefined;
- }
+ if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
+ if (this._deleteAfterIconify) {
+ views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document));
+ SelectionManager.DeselectAll();
+ }
+ this._iconifyBatch?.end();
+ this._iconifyBatch = undefined;
+ }
});
- if (force) finished(force);
+ if (forceDeleteOrIconify) finished(forceDeleteOrIconify);
else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished));
- }
- }
- onMaximizeDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, () => {
- DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]);
- return true;
- }, emptyFunction, this.onMaximizeClick, false, false);
- }
-
- onMaximizeClick = (e: any): void => {
- const selectedDocs = SelectionManager.Views();
- if (selectedDocs.length) {
- if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key
- const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
- CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), "right");
- } else if (e.shiftKey) { // open centered in a new workspace with Shift Key
- const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
- alias.context = undefined;
- alias.x = -alias[WidthSym]() / 2;
- alias.y = -alias[HeightSym]() / 2;
- CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
- } else if (e.altKey) { // open same document in new tab
- CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
- } else {
- var openDoc = selectedDocs[0].props.Document;
- if (openDoc.layoutKey === "layout_icon") {
- openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc);
- Doc.deiconifyView(openDoc);
- }
- LightboxView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1).map(view => view.props.Document));
- }
- }
- SelectionManager.DeselectAll();
- }
-
- onIconifyClick = (): void => {
- SelectionManager.Views().forEach(dv => dv?.iconify());
- SelectionManager.DeselectAll();
- }
-
- onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
-
- onRadiusDown = (e: React.PointerEvent): void => {
- this._resizeUndo = UndoManager.StartBatch("DocDecs set radius");
- setupMoveUpEvents(this, e, (e, down) => {
- const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1]));
- SelectionManager.Views().map(dv => dv.props.Document).map(doc => doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc)).
- map(d => d.borderRounding = `${Math.max(0, dist < 3 ? 0 : dist)}px`);
- return false;
- }, (e) => this._resizeUndo?.end(), (e) => { });
- }
-
- @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 };
- 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);
- return false;
- },
- () => {
- rotateUndo?.end();
- UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
- },
- emptyFunction);
- }
-
- @action
- onPointerDown = (e: React.PointerEvent): void => {
- DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc);
- this._inkDragDocs = DragManager.docsBeingDragged
- .filter(doc => doc.type === DocumentType.INK)
- .map(doc => {
- if (InkStrokeProperties.Instance._lock) {
- Doc.SetNativeHeight(doc, NumCast(doc._height));
- Doc.SetNativeWidth(doc, NumCast(doc._width));
- }
- return ({ doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
- });
- setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
- this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
- this._resizeHdlId = e.currentTarget.className;
- const bounds = e.currentTarget.getBoundingClientRect();
- this._offX = this._resizeHdlId.toLowerCase().includes("left") ? bounds.right - e.clientX : bounds.left - e.clientX;
- this._offY = this._resizeHdlId.toLowerCase().includes("top") ? bounds.bottom - e.clientY : bounds.top - e.clientY;
- this._resizeUndo = UndoManager.StartBatch("DocDecs resize");
- this._snapX = e.pageX;
- this._snapY = e.pageY;
- const ffviewSet = new Set<CollectionFreeFormView>();
- SelectionManager.Views().forEach(docView => {
- const ffview = docView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- ffview && ffviewSet.add(ffview);
- this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) });
- });
- Array.from(ffviewSet).map(ffview => ffview.setupDragLines(false));
- }
-
- onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
- const first = SelectionManager.Views()[0];
- let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
- var fixedAspect = Doc.NativeAspect(first.layoutDoc);
- InkStrokeProperties.Instance._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
- .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
-
- const resizeHdl = this._resizeHdlId.split(" ")[0];
- if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
- const project = (p: number[], a: number[], b: number[]) => {
- const atob = [b[0] - a[0], b[1] - a[1]];
- const atop = [p[0] - a[0], p[1] - a[1]];
- const len = atob[0] * atob[0] + atob[1] * atob[1];
- let dot = atop[0] * atob[0] + atop[1] * atob[1];
- const t = dot / len;
- dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
- return [a[0] + atob[0] * t, a[1] + atob[1] * t];
- };
- const tl = first.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]);
- thisPt = DragManager.snapDragAspect(drag, fixedAspect);
- } else {
- thisPt = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY);
- }
-
- move[0] = thisPt.x - this._snapX;
- move[1] = thisPt.y - this._snapY;
- this._snapX = thisPt.x;
- this._snapY = thisPt.y;
- let dragBottom = false, dragRight = false, dragBotRight = false;
- let dX = 0, dY = 0, dW = 0, dH = 0;
- switch (this._resizeHdlId.split(" ")[0]) {
- case "": break;
- case "documentDecorations-topLeftResizer":
- dX = -1;
- dY = -1;
- dW = -move[0];
- dH = -move[1];
- break;
- case "documentDecorations-topRightResizer":
- dW = move[0];
- dY = -1;
- dH = -move[1];
- break;
- case "documentDecorations-topResizer":
- dY = -1;
- dH = -move[1];
- dragBottom = true;
- break;
- case "documentDecorations-bottomLeftResizer":
- dX = -1;
- dW = -move[0];
- dH = move[1];
- break;
- case "documentDecorations-bottomRightResizer":
- dW = move[0];
- dH = move[1];
- dragBotRight = true;
- break;
- case "documentDecorations-bottomResizer":
- dH = move[1];
- dragBottom = true;
- break;
- case "documentDecorations-leftResizer":
- dX = -1;
- dW = -move[0];
- break;
- case "documentDecorations-rightResizer":
- dW = move[0];
- dragRight = true;
- break;
- }
-
- SelectionManager.Views().forEach(action((docView: DocumentView) => {
- if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
- if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
- const doc = Document(docView.rootDoc);
- const nwidth = docView.nativeWidth;
- const nheight = docView.nativeHeight;
- const docheight = doc._height || 0;
- const docwidth = doc._width || 0;
- const width = docwidth;
- let height = (docheight || (nheight / nwidth * width));
- height = !height || isNaN(height) ? 20 : height;
- const scale = docView.props.ScreenToLocalTransform().Scale;
- const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable;
- if (nwidth && nheight) {
- if (nwidth / nheight !== width / height && !dragBottom) {
- height = nheight / nwidth * width;
- }
- if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
- if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
- else dW = dH * nwidth / nheight;
- }
- }
- let actualdW = Math.max(width + (dW * scale), 20);
- let actualdH = Math.max(height + (dH * scale), 20);
- const fixedAspect = (nwidth && nheight && !doc._fitWidth);
- if (fixedAspect) {
- if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
- if (dragRight && modifyNativeDim) {
- doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc);
- } else {
- if (!doc._fitWidth) {
- actualdH = nheight / nwidth * actualdW;
- doc._height = actualdH;
- }
- else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
+ }
+ onMaximizeDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, () => {
+ DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]);
+ return true;
+ }, emptyFunction, this.onMaximizeClick, false, false);
+ }
+
+ onMaximizeClick = (e: any): void => {
+ const selectedDocs = SelectionManager.Views();
+ if (selectedDocs.length) {
+ if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key
+ const bestAlias = DocListCast(selectedDocs[0].props.Document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
+ CollectionDockingView.AddSplit(bestAlias ?? Doc.MakeAlias(selectedDocs[0].props.Document), "right");
+ } else if (e.shiftKey) { // open centered in a new workspace with Shift Key
+ const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
+ alias.context = undefined;
+ alias.x = -alias[WidthSym]() / 2;
+ alias.y = -alias[HeightSym]() / 2;
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
+ } else if (e.altKey) { // open same document in new tab
+ CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
+ } else {
+ var openDoc = selectedDocs[0].props.Document;
+ if (openDoc.layoutKey === "layout_icon") {
+ openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc);
+ Doc.deiconifyView(openDoc);
}
- doc._width = actualdW;
- }
- else {
- if (dragBottom && (modifyNativeDim ||
- (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
- doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc);
- doc._autoHeight = false;
+ LightboxView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1).map(view => view.props.Document));
+ }
+ }
+ SelectionManager.DeselectAll();
+ }
+
+ onIconifyClick = (): void => {
+ SelectionManager.Views().forEach(dv => dv?.iconify());
+ SelectionManager.DeselectAll();
+ }
+
+ onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
+
+ onRadiusDown = (e: React.PointerEvent): void => {
+ this._resizeUndo = UndoManager.StartBatch("DocDecs set radius");
+ setupMoveUpEvents(this, e, (e, down) => {
+ const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1]));
+ SelectionManager.Views().map(dv => dv.props.Document).map(doc => doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc)).
+ map(d => d.borderRounding = `${Math.max(0, dist < 3 ? 0 : dist)}px`);
+ return false;
+ }, (e) => this._resizeUndo?.end(), (e) => { });
+ }
+
+ @action
+ onRotateDown = (e: React.PointerEvent): void => {
+ const rotateUndo = UndoManager.StartBatch("rotatedown");
+ 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);
+ if (selectedInk.length) {
+ angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint);
} else {
- if (!doc._fitWidth) {
- actualdW = nwidth / nheight * actualdH;
- doc._width = actualdW;
- }
- else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
+ SelectionManager.Views().forEach(dv => dv.rootDoc._jitterRotation = NumCast(dv.rootDoc._jitterRotation) - angle * 180 / Math.PI);
}
- if (!modifyNativeDim) {
- actualdH = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
- doc._height = actualdH;
+ return false;
+ },
+ () => {
+ rotateUndo?.end();
+ UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
+ },
+ emptyFunction);
+ }
+
+ @action
+ onPointerDown = (e: React.PointerEvent): void => {
+ DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc);
+ this._inkDragDocs = DragManager.docsBeingDragged
+ .filter(doc => doc.type === DocumentType.INK)
+ .map(doc => {
+ if (InkStrokeProperties.Instance._lock) {
+ Doc.SetNativeHeight(doc, NumCast(doc._height));
+ Doc.SetNativeWidth(doc, NumCast(doc._width));
}
- else doc._height = actualdH;
- }
- } else {
- dH && (doc._height = actualdH);
- dW && (doc._width = actualdW);
- dH && (doc._autoHeight = false);
- }
- doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
- doc.y = (doc.y || 0) + dY * (actualdH - docheight);
- doc._lastModified = new DateField();
- }
- const val = this._dragHeights.get(docView.layoutDoc);
- if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) });
- }));
- return false;
- }
-
- @action
- onPointerUp = (e: PointerEvent): void => {
- this._resizeHdlId = "";
- this.Interacting = false;
- this._resizeUndo?.end();
- SnappingManager.clearSnapLines();
-
- // detect autoHeight gesture and apply
- SelectionManager.Views().map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) }))
- .filter(pair => pair.hgts && pair.hgts.lowest < pair.hgts.start && pair.hgts.lowest <= 20)
- .forEach(pair => pair.doc._autoHeight = true);
- //need to change points for resize, or else rotation/control points will fail.
- this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
- .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
- Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
- ({
- X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
- Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
- })));
- Doc.SetNativeWidth(doc, undefined);
- Doc.SetNativeHeight(doc, undefined);
+ return ({ doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
+ });
+
+ setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
+ this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
+ this._resizeHdlId = e.currentTarget.className;
+ const bounds = e.currentTarget.getBoundingClientRect();
+ this._offX = this._resizeHdlId.toLowerCase().includes("left") ? bounds.right - e.clientX : bounds.left - e.clientX;
+ this._offY = this._resizeHdlId.toLowerCase().includes("top") ? bounds.bottom - e.clientY : bounds.top - e.clientY;
+ this._resizeUndo = UndoManager.StartBatch("DocDecs resize");
+ this._snapX = e.pageX;
+ this._snapY = e.pageY;
+ const ffviewSet = new Set<CollectionFreeFormView>();
+ SelectionManager.Views().forEach(docView => {
+ const ffview = docView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ ffview && ffviewSet.add(ffview);
+ this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) });
});
- }
-
- @computed
- get selectionTitle(): string {
- if (SelectionManager.Views().length === 1) {
- const selected = SelectionManager.Views()[0];
- if (selected.ComponentView?.getTitle?.()) {
- return selected.ComponentView.getTitle();
+ Array.from(ffviewSet).map(ffview => ffview.setupDragLines(false));
+ }
+
+ onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
+ const first = SelectionManager.Views()[0];
+ let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
+ var fixedAspect = Doc.NativeAspect(first.layoutDoc);
+ InkStrokeProperties.Instance._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
+ .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
+
+ const resizeHdl = this._resizeHdlId.split(" ")[0];
+ if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
+ const project = (p: number[], a: number[], b: number[]) => {
+ const atob = [b[0] - a[0], b[1] - a[1]];
+ const atop = [p[0] - a[0], p[1] - a[1]];
+ const len = atob[0] * atob[0] + atob[1] * atob[1];
+ let dot = atop[0] * atob[0] + atop[1] * atob[1];
+ const t = dot / len;
+ dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
+ return [a[0] + atob[0] * t, a[1] + atob[1] * t];
+ };
+ const tl = first.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]);
+ thisPt = DragManager.snapDragAspect(drag, fixedAspect);
+ } else {
+ thisPt = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY);
}
- if (this._titleControlString.startsWith("=")) {
- return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
+
+ move[0] = thisPt.x - this._snapX;
+ move[1] = thisPt.y - this._snapY;
+ this._snapX = thisPt.x;
+ this._snapY = thisPt.y;
+ let dragBottom = false, dragRight = false, dragBotRight = false;
+ let dX = 0, dY = 0, dW = 0, dH = 0;
+ switch (this._resizeHdlId.split(" ")[0]) {
+ case "": break;
+ case "documentDecorations-topLeftResizer":
+ dX = -1;
+ dY = -1;
+ dW = -move[0];
+ dH = -move[1];
+ break;
+ case "documentDecorations-topRightResizer":
+ dW = move[0];
+ dY = -1;
+ dH = -move[1];
+ break;
+ case "documentDecorations-topResizer":
+ dY = -1;
+ dH = -move[1];
+ dragBottom = true;
+ break;
+ case "documentDecorations-bottomLeftResizer":
+ dX = -1;
+ dW = -move[0];
+ dH = move[1];
+ break;
+ case "documentDecorations-bottomRightResizer":
+ dW = move[0];
+ dH = move[1];
+ dragBotRight = true;
+ break;
+ case "documentDecorations-bottomResizer":
+ dH = move[1];
+ dragBottom = true;
+ break;
+ case "documentDecorations-leftResizer":
+ dX = -1;
+ dW = -move[0];
+ break;
+ case "documentDecorations-rightResizer":
+ dW = move[0];
+ dragRight = true;
+ break;
+ }
+
+ SelectionManager.Views().forEach(action((docView: DocumentView) => {
+ if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
+ if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
+ const doc = Document(docView.rootDoc);
+ const nwidth = docView.nativeWidth;
+ const nheight = docView.nativeHeight;
+ const docheight = doc._height || 0;
+ const docwidth = doc._width || 0;
+ const width = docwidth;
+ let height = (docheight || (nheight / nwidth * width));
+ height = !height || isNaN(height) ? 20 : height;
+ const scale = docView.props.ScreenToLocalTransform().Scale;
+ const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable;
+ if (nwidth && nheight) {
+ if (nwidth / nheight !== width / height && !dragBottom) {
+ height = nheight / nwidth * width;
+ }
+ if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
+ if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
+ else dW = dH * nwidth / nheight;
+ }
+ }
+ let actualdW = Math.max(width + (dW * scale), 20);
+ let actualdH = Math.max(height + (dH * scale), 20);
+ const fixedAspect = (nwidth && nheight && !doc._fitWidth);
+ if (fixedAspect) {
+ if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
+ if (dragRight && modifyNativeDim) {
+ doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc);
+ } else {
+ if (!doc._fitWidth) {
+ actualdH = nheight / nwidth * actualdW;
+ doc._height = actualdH;
+ }
+ else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
+ }
+ doc._width = actualdW;
+ }
+ else {
+ if (dragBottom && (modifyNativeDim ||
+ (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
+ doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc);
+ doc._autoHeight = false;
+ } else {
+ if (!doc._fitWidth) {
+ actualdW = nwidth / nheight * actualdH;
+ doc._width = actualdW;
+ }
+ else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
+ }
+ if (!modifyNativeDim) {
+ actualdH = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
+ doc._height = actualdH;
+ }
+ else doc._height = actualdH;
+ }
+ } else {
+ dH && (doc._height = actualdH);
+ dW && (doc._width = actualdW);
+ dH && (doc._autoHeight = false);
+ }
+ doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
+ doc.y = (doc.y || 0) + dY * (actualdH - docheight);
+ doc._lastModified = new DateField();
+ }
+ const val = this._dragHeights.get(docView.layoutDoc);
+ if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) });
+ }));
+ return false;
+ }
+
+ @action
+ onPointerUp = (e: PointerEvent): void => {
+ this._resizeHdlId = "";
+ this.Interacting = false;
+ this._resizeUndo?.end();
+ SnappingManager.clearSnapLines();
+
+ // detect autoHeight gesture and apply
+ SelectionManager.Views().map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) }))
+ .filter(pair => pair.hgts && pair.hgts.lowest < pair.hgts.start && pair.hgts.lowest <= 20)
+ .forEach(pair => pair.doc._autoHeight = true);
+ //need to change points for resize, or else rotation/control points will fail.
+ this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
+ .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
+ Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
+ ({
+ X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
+ Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
+ })));
+ Doc.SetNativeWidth(doc, undefined);
+ Doc.SetNativeHeight(doc, undefined);
+ });
+ }
+
+ @computed
+ get selectionTitle(): string {
+ if (SelectionManager.Views().length === 1) {
+ const selected = SelectionManager.Views()[0];
+ if (selected.ComponentView?.getTitle?.()) {
+ return selected.ComponentView.getTitle();
+ }
+ if (this._titleControlString.startsWith("=")) {
+ return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
+ }
+ if (this._titleControlString.startsWith("#")) {
+ return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-";
+ }
+ return this._accumulatedTitle;
}
- if (this._titleControlString.startsWith("#")) {
- return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-";
+ return SelectionManager.Views().length > 1 ? "-multiple-" : "-unset-";
+ }
+
+ @computed get hasIcons() {
+ return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === "layout_icon");
+ }
+
+ render() {
+ const bounds = this.Bounds;
+ const seldoc = SelectionManager.Views().slice(-1)[0];
+ if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+ return (null);
}
- return this._accumulatedTitle;
- }
- return SelectionManager.Views().length > 1 ? "-multiple-" : "-unset-";
- }
-
- @computed get canDelete() {
- return SelectionManager.Views().some(docView => {
- if (docView.rootDoc.stayInCollection) return false;
- const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
- //return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
- return (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin);
- });
- }
- @computed get hasIcons() {
- return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === "layout_icon");
- }
-
- render() {
- const bounds = this.Bounds;
- const seldoc = SelectionManager.Views().slice(-1)[0];
- if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
- return (null);
- }
- const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup;
- const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
- const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton);
- const canDelete = this.canDelete;
- const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
- <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
- <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()}
- onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(e => click!(e))))} >
- <FontAwesomeIcon icon={icon as any} />
- </div>
- </Tooltip>);
-
- const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
- const titleArea = hideTitle ? <div className="documentDecorations-title" onPointerDown={this.onTitleDown} key="title" /> :
- this._edtingTitle ?
- <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`}
- type="text" name="dynbox" autoComplete="on"
- value={this._accumulatedTitle}
- onBlur={e => this.titleBlur()}
- onChange={action(e => this._accumulatedTitle = e.target.value)}
- onKeyPress={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 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;
- const borderRadiusDraggerWidth = 15;
- 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 resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : "";
-
- return (<div className={`documentDecorations${colorScheme}`}>
- <div className="documentDecorations-background" style={{
- 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",
- 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={{
- 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), "Close")}
- {titleArea}
- {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
- {hideResizers ? (null) :
- <>
- <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
- <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-
- {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
- topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")}
- <div key="rot" className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
- onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown}
- onContextMenu={e => e.preventDefault()}>{useRotation && "⟲"}</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 >
- );
- }
+ // hide the decorations if the parent chooses to hide it or if the document itself hides it
+ const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup;
+ const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
+ const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar;
+ // if multiple documents have been opened at the same time, then don't show open button
+ const hideOpenButton = seldoc.props.hideOpenButton || seldoc.rootDoc.hideOpenButton ||
+ SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton);
+ const hideDeleteButton = seldoc.props.hideDeleteButton || seldoc.rootDoc.hideDeleteButton ||
+ SelectionManager.Views().some(docView => {
+ const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
+ return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
+ });
+ const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
+ <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
+ <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()}
+ onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(e => click!(e))))} >
+ <FontAwesomeIcon icon={icon as any} />
+ </div>
+ </Tooltip>);
+
+ const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
+ const titleArea = hideTitle ? (null) :
+ this._edtingTitle ?
+ <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`}
+ type="text" name="dynbox" autoComplete="on"
+ value={this._accumulatedTitle}
+ onBlur={e => this.titleBlur()}
+ onChange={action(e => this._accumulatedTitle = e.target.value)}
+ onKeyDown={this.titleEntered} /> :
+ <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
+ <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
+ </div>;
+
+
+ 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;
+ const borderRadiusDraggerWidth = 15;
+ 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 || 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: 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",
+ }}>
+ {hideDeleteButton ? <div /> : topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), "Close")}
+ {titleArea}
+ {hideOpenButton ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
+ {hideResizers ? (null) :
+ <>
+ <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
+ <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+
+ {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
+ topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")}
+ <div key="rot" className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
+ onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown}
+ onContextMenu={e => e.preventDefault()}>{useRotation && "⟲"}</div>
+ </>
+ }
+
+ {hideDocumentButtonBar ? (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 >
+ </>}
+ </div >
+ );
+ }
} \ No newline at end of file
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 50dca0a99..317f5f5d7 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -18,12 +18,14 @@ 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";
import { Touchable } from "./Touchable";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
+import { InkTranscription } from "./InkTranscription";
@observer
export class GestureOverlay extends Touchable {
@@ -73,6 +75,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 +128,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) {
+ InkTranscription.Instance.createInkGroup();
+ Doc.UserDoc().activeInkTool = InkTool.None;
this.prevPoints.set(pt.identifier, pt);
}
}
@@ -291,9 +297,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 +494,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) {
+ InkTranscription.Instance.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 +640,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);
+ this.dispatchGesture(GestureUtils.Gestures.Stroke);
+ // TODO: nda - check inks to group here
+ checkInksToGroup();
}
this._points = [];
}
@@ -808,7 +823,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 +896,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 6120c4c9e..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();
@@ -139,6 +138,10 @@ export class KeyManager {
window.getSelection()?.empty();
document.body.focus();
break;
+ case "enter": {
+ DocumentDecorations.Instance.onCloseClick(false);
+ break;
+ }
case "delete":
case "backspace":
if (document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA") {
@@ -169,7 +172,7 @@ export class KeyManager {
};
});
- private shift = action(async (keyname: string) => {
+ private shift = action((keyname: string) => {
const stopPropagation = false;
const preventDefault = false;
@@ -330,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/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index b15e4260d..ae35bc980 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -13,7 +13,6 @@ import { Colors } from "./global/globalEnums";
import { InkingStroke } from "./InkingStroke";
import { InkStrokeProperties } from "./InkStrokeProperties";
import { DocumentView } from "./nodes/DocumentView";
-
export interface InkHandlesProps {
inkDoc: Doc;
inkView: DocumentView;
@@ -103,7 +102,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
r={screenSpaceLineWidth * 2}
fill={Colors.MEDIUM_BLUE}
strokeWidth={1}
- stroke={Colors.MEDIUM_BLUE}
+ stroke={Colors.BLACK}
onPointerDown={e => this.onHandleDown(e, pts.I)}
pointerEvents="all"
cursor="default"
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..ab189c0d6
--- /dev/null
+++ b/src/client/views/InkTranscription.tsx
@@ -0,0 +1,334 @@
+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, InkTool } 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 { CurrentUserUtils } from '../util/CurrentUserUtils';
+
+
+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: process.env.IINKJS_APP,
+ hmacKey: process.env.IINKJS_HMAC,
+ 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;
+ this.groupInkDocs(selected, docView, word);
+ });
+ }
+
+ 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();
+ }
+ }
+ }
+
+ /**
+ * Creates the ink grouping once the user leaves the writing mode
+ */
+createInkGroup() {
+ // 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;
+ const newCollection = this.groupInkDocs(selected, ffView);
+ ffView.unprocessedDocs = [];
+
+ InkTranscription.Instance.transcribeInk(newCollection, ffView.layoutDoc, selected, false, ffView);
+ });
+ }
+ CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
+}
+
+/**
+ * Creates the groupings for a given list of ink docs on a specific doc view
+ * @param selected: the list of ink docs to create a grouping of
+ * @param docView: the view in which we want the grouping to be created
+ * @param word: optional param if the group we are creating is a word (subgrouping individual words)
+ * @returns a new collection Doc or undefined if the grouping fails
+ */
+ groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined {
+ const bounds: { x: number, y: number, width?: number, height?: number }[] = []
+
+ // calculate the necessary bounds from the selected ink docs
+ 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 });
+ }))
+
+ // calculate the aggregated bounds
+ const aggregBounds = aggregateBounds(bounds, 0, 0);
+ const marqViewRef = docView._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;
+ }
+
+ // map through all the selected ink strokes and create the groupings
+ 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);
+ // Gets a collection based on the selected nodes using a marquee view ref
+ const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+ if (newCollection) {
+ newCollection.height = newCollection[HeightSym]();
+ newCollection.width = newCollection[WidthSym]();
+ // if the grouping we are creating is an individual word
+ if (word) {
+ 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);
+ return newCollection;
+}
+
+ 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.scss b/src/client/views/MainView.scss
index 3799b6b13..a695577d0 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -20,6 +20,8 @@
position: relative;
width: 100%;
height: 100%;
+ display: flex;
+ flex-direction: column;
}
// add nodes menu. Note that the + button is actually an input label, not an actual button.
@@ -153,7 +155,8 @@
}
}
-.mainView-innerContent, .mainView-innerContent-Dark {
+.mainView-innerContent,
+.mainView-innerContent-Dark {
display: contents;
flex-direction: row;
position: relative;
@@ -186,8 +189,8 @@
.mainView-libraryHandle {
background-color: $light-gray;
}
-.mainView-innerContent-Dark
-{
+
+.mainView-innerContent-Dark {
.propertiesView {
background-color: #252525;
@@ -213,6 +216,7 @@
background: #353535;
}
}
+
.mainView-container-Dark {
.contextMenu-cont {
background: $medium-gray;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7ee9f5b70..15e1dbe18 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';
@@ -68,342 +69,342 @@ const _global = (window /* browser */ || global /* node */) as any;
@observer
export class MainView extends React.Component {
- public static Instance: MainView;
- public static Live: boolean = false;
- private _docBtnRef = React.createRef<HTMLDivElement>();
- @observable public LastButton: Opt<Doc>;
- @observable private _windowWidth: number = 0;
- @observable private _windowHeight: number = 0;
- @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
- @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
- @observable private _panelContent: string = "none";
- @observable private _sidebarContent: any = this.userDoc?.sidebar;
- @observable private _leftMenuFlyoutWidth: number = 0;
-
- @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 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 public get mainFreeform(): Opt<Doc> { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
-
- topMenuHeight = () => 35;
- topMenuWidth = returnZero; // value is ignored ...
- leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace("px", ""));
- leftMenuHeight = () => this._dashUIHeight;
- leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
- 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;
-
- componentDidMount() {
- document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!));
- const ele = document.getElementById("loader");
- const prog = document.getElementById("dash-progress");
- if (ele && prog) {
- // remove from DOM
- setTimeout(() => {
- clearTimeout();
- prog.style.transition = "1s";
- prog.style.width = "100%";
- }, 0);
- setTimeout(() => ele.outerHTML = '', 1000);
+ public static Instance: MainView;
+ public static Live: boolean = false;
+ private _docBtnRef = React.createRef<HTMLDivElement>();
+ @observable public LastButton: Opt<Doc>;
+ @observable private _windowWidth: number = 0;
+ @observable private _windowHeight: number = 0;
+ @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
+ @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
+ @observable private _panelContent: string = "none";
+ @observable private _sidebarContent: any = this.userDoc?.sidebar;
+ @observable private _leftMenuFlyoutWidth: number = 0;
+
+ @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 topOfHeaderBarDoc() { return this.topOfDashUI; }
+ @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", ""));
+ leftMenuHeight = () => this._dashUIHeight;
+ leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
+ 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() - this.leftMenuFlyoutWidth();
+ mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
+
+ componentDidMount() {
+ document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!));
+ const ele = document.getElementById("loader");
+ const prog = document.getElementById("dash-progress");
+ if (ele && prog) {
+ // remove from DOM
+ setTimeout(() => {
+ clearTimeout();
+ prog.style.transition = "1s";
+ prog.style.width = "100%";
+ }, 0);
+ setTimeout(() => ele.outerHTML = '', 1000);
+ }
+ this._sidebarContent.proto = undefined;
+ if (!MainView.Live) {
+ 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)));
+
+ const tag = document.createElement('script');
+ tag.src = "https://www.youtube.com/iframe_api";
+ const firstScriptTag = document.getElementsByTagName('script')[0];
+ firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
+ window.removeEventListener("keydown", KeyManager.Instance.handle);
+ window.addEventListener("keydown", KeyManager.Instance.handle);
+ window.removeEventListener("keyup", KeyManager.Instance.unhandle);
+ window.addEventListener("keyup", KeyManager.Instance.unhandle);
+ 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));
+ });
+ document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener);
+ this.initEventListeners();
+ }
+
+ componentWillUnMount() {
+ window.removeEventListener("keyup", KeyManager.Instance.unhandle);
+ window.removeEventListener("keydown", KeyManager.Instance.handle);
+ window.removeEventListener("pointerdown", this.globalPointerDown);
+ window.removeEventListener("paste", KeyManager.Instance.paste as any);
+ document.removeEventListener("linkAnnotationToDash", Hypothesis.linkListener);
+ }
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ MainView.Instance = this;
+ CurrentUserUtils._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
+
+ // causes errors to be generated when modifying an observable outside of an action
+ configure({ enforceActions: "observed" });
+
+ if (window.location.pathname !== "/home") {
+ const pathname = window.location.pathname.substr(1).split("/");
+ if (pathname.length > 1 && pathname[0] === "doc") {
+ CurrentUserUtils.MainDocId = pathname[1];
+ !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (CurrentUserUtils.GuestTarget = field)));
}
- 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",
- "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's
+ }
+
+ library.add(...[fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar,
+ fa.faSquare, far.faSquare as any, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
+ fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
+ fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
+ fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow,
+ fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
+ fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp,
+ 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.faVideoSlash, 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, 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.faVolumeDown, fa.faSquareRootAlt]);
+ this.initAuthenticationRouters();
+ }
+
+ globalPointerDown = action((e: PointerEvent) => {
+ AudioBox.Enabled = true;
+ const targets = document.elementsFromPoint(e.x, e.y);
+ if (targets.length) {
+ const targClass = targets[0].className.toString();
+ !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
+ !["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu();
+ }
+ });
+
+ initEventListeners = () => {
+ window.addEventListener("drop", e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
+ window.addEventListener("dragover", e => e.preventDefault(), false);
+ // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
+ document.addEventListener("pointerdown", this.globalPointerDown);
+ document.addEventListener("click", (e: MouseEvent) => {
+ if (!e.cancelBubble) {
+ const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join();
+ if (pathstr?.includes("libraryFlyout")) {
+ SelectionManager.DeselectAll();
+ }
}
- DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
-
- const tag = document.createElement('script');
- tag.src = "https://www.youtube.com/iframe_api";
- const firstScriptTag = document.getElementsByTagName('script')[0];
- firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
- window.removeEventListener("keydown", KeyManager.Instance.handle);
- window.addEventListener("keydown", KeyManager.Instance.handle);
- window.removeEventListener("keyup", KeyManager.Instance.unhandle);
- window.addEventListener("keyup", KeyManager.Instance.unhandle);
- 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));
+ }, false);
+ document.oncontextmenu = () => false;
+ }
+
+ initAuthenticationRouters = async () => {
+ // Load the user's active dashboard, or create a new one if initial session after signup
+ const received = CurrentUserUtils.MainDocId;
+ if (received && !this.userDoc) {
+ reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true });
+ } else {
+ PromiseValue(this.userDoc.activeDashboard).then(dash => {
+ if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash);
+ else CurrentUserUtils.createNewDashboard(this.userDoc);
});
- document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener);
- this.initEventListeners();
- }
-
- componentWillUnMount() {
- window.removeEventListener("keyup", KeyManager.Instance.unhandle);
- window.removeEventListener("keydown", KeyManager.Instance.handle);
- window.removeEventListener("pointerdown", this.globalPointerDown);
- window.removeEventListener("paste", KeyManager.Instance.paste as any);
- document.removeEventListener("linkAnnotationToDash", Hypothesis.linkListener);
- }
-
- constructor(props: Readonly<{}>) {
- super(props);
- MainView.Instance = this;
- CurrentUserUtils._urlState = HistoryUtil.parseUrl(window.location) || {} as any;
-
- // causes errors to be generated when modifying an observable outside of an action
- configure({ enforceActions: "observed" });
-
- if (window.location.pathname !== "/home") {
- const pathname = window.location.pathname.substr(1).split("/");
- if (pathname.length > 1 && pathname[0] === "doc") {
- CurrentUserUtils.MainDocId = pathname[1];
- !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (CurrentUserUtils.GuestTarget = field)));
- }
- }
-
- library.add(...[fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar, fa.faVideoSlash, fa.faVideo,
- fa.faSquare, far.faSquare as any, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
- fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
- fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
- fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow,
- fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
- fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp,
- 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.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.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.faVolumeDown]);
- this.initAuthenticationRouters();
- }
-
- globalPointerDown = action((e: PointerEvent) => {
- AudioBox.Enabled = true;
- const targets = document.elementsFromPoint(e.x, e.y);
- if (targets.length) {
- const targClass = targets[0].className.toString();
- !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
- !["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu();
- }
- });
-
- initEventListeners = () => {
- window.addEventListener("drop", e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
- window.addEventListener("dragover", e => e.preventDefault(), false);
- // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
- document.addEventListener("pointerdown", this.globalPointerDown);
- document.addEventListener("click", (e: MouseEvent) => {
- if (!e.cancelBubble) {
- const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join();
- if (pathstr?.includes("libraryFlyout")) {
- SelectionManager.DeselectAll();
- }
- }
- }, false);
- document.oncontextmenu = () => false;
- }
-
- initAuthenticationRouters = async () => {
- // Load the user's active dashboard, or create a new one if initial session after signup
- const received = CurrentUserUtils.MainDocId;
- if (received && !this.userDoc) {
- reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true });
- } else {
- if (received && CurrentUserUtils._urlState.sharing) {
- reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized,
- initialized => initialized && received && DocServer.GetRefField(received).then(docField => {
- if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) {
- CollectionDockingView.AddSplit(docField, "right");
- }
- }),
- );
- }
- const activeDash = PromiseValue(this.userDoc.activeDashboard);
- activeDash.then(dash => {
- if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash);
- else CurrentUserUtils.createNewDashboard(this.userDoc);
- });
- }
- }
-
- @action
- createNewPresentation = async () => {
- if (!await this.userDoc.myTrails) {
- this.userDoc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], {
- 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");
- this.userDoc.activePresentation = pres;
- Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres);
- }
-
- @action
- createNewFolder = async () => {
- if (!await this.userDoc.myFilesystem) {
- this.userDoc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
- const newFolder = ScriptField.MakeFunction(`createNewFolder()`, { scriptContext: "any" })!;
- const newFolderButton: Doc = Docs.Create.FontIconDocument({
- onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder",
- btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true
- });
- this.userDoc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([this.userDoc.myFileOrphans as Doc], {
- 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,
- _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."
- }));
- }
- const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
- Doc.AddDocToList(this.userDoc.myFilesystem as Doc, "data", folder);
- }
-
- @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}
- />;
- }
-
- @computed get dockingContent() {
- return <div key="docking" className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? "-flyout" : ""}`} onDrop={e => { e.stopPropagation(); e.preventDefault(); }}
- style={{
- minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
- transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined,
- }}>
- {!this.mainContainer ? (null) : this.mainDocView}
- </div>;
- }
-
- @action
- onPropertiesPointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e,
- action(e => (CurrentUserUtils.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false),
- action(() => CurrentUserUtils.propertiesWidth < 5 && (CurrentUserUtils.propertiesWidth = 0)),
- action(() => CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0), false);
- }
-
- @action
- onFlyoutPointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e,
- action(e => (this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false),
- () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
- this.closeFlyout);
- }
-
- sidebarScreenToLocal = () => new Transform(0, -this.topOfMainDoc, 1);
- mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
- addDocTabFunc = (doc: Doc, location: string): boolean => {
- const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":");
- const locationParams = locationFields.length > 1 ? locationFields[1] : "";
- if (doc.dockingConfig) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
- switch (locationFields[0]) {
- case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
- case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
- case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
- case "lightbox": return LightboxView.AddDocTab(doc, location);
- case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams);
- case "inPlace":
- case "add":
- default:
- return CollectionDockingView.AddSplit(doc, locationParams);
- }
- }
-
-
- @computed get flyout() {
- return !this._leftMenuFlyoutWidth ? <div key="flyout" className={`mainView-libraryFlyout-out`}>
- {this.docButtons}
- </div> :
- <div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }} >
- <div className="mainView-contentArea" >
- <DocumentView
- Document={this._sidebarContent.proto || this._sidebarContent}
- DataDoc={undefined}
- addDocument={undefined}
- 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}
- ScreenToLocalTransform={this.mainContainerXf}
- PanelWidth={this.leftMenuFlyoutWidth}
- PanelHeight={this.leftMenuFlyoutHeight}
- renderDepth={0}
- isContentActive={returnTrue}
- scriptContext={CollectionDockingView.Instance?.props.Document}
- focus={DocUtils.DefaultFocus}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- />
- </div>
- {this.docButtons}
- </div>;
- }
-
- @computed get leftMenuPanel() {
- return <div key="menu" className="mainView-leftMenuPanel">
- <DocumentView
- Document={Doc.UserDoc().menuStack as Doc}
+ }
+ }
+
+ @action
+ createNewPresentation = async () => {
+ if (!await this.userDoc.myTrails) {
+ this.userDoc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], {
+ title: "TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true
+ }));
+ }
+ 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);
+ }
+
+ @action
+ createNewFolder = async () => {
+ if (!await this.userDoc.myFilesystem) {
+ this.userDoc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
+ const newFolder = ScriptField.MakeFunction(`createNewFolder()`, { scriptContext: "any" })!;
+ const newFolderButton: Doc = Docs.Create.FontIconDocument({
+ onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder",
+ btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true
+ });
+ this.userDoc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([this.userDoc.myFileOrphans as Doc], {
+ 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: 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."
+ }));
+ }
+ const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
+ 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 className="mainView-headerBar" 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 <>
+ {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() {
+ return <div key="docking" className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? "-flyout" : ""}`} onDrop={e => { e.stopPropagation(); e.preventDefault(); }}
+ style={{
+ minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
+ transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined,
+ }}>
+ {!this.mainContainer ? (null) : this.mainDocView}
+ </div>;
+ }
+
+ @action
+ onPropertiesPointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e,
+ action(e => (CurrentUserUtils.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false),
+ action(() => CurrentUserUtils.propertiesWidth < 5 && (CurrentUserUtils.propertiesWidth = 0)),
+ action(() => CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0), false);
+ }
+
+ @action
+ onFlyoutPointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e,
+ action(e => (this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false),
+ () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
+ this.closeFlyout);
+ }
+
+ 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(":");
+ const locationParams = locationFields.length > 1 ? locationFields[1] : "";
+ if (doc.dockingConfig) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+ switch (locationFields[0]) {
+ case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
+ case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
+ case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
+ case "lightbox": return LightboxView.AddDocTab(doc, location);
+ case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams);
+ case "inPlace":
+ case "add":
+ default:
+ return CollectionDockingView.AddSplit(doc, locationParams);
+ }
+ }
+
+
+ @computed get flyout() {
+ return !this._leftMenuFlyoutWidth ? <div key="flyout" className={`mainView-libraryFlyout-out`}>
+ {this.docButtons}
+ </div> :
+ <div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }} >
+ <div className="mainView-contentArea" >
+ <DocumentView
+ Document={this._sidebarContent.proto || this._sidebarContent}
DataDoc={undefined}
addDocument={undefined}
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
+ docViewPath={returnEmptyDoclist}
+ styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === Doc.UserDoc().myFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
rootSelected={returnTrue}
removeDocument={returnFalse}
- ScreenToLocalTransform={this.sidebarScreenToLocal}
- PanelWidth={this.leftMenuWidth}
- PanelHeight={this.leftMenuHeight}
+ ScreenToLocalTransform={this.mainContainerXf}
+ PanelWidth={this.leftMenuFlyoutWidth}
+ PanelHeight={this.leftMenuFlyoutHeight}
renderDepth={0}
- docViewPath={returnEmptyDoclist}
- focus={DocUtils.DefaultFocus}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
isContentActive={returnTrue}
+ scriptContext={CollectionDockingView.Instance?.props.Document}
+ focus={DocUtils.DefaultFocus}
whenChildContentsActiveChanged={emptyFunction}
bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
@@ -411,301 +412,325 @@ export class MainView extends React.Component {
searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
- scriptContext={this}
- />
+ />
+ </div>
+ {this.docButtons}
</div>;
- }
-
- @action
- selectMenu = (button: Doc) => {
- const title = StrCast(Doc.GetProto(button).title);
- const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
- this.closeFlyout();
- if (willOpen) {
- switch (this._panelContent = title) {
- case "Settings":
- SettingsManager.Instance.open();
- break;
- case "Help":
- break;
- default:
- this.expandFlyout(button);
- }
+ }
+
+ @computed get leftMenuPanel() {
+ return <div key="menu" className="mainView-leftMenuPanel">
+ <DocumentView
+ Document={Doc.UserDoc().menuStack as Doc}
+ DataDoc={undefined}
+ addDocument={undefined}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ rootSelected={returnTrue}
+ removeDocument={returnFalse}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
+ PanelWidth={this.leftMenuWidth}
+ PanelHeight={this.leftMenuHeight}
+ renderDepth={0}
+ docViewPath={returnEmptyDoclist}
+ focus={DocUtils.DefaultFocus}
+ styleProvider={DefaultStyleProvider}
+ isContentActive={returnTrue}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ scriptContext={this}
+ />
+ </div>;
+ }
+
+ @action
+ selectMenu = (button: Doc) => {
+ const title = StrCast(Doc.GetProto(button).title);
+ const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
+ this.closeFlyout();
+ if (willOpen) {
+ switch (this._panelContent = title) {
+ case "Settings":
+ SettingsManager.Instance.open();
+ break;
+ case "Help":
+ break;
+ default:
+ this.expandFlyout(button);
}
- return true;
- }
-
- @computed get mainInnerContent() {
- const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth();
- const width = this.propertiesWidth() + leftMenuFlyoutWidth;
- return <>
- {this.leftMenuPanel}
- <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
- {this.flyout}
- <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
- <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
- </div>
- <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
-
- {this.dockingContent}
-
- <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
- <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
- </div>
- <div className="properties-container" style={{ width: this.propertiesWidth() }}>
- {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={this.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
- </div>
- </div>
- </div>
- </>;
- }
-
- @computed get mainDashboardArea() {
- return !this.userDoc ? (null) :
- <div className="mainView-dashboardArea" ref={r => {
- r && new _global.ResizeObserver(action(() => {
- this._dashUIWidth = r.getBoundingClientRect().width;
- this._dashUIHeight = r.getBoundingClientRect().height;
- })).observe(r);
- }} style={{
- color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black",
- height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`,
- width: "100%",
- }} >
- {this.mainInnerContent}
- </div>;
- }
-
-
- expandFlyout = action((button: Doc) => {
- // bcz: What's going on here!?
- // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div
- // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
- // interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
- this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250);
- setTimeout(action(() => this._leftMenuFlyoutWidth += 0.5), 0);
-
- this._sidebarContent.proto = button.target as any;
- this.LastButton = button;
- });
-
- closeFlyout = action(() => {
- this.LastButton = undefined;
- this._panelContent = "none";
- this._sidebarContent.proto = undefined;
- this._leftMenuFlyoutWidth = 0;
- });
-
- remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
- moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
- addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
-
- buttonBarXf = () => {
- if (!this._docBtnRef.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
- return new Transform(-translateX, -translateY, 1 / scale);
- }
-
- @computed get docButtons() {
- return !(this.userDoc.dockedBtns instanceof Doc) ? (null) :
- <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !this.userDoc.dockedBtns.linearViewIsExpanded ? "42px" : undefined }} >
- <CollectionLinearView
- Document={this.userDoc.dockedBtns}
- DataDoc={undefined}
- fieldKey={"data"}
- dropAction={"alias"}
- setHeight={returnFalse}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- rootSelected={returnTrue}
- bringToFront={emptyFunction}
- select={emptyFunction}
- isAnyChildContentActive={returnFalse}
- isContentActive={emptyFunction}
- isSelected={returnFalse}
- docViewPath={returnEmptyDoclist}
- moveDocument={this.moveButtonDoc}
- CollectionView={undefined}
- addDocument={this.addButtonDoc}
- addDocTab={this.addDocTabFunc}
- pinToPres={emptyFunction}
- removeDocument={this.remButtonDoc}
- ScreenToLocalTransform={this.buttonBarXf}
- PanelWidth={this.leftMenuFlyoutWidth}
- PanelHeight={this.leftMenuFlyoutHeight}
- renderDepth={0}
- focus={DocUtils.DefaultFocus}
- whenChildContentsActiveChanged={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{this.userDoc?.presentationMode}</div> : <></>}
- </div>;
- }
- @computed get snapLines() {
- return !this.userDoc.showSnapLines ? (null) : <div className="mainView-snapLines">
- <svg style={{ width: "100%", height: "100%" }}>
- {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
- {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
- </svg>
+ }
+ return true;
+ }
+
+ @computed get mainInnerContent() {
+ const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth();
+ const width = this.propertiesWidth() + leftMenuFlyoutWidth;
+ return <>
+ {this.leftMenuPanel}
+ <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
+ {this.flyout}
+ <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
+ <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
+ </div>
+ <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
+
+ {this.dockingContent}
+
+ <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
+ <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
+ </div>
+ <div className="properties-container" style={{ width: this.propertiesWidth() }}>
+ {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={this.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
+ </div>
+ </div>
+ </div>
+ </>;
+ }
+
+
+ @computed get mainDashboardArea() {
+ return !this.userDoc ? (null) :
+ <div className="mainView-dashboardArea" ref={r => {
+ r && new _global.ResizeObserver(action(() => {
+ this._dashUIWidth = r.getBoundingClientRect().width;
+ this._dashUIHeight = r.getBoundingClientRect().height;
+ })).observe(r);
+ }} style={{
+ color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black",
+ height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`,
+ width: "100%",
+ }} >
+ {this.mainInnerContent}
+ </div>;
+ }
+
+
+ expandFlyout = action((button: Doc) => {
+ // bcz: What's going on here!?
+ // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div
+ // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
+ // interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
+ this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250);
+ setTimeout(action(() => this._leftMenuFlyoutWidth += 0.5), 0);
+
+ this._sidebarContent.proto = button.target as any;
+ this.LastButton = button;
+ });
+
+ closeFlyout = action(() => {
+ this.LastButton = undefined;
+ this._panelContent = "none";
+ this._sidebarContent.proto = undefined;
+ this._leftMenuFlyoutWidth = 0;
+ });
+
+ remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
+ moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
+ addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true);
+
+ buttonBarXf = () => {
+ if (!this._docBtnRef.current) return Transform.Identity();
+ const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+ return new Transform(-translateX, -translateY, 1 / scale);
+ }
+
+ @computed get docButtons() {
+ return !(this.userDoc.dockedBtns instanceof Doc) ? (null) :
+ <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !this.userDoc.dockedBtns.linearViewIsExpanded ? "42px" : undefined }} >
+ <CollectionLinearView
+ Document={this.userDoc.dockedBtns}
+ DataDoc={undefined}
+ fieldKey={"data"}
+ dropAction={"alias"}
+ setHeight={returnFalse}
+ styleProvider={DefaultStyleProvider}
+ rootSelected={returnTrue}
+ bringToFront={emptyFunction}
+ select={emptyFunction}
+ isAnyChildContentActive={returnFalse}
+ isContentActive={emptyFunction}
+ isSelected={returnFalse}
+ docViewPath={returnEmptyDoclist}
+ moveDocument={this.moveButtonDoc}
+ CollectionView={undefined}
+ addDocument={this.addButtonDoc}
+ addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
+ removeDocument={this.remButtonDoc}
+ ScreenToLocalTransform={this.buttonBarXf}
+ PanelWidth={this.leftMenuFlyoutWidth}
+ PanelHeight={this.leftMenuFlyoutHeight}
+ renderDepth={0}
+ focus={DocUtils.DefaultFocus}
+ whenChildContentsActiveChanged={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{this.userDoc?.presentationMode}</div> : <></>}
</div>;
- }
-
- @computed get inkResources() {
- return <svg width={0} height={0}>
- <defs>
- <filter id="inkSelectionHalo">
- <feColorMatrix type="matrix"
- result="color"
- values="1 0 0 0 0
+ }
+ @computed get snapLines() {
+ return !this.userDoc.showSnapLines ? (null) : <div className="mainView-snapLines">
+ <svg style={{ width: "100%", height: "100%" }}>
+ {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+ {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)}
+ </svg>
+ </div>;
+ }
+
+ @computed get inkResources() {
+ return <svg width={0} height={0}>
+ <defs>
+ <filter id="inkSelectionHalo">
+ <feColorMatrix type="matrix"
+ result="color"
+ values="1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0">
- </feColorMatrix>
- <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur>
- <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset>
- <feMerge>
- <feMergeNode in="bg"></feMergeNode>
- <feMergeNode in="offset"></feMergeNode>
- <feMergeNode in="SourceGraphic"></feMergeNode>
- </feMerge>
- </filter>
- </defs>
- </svg>;
- }
-
- @computed get topbar() {
- TraceMobx();
- return <div className="mainView-topbar">
- <TopBar />
+ </feColorMatrix>
+ <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur>
+ <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset>
+ <feMerge>
+ <feMergeNode in="bg"></feMergeNode>
+ <feMergeNode in="offset"></feMergeNode>
+ <feMergeNode in="SourceGraphic"></feMergeNode>
+ </feMerge>
+ </filter>
+ </defs>
+ </svg>;
+ }
+
+ @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
+ return !DocumentLinksButton.invisibleWebDoc ? null :
+ <div className="mainView-invisibleWebRef" ref={DocumentLinksButton.invisibleWebRef}>
+ <WebBox
+ fieldKey={"data"}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ Document={DocumentLinksButton.invisibleWebDoc}
+ dropAction={"move"}
+ styleProvider={undefined}
+ isSelected={returnFalse}
+ select={returnFalse}
+ setHeight={returnFalse}
+ rootSelected={returnFalse}
+ renderDepth={0}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ bringToFront={returnFalse}
+ isContentActive={emptyFunction}
+ whenChildContentsActiveChanged={returnFalse}
+ focus={returnFalse}
+ docViewPath={returnEmptyDoclist}
+ PanelWidth={() => 500}
+ PanelHeight={() => 800}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ />
</div>;
- }
-
- @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
- return !DocumentLinksButton.invisibleWebDoc ? null :
- <div className="mainView-invisibleWebRef" ref={DocumentLinksButton.invisibleWebRef}>
+ }
+
+ render() {
+ return (<div className={`mainView-container${this.colorScheme}`}
+ onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)}
+ ref={r => {
+ r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r);
+ }}>
+ {this.inkResources}
+ <DictationOverlay />
+ <SharingManager />
+ <SettingsManager />
+ <CaptureManager />
+ <GroupManager />
+ <GoogleAuthenticationManager />
+ <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
+ <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
+ <TopBar />
+ {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
+ {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => DocumentLinksButton.LinkEditorDocView = undefined)} docView={DocumentLinksButton.LinkEditorDocView} /> : (null)}
+ {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
+ <div style={{ position: "relative", display: LightboxView.LightboxDoc ? "none" : undefined, zIndex: 1999 }} >
+ <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
+ </div>
+ <GestureOverlay >
+ {this.mainDashboardArea}
+ </GestureOverlay>
+ <PreviewCursor />
+ <TaskCompletionBox />
+ <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]} />
+ </div >);
+ }
+
+ makeWebRef = (ele: HTMLDivElement) => {
+ reaction(() => DocumentLinksButton.invisibleWebDoc,
+ invisibleDoc => {
+ ReactDOM.unmountComponentAtNode(ele);
+ invisibleDoc && ReactDOM.render(<span title="Drag as document" className="invisible-webbox" >
+ <div className="mainView-webRef" ref={DocumentLinksButton.invisibleWebRef}>
<WebBox
- fieldKey={"data"}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- Document={DocumentLinksButton.invisibleWebDoc}
- dropAction={"move"}
- layerProvider={undefined}
- styleProvider={undefined}
- isSelected={returnFalse}
- select={returnFalse}
- setHeight={returnFalse}
- rootSelected={returnFalse}
- renderDepth={0}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- bringToFront={returnFalse}
- isContentActive={emptyFunction}
- whenChildContentsActiveChanged={returnFalse}
- focus={returnFalse}
- docViewPath={returnEmptyDoclist}
- PanelWidth={() => 500}
- PanelHeight={() => 800}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
+ fieldKey={"data"}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ Document={invisibleDoc}
+ dropAction={"move"}
+ isSelected={returnFalse}
+ docViewPath={returnEmptyDoclist}
+ select={returnFalse}
+ rootSelected={returnFalse}
+ renderDepth={0}
+ setHeight={returnFalse}
+ styleProvider={undefined}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ bringToFront={returnFalse}
+ isContentActive={emptyFunction}
+ whenChildContentsActiveChanged={returnFalse}
+ focus={returnFalse}
+ PanelWidth={() => 500}
+ PanelHeight={() => 800}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
/>
- </div>;
- }
-
- render() {
- return (<div className={`mainView-container${this.colorScheme}`}
- onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)}
- ref={r => {
- r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r);
- }}>
- {this.inkResources}
- <DictationOverlay />
- <SharingManager />
- <SettingsManager />
- <CaptureManager />
- <GroupManager />
- <GoogleAuthenticationManager />
- <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
- <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
- {this.topbar}
- {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
- {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => DocumentLinksButton.LinkEditorDocView = undefined)} docView={DocumentLinksButton.LinkEditorDocView} /> : (null)}
- {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
- <div style={{ position: "relative", display: LightboxView.LightboxDoc ? "none" : undefined, zIndex: 2001 }} >
- <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
- </div>
- <GestureOverlay >
- {this.mainDashboardArea}
- </GestureOverlay>
- <PreviewCursor />
- <TaskCompletionBox />
- <ContextMenu />
- <RadialMenu />
- <AnchorMenu />
- <MarqueeOptionsMenu />
- <OverlayView />
- <TimelineMenu />
- <RichTextMenu />
- {this.snapLines}
- <div className="mainView-webRef" ref={this.makeWebRef} />
- <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
- </div >);
- }
-
- makeWebRef = (ele: HTMLDivElement) => {
- reaction(() => DocumentLinksButton.invisibleWebDoc,
- invisibleDoc => {
- ReactDOM.unmountComponentAtNode(ele);
- invisibleDoc && ReactDOM.render(<span title="Drag as document" className="invisible-webbox" >
- <div className="mainView-webRef" ref={DocumentLinksButton.invisibleWebRef}>
- <WebBox
- fieldKey={"data"}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- Document={invisibleDoc}
- dropAction={"move"}
- isSelected={returnFalse}
- docViewPath={returnEmptyDoclist}
- select={returnFalse}
- rootSelected={returnFalse}
- renderDepth={0}
- setHeight={returnFalse}
- layerProvider={undefined}
- styleProvider={undefined}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- bringToFront={returnFalse}
- isContentActive={emptyFunction}
- whenChildContentsActiveChanged={returnFalse}
- focus={returnFalse}
- PanelWidth={() => 500}
- PanelHeight={() => 800}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- />
- </div>;
- </span>, ele);
-
- let success = false;
- const onSuccess = () => {
- success = true;
- clearTimeout(interval);
- document.removeEventListener("editSuccess", onSuccess);
- };
-
- // For some reason, Hypothes.is annotations don't load until a click is registered on the page,
- // so we keep simulating clicks until annotations have loaded and editing is successful
- const interval = setInterval(() => !success && simulateMouseClick(ele, 50, 50, 50, 50), 500);
- setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
- document.addEventListener("editSuccess", onSuccess);
- });
- }
+ </div>;
+ </span>, ele);
+
+ let success = false;
+ const onSuccess = () => {
+ success = true;
+ clearTimeout(interval);
+ document.removeEventListener("editSuccess", onSuccess);
+ };
+
+ // For some reason, Hypothes.is annotations don't load until a click is registered on the page,
+ // so we keep simulating clicks until annotations have loaded and editing is successful
+ const interval = setInterval(() => !success && simulateMouseClick(ele, 50, 50, 50, 50), 500);
+ setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
+ document.addEventListener("editSuccess", onSuccess);
+ });
+ }
}
ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); }); \ No newline at end of file
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.scss b/src/client/views/OverlayView.scss
index 42efecb98..302e7a5e3 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -56,5 +56,6 @@
.overlayView-doc {
z-index: 9002; //so that it appears above chroma
position: absolute;
- pointer-events: all;
+ top: 0;
+ left: 0;
} \ No newline at end of file
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 4b8e87b0f..ab5dc74c9 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,11 +1,13 @@
+import { docs } from "googleapis/build/src/apis/docs";
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
+import { computedFn } from "mobx-utils";
import * as React from "react";
import ReactLoading from 'react-loading';
import { Doc, WidthSym, HeightSym } 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 +148,14 @@ 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;
+ }
+
+ docScreenToLocalXf = computedFn(function docScreenToLocalXf(this: any, doc: Doc) {
+ return () => new Transform(-NumCast(doc.x), -NumCast(doc.y), 1);
+ }.bind(this));
@computed get overlayDocs() {
return CurrentUserUtils.OverlayDocs?.map(d => {
@@ -179,24 +189,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)` }}>
<DocumentView
Document={d}
rootSelected={returnTrue}
bringToFront={emptyFunction}
addDocument={undefined}
- removeDocument={doc => (doc instanceof Doc ? [doc] : doc).map(doc => Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), "data", doc)).length ? true : false}
+ removeDocument={this.removeOverlayDoc}
PanelWidth={d[WidthSym]}
PanelHeight={d[HeightSym]}
- ScreenToLocalTransform={() => new Transform(-NumCast(d.x), -NumCast(d.y), 1)}
+ ScreenToLocalTransform={this.docScreenToLocalXf(d)}
renderDepth={1}
isDocumentActive={returnTrue}
- isContentActive={emptyFunction}
+ 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/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 24857d8ee..f24ff09db 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -3,25 +3,23 @@ import { Tooltip } from '@material-ui/core';
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, Opt } from "../../fields/Doc";
+import { Id } from "../../fields/FieldSymbols";
import { InkField } from '../../fields/InkField';
import { RichTextField } from '../../fields/RichTextField';
import { BoolCast, StrCast } from "../../fields/Types";
+import { ImageField } from "../../fields/URLField";
import { DocUtils } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
import { SelectionManager } from '../util/SelectionManager';
import { undoBatch } from '../util/UndoManager';
import { CollectionViewType } from './collections/CollectionView';
+import { Colors } from "./global/globalEnums";
import { InkingStroke } from './InkingStroke';
import { DocumentView } from './nodes/DocumentView';
+import { VideoBox } from "./nodes/VideoBox";
+import { pasteImageBitmap } from "./nodes/WebBoxRenderer";
import './PropertiesButtons.scss';
import React = require("react");
-import { Colors } from "./global/globalEnums";
-import { CollectionFreeFormView } from "./collections/collectionFreeForm";
-import { DocumentManager } from "../util/DocumentManager";
-import { pasteImageBitmap } from "./nodes/WebBoxRenderer";
-import { VideoBox } from "./nodes/VideoBox";
-import { Id } from "../../fields/FieldSymbols";
-import { ImageField } from "../../fields/URLField";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
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..4fecfa4d9 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -123,8 +123,9 @@ export class ScriptingRepl extends React.Component {
let stopProp = true;
switch (e.key) {
case "Enter": {
+ e.stopPropagation();
const docGlobals: { [name: string]: any } = {};
- DocumentManager.Instance.DocumentViews.forEach((dv, i) => docGlobals[`d${i}`] = dv.props.Document);
+ Array.from(DocumentManager.Instance.DocumentViews).forEach((dv, i) => docGlobals[`d${i}`] = dv.props.Document);
const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals);
const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer(), globals });
if (!script.compiled) {
@@ -212,7 +213,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..a790a0475 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -26,6 +26,7 @@ import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
import { CollectionViewType } from './CollectionView';
import { TabDocView } from './TabDocView';
import React = require("react");
+import { SelectionManager } from '../../util/SelectionManager';
const _global = (window /* browser */ || global /* node */) as any;
@observer
@@ -50,9 +51,8 @@ export class CollectionDockingView extends CollectionSubView() {
public _flush: UndoManager.Batch | undefined;
private _ignoreStateChange = "";
public tabMap: Set<any> = new Set();
- public get initialized() { return this._goldenLayout !== null; }
public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; }
- @observable private _goldenLayout: any = null;
+ private _goldenLayout: any = null;
constructor(props: SubCollectionViewProps) {
super(props);
@@ -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();
@@ -116,6 +118,7 @@ export class CollectionDockingView extends CollectionSubView() {
@undoBatch
public static OpenFullScreen(doc: Doc) {
+ SelectionManager.DeselectAll();
const instance = CollectionDockingView.Instance;
if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc);
@@ -169,12 +172,6 @@ export class CollectionDockingView extends CollectionSubView() {
@undoBatch
@action
public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) {
- if (document.type === DocumentType.PRES) {
- const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []);
- if (docs.includes(document)) {
- docs.splice(docs.indexOf(document), 1);
- }
- }
if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document);
const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document);
@@ -183,19 +180,8 @@ export class CollectionDockingView extends CollectionSubView() {
return true;
}
const instance = CollectionDockingView.Instance;
+ const glayRoot = instance._goldenLayout.root;
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) {
@@ -203,29 +189,29 @@ export class CollectionDockingView extends CollectionSubView() {
stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]);
} else {
const newItemStackConfig = { type: 'stack', content: [docContentConfig] };
- const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
- if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
- instance._goldenLayout.root.addChild(newContentItem);
- } else if (instance._goldenLayout.root.contentItems[0].isStack) {
- instance._goldenLayout.root.contentItems[0].addChild(docContentConfig);
+ const newContentItem = glayRoot.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+ if (glayRoot.contentItems.length === 0) { // if no rows / columns
+ glayRoot.addChild(newContentItem);
+ } else if (glayRoot.contentItems[0].isStack) {
+ glayRoot.contentItems[0].addChild(docContentConfig);
} else if (
- instance._goldenLayout.root.contentItems.length === 1 &&
- instance._goldenLayout.root.contentItems[0].contentItems.length === 1 &&
- instance._goldenLayout.root.contentItems[0].contentItems[0].contentItems.length === 0) {
- instance._goldenLayout.root.contentItems[0].contentItems[0].addChild(docContentConfig);
+ glayRoot.contentItems.length === 1 &&
+ glayRoot.contentItems[0].contentItems.length === 1 &&
+ glayRoot.contentItems[0].contentItems[0].contentItems.length === 0) {
+ glayRoot.contentItems[0].contentItems[0].addChild(docContentConfig);
}
else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
switch (pullSide) {
default:
- case "right": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
- case "left": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
+ case "right": glayRoot.contentItems[0].addChild(newContentItem); break;
+ case "left": glayRoot.contentItems[0].addChild(newContentItem, 0); break;
case "top":
case "bottom":
// if not going in a row layout, must add already existing content into column
- const rowlayout = instance._goldenLayout.root.contentItems[0];
+ const rowlayout = glayRoot.contentItems[0];
const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
- CollectionDockingView.Instance._goldenLayout.saveScrollTops(rowlayout.element);
+ instance._goldenLayout.saveScrollTops(rowlayout.element);
rowlayout.parent.replaceChild(rowlayout, newColumn);
if (pullSide === "top") {
newColumn.addChild(rowlayout, undefined, true);
@@ -234,23 +220,23 @@ export class CollectionDockingView extends CollectionSubView() {
newColumn.addChild(newContentItem, undefined, true);
newColumn.addChild(rowlayout, 0, true);
}
- CollectionDockingView.Instance._goldenLayout.restoreScrollTops(rowlayout.element);
+ instance._goldenLayout.restoreScrollTops(rowlayout.element);
rowlayout.config.height = 50;
newContentItem.config.height = 50;
}
} else {// if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
switch (pullSide) {
- case "top": instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0); break;
- case "bottom": instance._goldenLayout.root.contentItems[0].addChild(newContentItem); break;
+ case "top": glayRoot.contentItems[0].addChild(newContentItem, 0); break;
+ case "bottom": glayRoot.contentItems[0].addChild(newContentItem); break;
case "left":
case "right":
default:
// if not going in a row layout, must add already existing content into column
- const collayout = instance._goldenLayout.root.contentItems[0];
+ const collayout = glayRoot.contentItems[0];
const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
- CollectionDockingView.Instance._goldenLayout.saveScrollTops(collayout.element);
+ instance._goldenLayout.saveScrollTops(collayout.element);
collayout.parent.replaceChild(collayout, newRow);
if (pullSide === "left") {
newRow.addChild(collayout, undefined, true);
@@ -259,7 +245,7 @@ export class CollectionDockingView extends CollectionSubView() {
newRow.addChild(newContentItem, undefined, true);
newRow.addChild(collayout, 0, true);
}
- CollectionDockingView.Instance._goldenLayout.restoreScrollTops(collayout.element);
+ instance._goldenLayout.restoreScrollTops(collayout.element);
collayout.config.width = 50;
newContentItem.config.width = 50;
@@ -281,7 +267,7 @@ export class CollectionDockingView extends CollectionSubView() {
return true;
}
- async setupGoldenLayout() {
+ setupGoldenLayout = async () => {
const config = StrCast(this.props.Document.dockingConfig);
if (config) {
const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
@@ -298,26 +284,19 @@ export class CollectionDockingView extends CollectionSubView() {
this._goldenLayout.unbind('stackCreated', this.stackCreated);
} catch (e) { }
}
+ this.tabMap.clear();
+ this._goldenLayout.destroy();
}
- this.tabMap.clear();
- this._goldenLayout?.destroy();
- runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config)));
- this._goldenLayout.on('tabCreated', this.tabCreated);
- this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
- this._goldenLayout.on('stackCreated', this.stackCreated);
- this._goldenLayout.registerComponent('DocumentFrameRenderer', TabDocView);
- this._goldenLayout.container = this._containerRef.current;
- if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
- try {
- this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
- } catch (e) {
- this._goldenLayout.config.maximisedItemId = null;
- }
- }
- this._goldenLayout.init();
- this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped);
- this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart);
- this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged);
+ const glay = this._goldenLayout = new GoldenLayout(JSON.parse(config));
+ glay.on('tabCreated', this.tabCreated);
+ glay.on('tabDestroyed', this.tabDestroyed);
+ glay.on('stackCreated', this.stackCreated);
+ glay.registerComponent('DocumentFrameRenderer', TabDocView);
+ glay.container = this._containerRef.current;
+ glay.init();
+ glay.root.layoutManager.on('itemDropped', this.tabItemDropped);
+ glay.root.layoutManager.on('dragStart', this.tabDragStart);
+ glay.root.layoutManager.on('activeContentItemChanged', this.stateChanged);
}
}
@@ -332,7 +311,7 @@ export class CollectionDockingView extends CollectionSubView() {
}
this._ignoreStateChange = "";
});
- setTimeout(() => this.setupGoldenLayout(), 0);
+ setTimeout(this.setupGoldenLayout);
//window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
}
}
@@ -378,13 +357,14 @@ export class CollectionDockingView extends CollectionSubView() {
const htmlTarget = e.target as HTMLElement;
window.addEventListener("mouseup", this.onPointerUp);
if (!htmlTarget.closest("*.lm_content") && (htmlTarget.closest("*.lm_tab") || htmlTarget.closest("*.lm_stack"))) {
- if (htmlTarget.className !== "lm_close_tab") {
+ const className = typeof htmlTarget.className === "string" ? htmlTarget.className : "";
+ if (!className.includes("lm_close") && !className.includes("lm_maximise")) {
this._flush = UndoManager.StartBatch("golden layout edit");
}
}
}
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 +384,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,40 +401,27 @@ 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);
+ UndoManager.RunInBatch(() => {
+ this.props.Document.dockingConfig = json;
+ this.props.Document.data = new List<Doc>(docs);
+ }, "state changed");
}
return changesMade;
}
tabDestroyed = (tab: any) => {
+ const dview = CollectionDockingView.Instance.props.Document;
+ const fieldKey = CollectionDockingView.Instance.props.fieldKey;
+ Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
this.tabMap.delete(tab);
tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele));
this.stateChanged();
}
tabCreated = (tab: any) => {
+ this.tabMap.add(tab);
tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
}
@@ -474,12 +444,14 @@ 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_maximise') //get the close icon
+ .click(() => setTimeout(this.stateChanged));
+ 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 +474,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/CollectionDockingViewMenu.scss b/src/client/views/collections/CollectionDockingViewMenu.scss
deleted file mode 100644
index 4157f0f7e..000000000
--- a/src/client/views/collections/CollectionDockingViewMenu.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-
-.dockingViewButtonSelector {
- div {
- overflow: visible !important;
- }
-
- display: inline-block;
- width:100%;
- height:100%;
-}
-.dockingViewButtonSelector-flyout {
- position: relative;
- z-index: 9999;
- background-color: #eeeeee;
- box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
- color: black;
-
- padding: 10px;
- border-radius: 3px;
- display: inline-block;
- height: 100%;
- width: 100%;
- border-radius: 3px;
-
- hr {
- height: 1px;
- margin: 0px;
- background-color: gray;
- border-top: 0px;
- border-bottom: 0px;
- border-right: 0px;
- border-left: 0px;
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingViewMenu.tsx b/src/client/views/collections/CollectionDockingViewMenu.tsx
deleted file mode 100644
index 1cab293a8..000000000
--- a/src/client/views/collections/CollectionDockingViewMenu.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { DocumentButtonBar } from "../DocumentButtonBar";
-import { DocumentView } from "../nodes/DocumentView";
-const higflyout = require("@hig/flyout");
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-@observer
-export class CollectionDockingViewMenu extends React.Component<{ views: () => DocumentView[], Stack: any }> {
- customStylesheet(styles: any) {
- return {
- ...styles,
- panel: {
- ...styles.panel,
- minWidth: "100px"
- },
- };
- }
- _ref = React.createRef<HTMLDivElement>();
-
- @computed get flyout() {
- return (
- <div className="dockingViewButtonSelector-flyout" title=" " ref={this._ref}>
- <DocumentButtonBar views={this.props.views} stack={this.props.Stack} />
- </div>
- );
- }
-
- @observable _tooltipOpen: boolean = false;
- render() {
- return <Tooltip open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom">
- <span className="dockingViewButtonSelector"
- onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}
- onPointerDown={action(e => {
- this.props.views()[0]?.select(false);
- this._tooltipOpen = false;
- })} >
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
- <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} />
- </Flyout>
- </span>
- </Tooltip >;
- }
-}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 364a2440e..01e77f342 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>;
@@ -459,7 +457,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
return !targetDoc ? (null) : <Tooltip key="pin" title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top">
<button className="antimodeMenu-button" style={{ backgroundColor: isPinned ? "121212" : undefined, borderLeft: "1px solid gray" }}
- onClick={e => TabDocView.PinDoc(targetDoc, { unpin: isPinned })}>
+ onClick={e => TabDocView.PinDoc(targetDoc, { /* unpin: isPinned*/ })}>
<FontAwesomeIcon className="colMenu-icon" size="lg" icon="map-pin" />
</button>
</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");
}
@@ -509,7 +507,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
activeDoc.presPinViewY = y;
activeDoc.presPinViewScale = scale;
} else if (targetDoc.type === DocumentType.VID) {
- activeDoc.presPinTimecode = targetDoc._currentTimecode;
activeDoc.presPinView = true;
} else if (targetDoc.type === DocumentType.COMPARISON) {
const width = targetDoc._clipWidth;
@@ -733,6 +730,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..ebdea9aaf 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,9 +438,13 @@ 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,
+ _isLinkButton: true,
annotationOn: rootDoc,
_timelineLabel: true,
borderRounding: anchorEndTime === undefined ? "100%" : undefined
@@ -692,7 +700,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
PanelHeight={this.timelineContentHeight}
PanelWidth={this.timelineContentWidth}
/>
- {this.renderDictation}
+ {/* {this.renderDictation} */}
<div
className="collectionStackedTimeline-current"
@@ -785,6 +793,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 +927,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 8e45ec3b3..11d5df197 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -4,14 +4,15 @@ import { Tooltip } from '@material-ui/core';
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
import { clamp } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
-import { DataSym, Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { DataSym, Doc, 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";
+import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -24,16 +25,17 @@ 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 { DashFieldView } from '../nodes/formattedText/DashFieldView';
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';
+import { listSpec } from '../../../fields/Schema';
const _global = (window /* browser */ || global /* node */) as any;
interface TabDocViewProps {
@@ -65,6 +67,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,53 +77,56 @@ 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];
const iconWrap = document.createElement("div");
const closeWrap = document.createElement("div");
-
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;
}));
- const dragBtnDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, emptyFunction);
- };
-
-
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 lm_moreInfo";
+ const dragBtnDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), returnFalse, action(e => {
+ if (this.view) {
+ SelectionManager.SelectView(this.view, false);
+ let child = this.view.ContentDiv!.children[0];
+ while (child.children.length) {
+ const next = Array.from(child.children).find(c => c.className?.toString().includes("SVGAnimatedString") || typeof (c.className) === "string");
+ if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
+ if (next?.className?.toString().includes(DashFieldView.name)) break;
+ if (next) child = next;
+ else break;
+ }
+ simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+ }
+ else { this._activated = true;
+ setTimeout(() =>this.view && SelectionManager.SelectView(this.view, false));
+ }
+ }));
};
- 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} />;
const closeIcon = <FontAwesomeIcon icon={"times"} />;
ReactDOM.render(docIcon, iconWrap);
ReactDOM.render(closeIcon, closeWrap);
+ tab.reactComponents = [iconWrap, closeWrap];
// tab.element[0].append(closeWrap);
tab.element[0].prepend(iconWrap);
tab._disposers.layerDisposer = reaction(() => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
@@ -131,7 +138,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
titleEle.style.backgroundColor = "transparent";
iconWrap.style.color = textColor;
closeWrap.style.color = textColor;
- moreInfoDrag.style.backgroundColor = textColor;
tab.element[0].style.background = !layer ? color : "dimgrey";
}, { fireImmediately: true });
}
@@ -164,19 +170,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
// toggle.style.textTransform = selected ? "uppercase" : "";
}));
- //attach the selection doc buttons menu to the drag handle
- const stack: HTMLDivElement = tab.contentItem.parent;
- const header: HTMLDivElement = tab;
- stack.onscroll = action((e: any) => {
- console.log('scrolling...');
- });
- const moreInfoDrag = document.createElement("div");
- moreInfoDrag.className = "lm_iconWrap";
- tab._disposers.buttonDisposer = reaction(() => this.view, view =>
- view && [ReactDOM.render(<span><CollectionDockingViewMenu views={() => [view]} Stack={stack} /></span>, moreInfoDrag), tab._disposers.buttonDisposer?.()],
- { fireImmediately: true });
- // tab.reactComponents = [moreInfoDrag];
- // tab.element[0].children[3].before(moreInfoDrag);
// highlight the tab when the tab document is brushed in any part of the UI
tab._disposers.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
@@ -189,7 +182,6 @@ 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");
});
@@ -200,11 +192,13 @@ export class TabDocView extends React.Component<TabDocViewProps> {
* Adds a document to the presentation view
**/
@action
- public static async PinDoc(doc: Doc, pinProps?: PinProps) {
- if (pinProps?.unpin) console.log('TODO: Remove UNPIN from this location');
+ public static PinDoc(doc: Doc, pinProps?: PinProps) {
//add this new doc to props.Document
+
+ // all docs will be added to the ActivePresentation as stored on CurrentUserUtils
const curPres = CurrentUserUtils.ActivePresentation;
if (curPres) {
+ // Edge Case 1: Cannot pin document to itself
if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; }
const batch = UndoManager.StartBatch("pinning doc");
const pinDoc = Doc.MakeAlias(doc);
@@ -226,6 +220,14 @@ export class TabDocView extends React.Component<TabDocViewProps> {
const size: number = PresBox.Instance?._selectedArray.size;
const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
+ // If pinWithView option set then update scale and x / y props of slide
+ if (pinProps?.pinWithView) {
+ const viewProps = pinProps.pinWithView;
+ pinDoc.presPinView = true;
+ pinDoc.presPinViewX = viewProps.bounds.left + viewProps.bounds.width / 2;
+ pinDoc.presPinViewY = viewProps.bounds.top + viewProps.bounds.height / 2;
+ pinDoc.presPinViewScale = viewProps.scale;
+ }
Doc.AddDocToList(curPres, "data", pinDoc, presSelected);
if (!pinProps?.audioRange && duration !== undefined) {
pinDoc.mediaStart = "manual";
@@ -244,21 +246,16 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.presMovement = PresMovement.None;
}
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);
- 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
- CollectionDockingView.AddSplit(curPres, "right");
- }
- PresBox.Instance?._selectedArray.clear();
- pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
- DocumentManager.Instance.jumpToDocument(doc, false, undefined);
- batch.end();
- });
+ if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) {
+ const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []);
+ if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1);
+ CollectionDockingView.AddSplit(curPres, "right");
+ setTimeout(() => DocumentManager.Instance.jumpToDocument(doc, false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things
+ }
+ PresBox.Instance?._selectedArray.clear();
+ pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array
+ setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
+ return pinDoc;
}
}
@@ -275,10 +272,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.tab && CollectionDockingView.Instance.tabMap.delete(this.tab);
+ this._view && DocumentManager.Instance.RemoveView(this._view);
this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
}
@@ -307,7 +307,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 +350,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 +363,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 +425,13 @@ export class TabDocView extends React.Component<TabDocViewProps> {
height: "100%", width: "100%"
}} ref={ref => {
if (this._mainCont = ref) {
+ if (this._lastTab) {
+ this._view && DocumentManager.Instance.RemoveView(this._view);
+ }
+ 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}
@@ -443,9 +457,20 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
default: return DefaultStyleProvider(doc, props, property);
case StyleProp.PointerEvents: return "none";
case StyleProp.DocContents:
- const background = doc.type === DocumentType.PDF ? "red" : doc.type === DocumentType.IMG ? "blue" : doc.type === DocumentType.RTF ? "orange" :
- doc.type === DocumentType.VID ? "purple" : doc.type === DocumentType.WEB ? "yellow" : doc.type === DocumentType.MAP ? "blue" : "gray";
- return doc.type === DocumentType.COL ?
+ const background = ((type: DocumentType) => {
+ switch (type) {
+ case DocumentType.PDF: return "pink";
+ case DocumentType.AUDIO: return "lightgreen";
+ case DocumentType.WEB: return "brown";
+ case DocumentType.IMG: return "blue";
+ case DocumentType.MAP: return "orange";
+ case DocumentType.VID: return "purple";
+ case DocumentType.RTF: return "yellow";
+ case DocumentType.COL: return undefined;
+ default: return "gray";
+ }
+ })(doc.type as DocumentType);
+ return !background ?
undefined :
<div style={{ width: doc[WidthSym](), height: doc[HeightSym](), position: "absolute", display: "block", background }} />;
}
@@ -509,7 +534,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}
@@ -522,4 +546,4 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
</div>
</div>;
}
-}
+} \ No newline at end of file
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..09f05f69a 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,16 @@ 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);
+ }
+
+ componentDidUpdate() {
+ this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
+ }
+
+ componentDidMount() {
+ this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
}
onDragUp = (e: PointerEvent) => {
@@ -236,7 +266,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 +296,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 +389,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 +446,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 +470,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 || this.props.AddToMap /* hack to identify pres box trees */ ? (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 +488,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 +502,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 +529,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 +608,10 @@ 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));
+
+ onChildClick = () => {
+ return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!);
+ }
onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick);
@@ -595,24 +642,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 +711,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 +799,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 +843,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 +867,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 +880,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 +894,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 +934,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 +979,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 19ae99743..5534ddd35 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,18 @@ 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";
+import { InkTranscription } from "../../InkTranscription";
import e = require("connect-flash");
import { RecordingApi } from "../../../util/RecordingApi";
@@ -77,6 +81,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.
};
@@ -103,6 +108,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; }
@@ -119,12 +127,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 },
@@ -157,6 +164,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;
@@ -223,7 +231,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);
@@ -252,7 +260,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);
@@ -419,8 +427,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;
@@ -437,6 +444,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;
@@ -447,7 +467,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);
@@ -493,6 +518,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) {
@@ -501,6 +528,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;
@@ -625,7 +656,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);
@@ -680,6 +718,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)) {
@@ -810,6 +849,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]);
}
}
@@ -942,12 +982,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
deltaScale = NumCast(this.rootDoc._viewScaleMin, 1) / invTransform.Scale;
}
- // console.log('zoom scaling', this.zoomScaling())
- console.log('delta scaling', deltaScale)
-
- // console.log('transform abt zoomScaling', this.getLocalTransform().inverse().scaleAbout(this.zoomScaling(), x, y));
-
-
const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
@@ -958,7 +992,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();
}
@@ -1034,7 +1068,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;
@@ -1086,21 +1120,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();
@@ -1137,23 +1171,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 {
@@ -1170,16 +1205,42 @@ 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}
renderDepth={this.props.renderDepth + 1}
replica={entry.replica}
- dataTransition={entry.transition}
suppressSetHeight={this.layoutEngine ? true : false}
renderCutoffProvider={this.renderCutoffProvider}
ContainingCollectionView={this.props.CollectionView}
@@ -1189,15 +1250,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}
@@ -1207,16 +1270,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
/>;
@@ -1440,31 +1502,58 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}));
}
- updateIcon = () => {
- this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString();
- this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString();
- const htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!);
- this.props.docViewPath().lastElement().ContentDiv!.style.width = "";
- this.props.docViewPath().lastElement().ContentDiv!.style.height = "";
- const nativeWidth = this.layoutDoc[WidthSym]();
- const nativeHeight = this.layoutDoc[HeightSym]();
+ static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) {
+ 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);
+ }
+ }
+ if (oldDiv instanceof HTMLCanvasElement) {
+ const canvas = oldDiv;
+ const img = document.createElement('img'); // create a Image Element
+ img.src = canvas.toDataURL(); //image source
+ img.style.width = canvas.style.width;
+ img.style.height = canvas.style.height;
+ const newCan = newDiv as HTMLCanvasElement;
+ const parEle = newCan.parentElement as HTMLElement;
+ parEle.removeChild(newCan);
+ parEle.appendChild(img);
+ }
+ }
+
+ updateIcon = () => CollectionFreeFormView.UpdateIcon(
+ this.layoutDoc[Id] + "-icon" + (new Date()).getTime(),
+ this.props.docViewPath().lastElement().ContentDiv!,
+ this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](),
+ this.props.PanelWidth(), this.props.PanelHeight(), 0,
+ (iconFile, nativeWidth, nativeHeight) => {
+ this.dataDoc.icon = new ImageField(iconFile);
+ this.dataDoc["icon-nativeWidth"] = nativeWidth;
+ this.dataDoc["icon-nativeHeight"] = nativeHeight;
+ });
+ public static UpdateIcon(filename:string, docViewContent:HTMLElement, width: number, height: number,
+ panelWidth:number, panelHeight: number, scrollTop:number, cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) {
+ const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
+ newDiv.style.width = width.toString();
+ newDiv.style.height = height.toString();
+ this.replaceCanvases(docViewContent, newDiv);
+ const htmlString = new XMLSerializer().serializeToString(newDiv);
+ const nativeWidth = width;
+ const nativeHeight = height;
CreateImage(
- "",
+ Utils.prepend(""),
document.styleSheets,
- htmlString?.replace(/docView-hack/g, 'documentView-hack'),
+ htmlString,
nativeWidth,
- nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
- NumCast(this.layoutDoc._scrollTop)
+ nativeWidth * panelHeight / panelWidth,
+ 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.dataDoc.icon = new ImageField(returnedfilename);
- this.dataDoc["icon-nativeWidth"] = nativeWidth;
- this.dataDoc["icon-nativeHeight"] = nativeHeight;
- }), 500));
+ VideoBox.convertDataUri(data_url, filename).then(
+ returnedfilename => setTimeout(() => {
+ cb(returnedfilename as string, nativeWidth, nativeHeight);
+ }, 500));
})
.catch(function (error: any) {
console.error('oops, something went wrong!', error);
@@ -1539,12 +1628,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" });
@@ -1556,7 +1649,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...");
@@ -1598,6 +1690,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();
@@ -1607,7 +1714,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
@@ -1660,6 +1767,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}
@@ -1732,12 +1840,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} />}
@@ -1760,7 +1868,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%",
@@ -1969,4 +2077,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..77ac855e6 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,38 +1,36 @@
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";
import { ContextMenu } from "../../ContextMenu";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
-import { PresBox } from "../../nodes/trails/PresBox";
+import { PinViewProps, 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";
+import { TabDocView } from "../TabDocView";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -46,6 +44,14 @@ interface MarqueeViewProps {
ungroup?: () => void;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void;
}
+
+export interface MarqueeViewBounds {
+ left: number;
+ top: number;
+ width: number;
+ height: number;
+}
+
@observer
export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps>
{
@@ -54,15 +60,18 @@ 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]) };
+ const bounds:MarqueeViewBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }
+ return bounds;
}
get inkDoc() { return this.props.Document; }
get ink() { return Cast(this.props.Document.ink, InkField); }
@@ -209,8 +218,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 +250,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 +273,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.pinWithView();
+ 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 +363,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 +372,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;
@@ -381,40 +395,23 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
+ /**
+ * This triggers the TabDocView.PinDoc method which is the universal method
+ * used to pin documents to the currently active presentation trail.
+ *
+ * This one is unique in that it includes the bounds associated with marquee view.
+ */
@undoBatch
@action
- pinWithView = (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";
- 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)) {
- 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);
- if (e instanceof KeyboardEvent ? e.key === "c" : true) {
- 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;
- }
- }
- MarqueeOptionsMenu.Instance.fadeOut(true);
+ pinWithView = async () => {
+ const scale = Math.min(this.props.PanelWidth() / this.Bounds.width, this.props.PanelHeight() / this.Bounds.height);
+ const doc = this.props.Document;
+ const viewOptions:PinViewProps = {
+ bounds: this.Bounds,
+ scale: scale
+ };
+ TabDocView.PinDoc(doc, {pinWithView: viewOptions});
+ MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
}
@@ -437,7 +434,8 @@ 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);
+ // TODO: nda - this is the code to actually get a new grouped collection
+ 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 +529,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 +615,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 +643,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/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 3ddcf803d..a75e7a0c4 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -102,6 +102,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
this.props.itemHandler?.(this.props.linkDoc);
} else {
+ const focusDoc = Cast(this.props.linkDoc.anchor1, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor1, Doc, null) :
+ Cast(this.props.linkDoc.anchor2, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor12, Doc, null) : undefined;
+
+ if (focusDoc) this.props.docView.ComponentView?.scrollFocus?.(focusDoc, true);
LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
}
});
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..669622455 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -4,8 +4,6 @@ import { action, computed, IReactionDisposer, observable, runInAction } from "mo
import { observer } from "mobx-react";
import { DateField } from "../../../fields/DateField";
import { Doc, DocListCast } from "../../../fields/Doc";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
import { ComputedField } from "../../../fields/ScriptField";
import { Cast, DateCast, NumCast } from "../../../fields/Types";
import { AudioField, nullAudio } from "../../../fields/URLField";
@@ -84,12 +82,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 +235,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);
}
@@ -298,11 +294,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// button for starting and stopping the recording
- Record = (e: React.MouseEvent) => {
- if (e.button === 0 && !e.ctrlKey) {
+ Record = (e: React.PointerEvent) => {
+ e.button === 0 && !e.ctrlKey && setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
this._recorder ? this.stopRecording() : this.recordAudioAnnotation();
- e.stopPropagation();
- }
+ }), false);
}
// for play button
@@ -340,24 +335,28 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// for dictation button, creates a text document for dictation
onFile = (e: any) => {
- const newDoc = CurrentUserUtils.GetNewTextDoc(
- "",
- NumCast(this.rootDoc.x),
- NumCast(this.rootDoc.y) +
- NumCast(this.layoutDoc._height) +
- 10,
- NumCast(this.layoutDoc._width),
- 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);
- e.stopPropagation();
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+ const newDoc = CurrentUserUtils.GetNewTextDoc(
+ "",
+ NumCast(this.rootDoc.x),
+ NumCast(this.rootDoc.y) +
+ NumCast(this.layoutDoc._height) +
+ 10,
+ NumCast(this.layoutDoc._width),
+ 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");
+ 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);
+ }
+ }), false);
}
@@ -370,21 +369,21 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// pause the time during recording phase
- @action
- recordPause = (e: React.MouseEvent) => {
- this._pauseStart = new Date().getTime();
- this._paused = true;
- this._recorder.pause();
- e.stopPropagation();
+ recordPause = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+ this._pauseStart = new Date().getTime();
+ this._paused = true;
+ this._recorder.pause();
+ }), false);
}
// continue the recording
- @action
- recordPlay = (e: React.MouseEvent) => {
- this._pauseEnd = new Date().getTime();
- this._paused = false;
- this._recorder.resume();
- e.stopPropagation();
+ recordPlay = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+ this._pauseEnd = new Date().getTime();
+ this._paused = false;
+ this._recorder.resume();
+ }), false);
}
@@ -503,19 +502,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// UI for recording, initially displayed when new audio created in Dash
@computed get recordingControls() {
return <div className="audiobox-recorder">
- <div className="audiobox-dictation" onClick={this.onFile}>
+ <div className="audiobox-dictation" onPointerDown={this.onFile}>
<FontAwesomeIcon
size="2x"
icon="file-alt" />
</div>
{[media_state.Recording, media_state.Playing].includes(this.mediaState) ?
<div className="recording-controls" onClick={e => e.stopPropagation()}>
- <div className="record-button" onClick={this.Record}>
+ <div className="record-button" onPointerDown={this.Record}>
<FontAwesomeIcon
size="2x"
icon="stop" />
</div>
- <div className="record-button" onClick={this._paused ? this.recordPlay : this.recordPause}>
+ <div className="record-button" onPointerDown={this._paused ? this.recordPlay : this.recordPause}>
<FontAwesomeIcon
size="2x"
icon={this._paused ? "play" : "pause"} />
@@ -525,7 +524,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
</div>
</div>
:
- <div className="audiobox-start-record">
+ <div className="audiobox-start-record" onPointerDown={this.Record}>
<FontAwesomeIcon icon="microphone" />
RECORD
</div>}
@@ -611,7 +610,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}
@@ -653,8 +651,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ref={this.setupTimelineDrop}
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..bedc97575 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -6,7 +6,7 @@ import { listSpec } from "../../../fields/Schema";
import { ComputedField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { DashColor, numberRange } from "../../../Utils";
+import { DashColor, numberRange, OmitKeys } from "../../../Utils";
import { DocumentManager } from "../../util/DocumentManager";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
@@ -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,
@@ -172,7 +170,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
width: this.panelWidth(),
height: this.panelHeight(),
transform: this.transform,
- transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
+ transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
zIndex: this.ZInd,
mixBlendMode: mixBlendMode,
display: this.ZInd === -99 ? "none" : undefined
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.scss b/src/client/views/nodes/DocumentView.scss
index 5c5d2df10..6a1bfa406 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -3,12 +3,14 @@
.documentView-effectsWrapper {
border-radius: inherit;
}
+
+// documentViews have a docView-hack tag which is replaced by this tag when capturing bitmaps (when the dom is converted to an html string)
.documentView-hack {
- display: inline;
+ display: inline; // this swap is needed because for some reason when capturing bitmaps, things don't appear unless dispay:inline is explicitly set.
}
.documentView-customBorder {
- width:100%;
+ width: 100%;
height: 100%;
position: absolute;
top: 0;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f48173975..41a64d6a9 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,1333 +50,1355 @@ 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 {
- MediaRecorder: MediaRecorder;
+ MediaRecorder: MediaRecorder;
}
declare class MediaRecorder {
- // whatever MediaRecorder has
- constructor(e: any);
+ // whatever MediaRecorder has
+ constructor(e: any);
}
export enum ViewAdjustment {
- resetView = 1,
- doNothing = 0
+ resetView = 1,
+ doNothing = 0
}
export const ViewSpecPrefix = "viewSpec"; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
export interface DocFocusOptions {
- originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
- willZoom?: boolean; // determines whether to zoom in on target document
- scale?: number; // percent of containing frame to zoom into document
- afterFocus?: DocAfterFocusFunc; // function to call after focusing on a document
- docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
- instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
+ originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
+ willZoom?: boolean; // determines whether to zoom in on target document
+ scale?: number; // percent of containing frame to zoom into document
+ afterFocus?: DocAfterFocusFunc; // function to call after focusing on a document
+ docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
+ instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
}
export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment>;
export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void;
export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
export interface DocComponentView {
- updateIcon?: () => void; // updates the icon representation of the document
- getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
- scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
- setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
- reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
- shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views
- menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
- isAnyChildContentActive?: () => boolean; // is any child content of the document active
- getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
- setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
- playFrom?: (time: number, endTime?: number) => void;
- Pause?: () => void;
- setFocus?: () => void;
- componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
- fieldKey?: string;
- annotationKey?: string;
- getTitle?: () => string;
- getScrollHeight?: () => number;
- getCenter?: (xf: Transform) => { X: number, Y: number };
- ptToScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
- ptFromScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
- snapPt?: (pt: { X: number, Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number, Y: number }, distance: number };
- search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
+ updateIcon?: () => void; // updates the icon representation of the document
+ getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
+ scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
+ setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
+ reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
+ shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views
+ menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
+ isAnyChildContentActive?: () => boolean; // is any child content of the document active
+ getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
+ setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
+ playFrom?: (time: number, endTime?: number) => void;
+ Pause?: () => void;
+ setFocus?: () => void;
+ componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
+ fieldKey?: string;
+ annotationKey?: string;
+ getTitle?: () => string;
+ getScrollHeight?: () => number;
+ getCenter?: (xf: Transform) => { X: number, Y: number };
+ ptToScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
+ ptFromScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number };
+ snapPt?: (pt: { X: number, Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number, Y: number }, distance: number };
+ search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
}
// These props are passed to both FieldViews and DocumentViews
export interface DocumentViewSharedProps {
- fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps. In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews
- DocumentView?: () => DocumentView;
- renderDepth: number;
- Document: Doc;
- DataDoc?: Doc;
- fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
- ContainingCollectionView: Opt<CollectionView>;
- ContainingCollectionDoc: Opt<Doc>;
- suppressSetHeight?: boolean;
- thumbShown?: () => boolean;
- isHovering?: () => boolean;
- setContentView?: (view: DocComponentView) => any;
- CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
- PanelWidth: () => number;
- PanelHeight: () => number;
- docViewPath: () => DocumentView[];
- 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;
- docFilters: () => string[];
- docRangeFilters: () => string[];
- searchFilterDocs: () => Doc[];
- showTitle?: () => string;
- whenChildContentsActiveChanged: (isActive: boolean) => void;
- rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
- addDocTab: (doc: Doc, where: string) => boolean;
- 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)
- addDocument?: (doc: Doc | Doc[]) => boolean;
- removeDocument?: (doc: Doc | Doc[]) => boolean;
- moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
- pinToPres: (document: Doc) => void;
- ScreenToLocalTransform: () => Transform;
- bringToFront: (doc: Doc, sendToBack?: boolean) => void;
- dropAction?: dropActionType;
- dontRegisterView?: boolean;
- hideLinkButton?: boolean;
- hideCaptions?: boolean;
- ignoreAutoHeight?: boolean;
- forceAutoHeight?: boolean;
- disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
- pointerEvents?: 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;
+ fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps. In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews
+ DocumentView?: () => DocumentView;
+ renderDepth: number;
+ Document: Doc;
+ DataDoc?: Doc;
+ fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
+ ContainingCollectionView: Opt<CollectionView>;
+ ContainingCollectionDoc: Opt<Doc>;
+ suppressSetHeight?: boolean;
+ thumbShown?: () => boolean;
+ isHovering?: () => boolean;
+ setContentView?: (view: DocComponentView) => any;
+ CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
+ 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
+ styleProvider: Opt<StyleProviderFunc>;
+ focus: DocFocusFunc;
+ fitWidth?: (doc: Doc) => boolean;
+ docFilters: () => string[];
+ docRangeFilters: () => string[];
+ searchFilterDocs: () => Doc[];
+ showTitle?: () => string;
+ whenChildContentsActiveChanged: (isActive: boolean) => void;
+ rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
+ addDocTab: (doc: Doc, where: string) => boolean;
+ 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)
+ addDocument?: (doc: Doc | Doc[]) => boolean;
+ removeDocument?: (doc: Doc | Doc[]) => boolean;
+ moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+ pinToPres: (document: Doc) => void;
+ ScreenToLocalTransform: () => Transform;
+ bringToFront: (doc: Doc, sendToBack?: boolean) => void;
+ dropAction?: dropActionType;
+ dontRegisterView?: boolean;
+ hideLinkButton?: boolean;
+ hideCaptions?: boolean;
+ ignoreAutoHeight?: boolean;
+ forceAutoHeight?: boolean;
+ disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
+ 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;
}
// these props are specific to DocuentViews
export interface DocumentViewProps extends DocumentViewSharedProps {
- // properties specific to DocumentViews but not to FieldView
- freezeDimensions?: boolean;
- hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
- hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
- hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
- treeViewDoc?: Doc;
- isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
- isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
- contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
- radialMenu?: String[];
- LayoutTemplateString?: string;
- dontCenter?: "x" | "y" | "xy";
- ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
- NativeWidth?: () => number;
- NativeHeight?: () => number;
- LayoutTemplate?: () => Opt<Doc>;
- contextMenuItems?: () => { script: ScriptField, filter?: ScriptField, label: string, icon: string }[];
- onClick?: () => ScriptField;
- onDoubleClick?: () => ScriptField;
- onPointerDown?: () => ScriptField;
- onPointerUp?: () => ScriptField;
+ // properties specific to DocumentViews but not to FieldView
+ freezeDimensions?: boolean;
+ hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
+ hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideDocumentButtonBar?: boolean;
+ hideOpenButton?: boolean;
+ hideDeleteButton?: boolean;
+ treeViewDoc?: Doc;
+ isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
+ isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
+ contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
+ radialMenu?: String[];
+ LayoutTemplateString?: string;
+ 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;
+ LayoutTemplate?: () => Opt<Doc>;
+ contextMenuItems?: () => { script: ScriptField, filter?: ScriptField, label: string, icon: string }[];
+ onClick?: () => ScriptField;
+ onDoubleClick?: () => ScriptField;
+ onPointerDown?: () => ScriptField;
+ onPointerUp?: () => ScriptField;
+ onBrowseClick?: () => (ScriptField | undefined);
+ onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined);
}
// these props are only available in DocumentViewIntenral
export interface DocumentViewInternalProps extends DocumentViewProps {
- NativeWidth: () => number;
- NativeHeight: () => number;
- isSelected: (outsideReaction?: boolean) => boolean;
- select: (ctrlPressed: boolean) => void;
- DocumentView: () => DocumentView;
- viewPath: () => DocumentView[];
+ NativeWidth: () => number;
+ NativeHeight: () => number;
+ isSelected: (outsideReaction?: boolean) => boolean;
+ select: (ctrlPressed: boolean) => void;
+ DocumentView: () => DocumentView;
+ viewPath: () => DocumentView[];
}
@observer
export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
- public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
- _animateScaleTime = 300; // milliseconds;
- @observable _animateScalingTo = 0;
- @observable _mediaState = 0;
- @observable _pendingDoubleClick = false;
- private _disposers: { [name: string]: IReactionDisposer } = {};
- private _downX: number = 0;
- private _downY: number = 0;
- private _firstX: number = -1;
- private _firstY: number = -1;
- private _lastTap: number = 0;
- private _doubleTap = false;
- private _mainCont = React.createRef<HTMLDivElement>();
- private _titleRef = React.createRef<EditableView>();
- private _timeout: NodeJS.Timeout | undefined;
- private _dropDisposer?: DragManager.DragDropDisposer;
- private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
- protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
-
- private get topMost() { return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; }
- public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
- public get ContentDiv() { return this._mainCont.current; }
- public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); }
- @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
- @computed get ContentScale() { return this.props.ContentScaling?.() || 1; }
- @computed get thumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); }
- @computed get hidden() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden); }
- @computed get opacity() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity); }
- @computed get boxShadow() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow); }
- @computed get borderRounding() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); }
- @computed get hideLinkButton() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton + (this.props.isSelected() ? ":selected" : "")); }
- @computed get widgetDecorations() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ":selected" : "")); }
- @computed get backgroundColor() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor); }
- @computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); }
- @computed get headerMargin() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
- @computed get titleHeight() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0; }
- @computed get pointerEvents() { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ":selected" : "")); }
- @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 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(); }
- setupHandlers() {
- this.cleanupHandlers(false);
- if (this._mainCont.current) {
- this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
- this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
- this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
- }
- }
- cleanupHandlers(unbrush: boolean) {
- this._dropDisposer?.();
- this._multiTouchDisposer?.();
- this._holdDisposer?.();
- unbrush && Doc.UnBrushDoc(this.props.Document);
- Object.values(this._disposers).forEach(disposer => disposer?.());
- }
-
- handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
- this.removeMoveListeners();
- this.removeEndListeners();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- if (RadialMenu.Instance._display === false) {
- this.addHoldMoveListeners();
- this.addHoldEndListeners();
- this.onRadialMenu(e, me);
- const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
- this._firstX = pt.pageX;
- this._firstY = pt.pageY;
- }
- }
-
- handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+ public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
+ _animateScaleTime = 300; // milliseconds;
+ @observable _animateScalingTo = 0;
+ @observable _mediaState = 0;
+ @observable _pendingDoubleClick = false;
+ private _disposers: { [name: string]: IReactionDisposer } = {};
+ private _downX: number = 0;
+ private _downY: number = 0;
+ private _firstX: number = -1;
+ private _firstY: number = -1;
+ private _lastTap: number = 0;
+ private _doubleTap = false;
+ private _mainCont = React.createRef<HTMLDivElement>();
+ private _titleRef = React.createRef<EditableView>();
+ private _timeout: NodeJS.Timeout | undefined;
+ private _dropDisposer?: DragManager.DragDropDisposer;
+ private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
+
+ private get topMost() { return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; }
+ public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
+ public get ContentDiv() { return this._mainCont.current; }
+ public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); }
+ @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
+ @computed get ContentScale() { return this.props.ContentScaling?.() || 1; }
+ @computed get thumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); }
+ @computed get hidden() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden); }
+ @computed get opacity() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity); }
+ @computed get boxShadow() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow); }
+ @computed get borderRounding() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); }
+ @computed get hideLinkButton() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton + (this.props.isSelected() ? ":selected" : "")); }
+ @computed get widgetDecorations() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ":selected" : "")); }
+ @computed get backgroundColor() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor); }
+ @computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); }
+ @computed get headerMargin() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
+ @computed get titleHeight() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0; }
+ @computed get pointerEvents() { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ":selected" : "")); }
+ @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?.() ?? (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(); }
+ setupHandlers() {
+ this.cleanupHandlers(false);
+ if (this._mainCont.current) {
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
+ this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
+ this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
+ }
+ }
+ @action
+ cleanupHandlers(unbrush: boolean) {
+ this._dropDisposer?.();
+ this._multiTouchDisposer?.();
+ this._holdDisposer?.();
+ unbrush && Doc.UnBrushDoc(this.props.Document);
+ Object.values(this._disposers).forEach(disposer => disposer?.());
+ }
+
+ handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
+ this.removeMoveListeners();
+ this.removeEndListeners();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (RadialMenu.Instance._display === false) {
+ this.addHoldMoveListeners();
+ this.addHoldEndListeners();
+ this.onRadialMenu(e, me);
const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
-
- if (this._firstX === -1 || this._firstY === -1) {
- return;
- }
- if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
- this.handle1PointerHoldEnd(e, me);
- }
- }
-
- handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- this.removeHoldMoveListeners();
- this.removeHoldEndListeners();
- RadialMenu.Instance.closeMenu();
- this._firstX = -1;
- this._firstY = -1;
- SelectionManager.DeselectAll();
- me.touchEvent.stopPropagation();
- me.touchEvent.preventDefault();
+ this._firstX = pt.pageX;
+ this._firstY = pt.pageY;
+ }
+ }
+
+ handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+ const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
+
+ if (this._firstX === -1 || this._firstY === -1) {
+ return;
+ }
+ if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
+ this.handle1PointerHoldEnd(e, me);
+ }
+ }
+
+ handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+ this.removeHoldMoveListeners();
+ this.removeHoldEndListeners();
+ RadialMenu.Instance.closeMenu();
+ this._firstX = -1;
+ this._firstY = -1;
+ SelectionManager.DeselectAll();
+ me.touchEvent.stopPropagation();
+ me.touchEvent.preventDefault();
+ e.stopPropagation();
+ if (RadialMenu.Instance.used) {
+ this.onContextMenu(undefined, me.touches[0].pageX, me.touches[0].pageY);
+ }
+ }
+
+ handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
+ if (!e.nativeEvent.cancelBubble && !this.props.isSelected()) {
e.stopPropagation();
- if (RadialMenu.Instance.used) {
- this.onContextMenu(undefined, me.touches[0].pageX, me.touches[0].pageY);
- }
- }
+ e.preventDefault();
- handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (!e.nativeEvent.cancelBubble && !this.props.isSelected()) {
- e.stopPropagation();
- e.preventDefault();
-
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ }
+ }
+
+ handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
+ SelectionManager.DeselectAll();
+ if (this.Document.onPointerDown) return;
+ const touch = me.touchEvent.changedTouches.item(0);
+ if (touch) {
+ this._downX = touch.clientX;
+ this._downY = touch.clientY;
+ if (!e.nativeEvent.cancelBubble) {
+ if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation();
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ e.stopPropagation();
}
- }
+ }
+ }
- handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- SelectionManager.DeselectAll();
- if (this.Document.onPointerDown) return;
+ handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
+ if (e.cancelBubble && this.props.isDocumentActive?.()) {
+ this.removeMoveListeners();
+ }
+ else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
const touch = me.touchEvent.changedTouches.item(0);
- if (touch) {
- this._downX = touch.clientX;
- this._downY = touch.clientY;
- if (!e.nativeEvent.cancelBubble) {
- if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation();
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- e.stopPropagation();
- }
+ if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
+ if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) {
+ this.cleanUpInteractions();
+ this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
+ }
}
- }
-
- handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- if (e.cancelBubble && this.props.isDocumentActive?.()) {
- this.removeMoveListeners();
- }
- else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
- const touch = me.touchEvent.changedTouches.item(0);
- if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
- if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) {
- this.cleanUpInteractions();
- this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
- }
- }
- e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
- e.preventDefault();
- }
- }
-
- @action
- handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt1 = myTouches[0];
- const pt2 = myTouches[1];
- const oldPoint1 = this.prevPoints.get(pt1.identifier);
- const oldPoint2 = this.prevPoints.get(pt2.identifier);
- const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
- if (pinching !== 0 && oldPoint1 && oldPoint2) {
- const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
- const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
- const dX = -1 * Math.sign(dW);
- const dY = -1 * Math.sign(dH);
-
- if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
- const doc = Document(this.props.Document);
- const layoutDoc = Document(Doc.Layout(this.props.Document));
- let nwidth = Doc.NativeWidth(layoutDoc);
- let nheight = Doc.NativeHeight(layoutDoc);
- const width = (layoutDoc._width || 0);
- const height = (layoutDoc._height || (nheight / nwidth * width));
- const scale = this.props.ScreenToLocalTransform().Scale * this.ContentScale;
- const actualdW = Math.max(width + (dW * scale), 20);
- const actualdH = Math.max(height + (dH * scale), 20);
- doc.x = (doc.x || 0) + dX * (actualdW - width);
- doc.y = (doc.y || 0) + dY * (actualdH - height);
- const fixedAspect = e.ctrlKey || (nwidth && nheight);
- if (fixedAspect && (!nwidth || !nheight)) {
- Doc.SetNativeWidth(layoutDoc, nwidth = layoutDoc._width || 0);
- Doc.SetNativeHeight(layoutDoc, nheight = layoutDoc._height || 0);
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
+ }
+ }
+
+ @action
+ handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
+ const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
+ const pt1 = myTouches[0];
+ const pt2 = myTouches[1];
+ const oldPoint1 = this.prevPoints.get(pt1.identifier);
+ const oldPoint2 = this.prevPoints.get(pt2.identifier);
+ const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
+ if (pinching !== 0 && oldPoint1 && oldPoint2) {
+ const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
+ const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
+ const dX = -1 * Math.sign(dW);
+ const dY = -1 * Math.sign(dH);
+
+ if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
+ const doc = Document(this.props.Document);
+ const layoutDoc = Document(Doc.Layout(this.props.Document));
+ let nwidth = Doc.NativeWidth(layoutDoc);
+ let nheight = Doc.NativeHeight(layoutDoc);
+ const width = (layoutDoc._width || 0);
+ const height = (layoutDoc._height || (nheight / nwidth * width));
+ const scale = this.props.ScreenToLocalTransform().Scale * this.ContentScale;
+ const actualdW = Math.max(width + (dW * scale), 20);
+ const actualdH = Math.max(height + (dH * scale), 20);
+ doc.x = (doc.x || 0) + dX * (actualdW - width);
+ doc.y = (doc.y || 0) + dY * (actualdH - height);
+ const fixedAspect = e.ctrlKey || (nwidth && nheight);
+ if (fixedAspect && (!nwidth || !nheight)) {
+ Doc.SetNativeWidth(layoutDoc, nwidth = layoutDoc._width || 0);
+ Doc.SetNativeHeight(layoutDoc, nheight = layoutDoc._height || 0);
+ }
+ if (nwidth > 0 && nheight > 0) {
+ if (Math.abs(dW) > Math.abs(dH)) {
+ if (!fixedAspect) {
+ Doc.SetNativeWidth(layoutDoc, actualdW / (layoutDoc._width || 1) * Doc.NativeWidth(layoutDoc));
}
- if (nwidth > 0 && nheight > 0) {
- if (Math.abs(dW) > Math.abs(dH)) {
- if (!fixedAspect) {
- Doc.SetNativeWidth(layoutDoc, actualdW / (layoutDoc._width || 1) * Doc.NativeWidth(layoutDoc));
- }
- layoutDoc._width = actualdW;
- if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
- else layoutDoc._height = actualdH;
- }
- else {
- if (!fixedAspect) {
- Doc.SetNativeHeight(layoutDoc, actualdH / (layoutDoc._height || 1) * Doc.NativeHeight(doc));
- }
- layoutDoc._height = actualdH;
- if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
- else layoutDoc._width = actualdW;
- }
- } else {
- dW && (layoutDoc._width = actualdW);
- dH && (layoutDoc._height = actualdH);
- dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false);
+ layoutDoc._width = actualdW;
+ if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
+ else layoutDoc._height = actualdH;
+ }
+ else {
+ if (!fixedAspect) {
+ Doc.SetNativeHeight(layoutDoc, actualdH / (layoutDoc._height || 1) * Doc.NativeHeight(doc));
}
- }
- e.stopPropagation();
- e.preventDefault();
+ layoutDoc._height = actualdH;
+ if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
+ else layoutDoc._width = actualdW;
+ }
+ } else {
+ dW && (layoutDoc._width = actualdW);
+ dH && (layoutDoc._height = actualdH);
+ dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false);
+ }
}
- }
-
- @action
- onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
- const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
- RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
-
- // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 });
- const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
- (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
- // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 });
- RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
- RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
-
- SelectionManager.DeselectAll();
- }
-
- startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
- if (this._mainCont.current) {
- 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.dropAction = dropAction;
- dragData.treeViewDoc = this.props.treeViewDoc;
- dragData.removeDocument = this.props.removeDocument;
- dragData.moveDocument = this.props.moveDocument;
- const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
- DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart) },
- () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed.
- ffview?.setupDragLines(false);
- }
- }
-
- onKeyDown = (e: React.KeyboardEvent) => {
- if (e.altKey && !e.nativeEvent.cancelBubble) {
- e.stopPropagation();
- e.preventDefault();
- if (e.key === "†" || e.key === "t") {
- if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = "title";
- if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0);
- else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
- this._titleRef.current?.setIsFocused(false);
- this._componentView?.setFocus?.();
- }
- }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ @action
+ onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
+ const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
+ RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
+
+ // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 });
+ const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
+ (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
+ // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 });
+ RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
+ RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
+
+ SelectionManager.DeselectAll();
+ }
+
+ startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
+ if (this._mainCont.current) {
+ 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;
+ dragData.moveDocument = this.props.moveDocument;
+ const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
+ DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart) },
+ () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed.
+ ffview?.setupDragLines(false);
+ }
+ }
+
+ onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.altKey && !e.nativeEvent.cancelBubble) {
+ e.stopPropagation();
+ e.preventDefault();
+ if (e.key === "†" || e.key === "t") {
+ if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = "title";
+ if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0);
+ else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
+ this._titleRef.current?.setIsFocused(false);
+ this._componentView?.setFocus?.();
+ }
}
- }
-
- focus = (anchor: Doc, options?: DocFocusOptions) => {
- LightboxView.SetCookie(StrCast(anchor["cookies-set"]));
- // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
- Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
- this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
- });
- // after a timeout, the right _componentView should have been created, so call it to update its view spec values
- setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
- const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
- const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
- this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
- ...options, afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0))
- });
-
- }
- onClick = action((e: React.MouseEvent | React.PointerEvent) => {
- if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
- (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);
- 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);
- this._pendingDoubleClick = false;
- this._timeout = undefined;
- }
- if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
- const { clientX, clientY, shiftKey } = e;
- const func = () => this.onDoubleClickHandler.script.run({
- this: this.layoutDoc,
- self: this.rootDoc,
- scriptContext: this.props.scriptContext,
- thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView(),
- clientX, clientY, shiftKey
- }, console.log);
- 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?.())
- , "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
- const { clientX, clientY, shiftKey } = e;
- const func = () => this.onClickHandler.script.run({
- this: this.layoutDoc,
- self: this.rootDoc,
- _readOnly_: false,
- scriptContext: this.props.scriptContext,
- thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView(),
- clientX, clientY, shiftKey
- }, console.log).result?.select === true ? this.props.select(false) : "";
- const clickFunc = () => this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, "on click");
- if (this.onDoubleClickHandler) {
- 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) {
- 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
- stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
- } else {
- runInAction(() => this._pendingDoubleClick = true);
- this._timeout = setTimeout(action(() => { this._pendingDoubleClick = false; this._timeout = undefined; }), 350);
- this.props.select(e.ctrlKey || e.shiftKey);
- }
- preventDefault = false;
- }
- stopPropagate && e.stopPropagation();
- preventDefault && e.preventDefault();
+ }
+ }
+
+ focus = (anchor: Doc, options?: DocFocusOptions) => {
+ LightboxView.SetCookie(StrCast(anchor["cookies-set"]));
+ // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
+ Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
+ this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
+ });
+ // after a timeout, the right _componentView should have been created, so call it to update its view spec values
+ setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
+ const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+ const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
+ this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
+ ...options, afterFocus: (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0))
+ });
+
+ }
+ onClick = action((e: React.MouseEvent | React.PointerEvent) => {
+ if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
+ (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+ let stopPropagate = true;
+ let preventDefault = true;
+ 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);
+ this._pendingDoubleClick = false;
+ this._timeout = undefined;
+ }
+ if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+ const { clientX, clientY, shiftKey } = e;
+ const func = () => this.onDoubleClickHandler.script.run({
+ this: this.layoutDoc,
+ self: this.rootDoc,
+ scriptContext: this.props.scriptContext,
+ thisContainer: this.props.ContainingCollectionDoc,
+ documentView: this.props.DocumentView(),
+ clientX, clientY, shiftKey
+ }, console.log);
+ 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?.(), this.props.addDocTab)
+ , "double tap");
+ SelectionManager.DeselectAll();
+ Doc.UnBrushDoc(this.props.Document);
+ }
+ } 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,
+ self: this.rootDoc,
+ _readOnly_: false,
+ scriptContext: this.props.scriptContext,
+ thisContainer: this.props.ContainingCollectionDoc,
+ documentView: this.props.DocumentView(),
+ clientX, clientY, shiftKey
+ }, console.log).result?.select === true ? this.props.select(false) : "";
+ const clickFunc = () => this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, "on click");
+ if (this.onDoubleClickHandler) {
+ runInAction(() => this._pendingDoubleClick = true);
+ this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 350);
+ } else clickFunc();
+ } 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
+ stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
+ } else {
+ runInAction(() => this._pendingDoubleClick = true);
+ this._timeout = setTimeout(action(() => { this._pendingDoubleClick = false; this._timeout = undefined; }), 350);
+ this.props.select(e.ctrlKey || e.shiftKey);
+ }
+ preventDefault = false;
}
- });
-
- onPointerDown = (e: React.PointerEvent): void => {
- // 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.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
- // TODO: check here for panning/inking
- }
- return;
+ stopPropagate && e.stopPropagation();
+ preventDefault && e.preventDefault();
+ }
+ });
+
+ 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, 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
+ // TODO: check here for panning/inking
}
- this._downX = e.clientX;
- this._downY = e.clientY;
- if ((!e.nativeEvent.cancelBubble || this.onClickHandler || this.layoutDoc.onDragStart) &&
- // 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.Document.ignoreClick &&
- !e.ctrlKey &&
- (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
- !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
- e.stopPropagation();
- // don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though
- //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault();
- }
- if (this.props.isDocumentActive?.()) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- }
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointerup", this.onPointerUp);
+ return;
+ }
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if ((!e.nativeEvent.cancelBubble || this.onClickHandler || this.layoutDoc.onDragStart) &&
+ // 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)) &&
+ !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
+ e.stopPropagation();
+ // don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though
+ //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault();
}
- }
-
- onPointerMove = (e: PointerEvent): void => {
- if (e.cancelBubble) return;
- if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].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) {
- if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && "alias") || (this.props.dropAction || this.Document.dropAction || undefined) as dropActionType);
- }
- }
- e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
- e.preventDefault();
+ if (this.props.isDocumentActive?.()) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
}
- }
-
- cleanupPointerEvents = () => {
- this.cleanUpInteractions();
- document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- }
-
- onPointerUp = (e: PointerEvent): void => {
- this.cleanupPointerEvents();
-
- if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
- this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
- } else {
- this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
- // bcz: this is a placeholder. documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
- if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now();// don't want to process the start of a double tap if the doucment is selected
- }
- }
-
- @undoBatch @action
- toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
- this.Document.ignoreClick = false;
- if (setPushpin) {
- this.Document.isPushpin = !this.Document.isPushpin;
- this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton;
- } else {
- this.Document._isLinkButton = !this.Document._isLinkButton;
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ }
+
+ onPointerMove = (e: PointerEvent): void => {
+ if (e.cancelBubble) 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) {
+ if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && "alias") || (this.props.dropAction || this.Document.dropAction || undefined) as dropActionType);
+ }
}
- if (this.Document._isLinkButton && !this.onClickHandler) {
- this.Document.followLinkZoom = zoom;
- this.Document.followLinkLocation = location;
- } else if (this.Document._isLinkButton && this.onClickHandler) {
- this.Document._isLinkButton = false;
- this.Document["onClick-rawScript"] = this.dataDoc["onClick-rawScript"] = this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
- }
- }
- @undoBatch @action
- toggleTargetOnClick = (): void => {
- this.Document.ignoreClick = false;
- this.Document._isLinkButton = true;
- this.Document.isPushpin = true;
- }
- @undoBatch @action
- followLinkOnClick = (location: Opt<string>, zoom: boolean,): void => {
- this.Document.ignoreClick = false;
- this.Document._isLinkButton = true;
- this.Document.isPushpin = false;
- this.Document.followLinkZoom = zoom;
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
+ }
+ }
+
+ cleanupPointerEvents = () => {
+ this.cleanUpInteractions();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ onPointerUp = (e: PointerEvent): void => {
+ this.cleanupPointerEvents();
+
+ if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
+ } else {
+ this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+ // bcz: this is a placeholder. documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
+ if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now();// don't want to process the start of a double tap if the doucment is selected
+ }
+ }
+
+ @undoBatch @action
+ toggleFollowLink = (location: Opt<string>, zoom?: boolean, setPushpin?: boolean): void => {
+ this.Document.ignoreClick = false;
+ if (setPushpin) {
+ this.Document.isPushpin = !this.Document.isPushpin;
+ this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton;
+ } else {
+ this.Document._isLinkButton = !this.Document._isLinkButton;
+ }
+ if (this.Document._isLinkButton && !this.onClickHandler) {
+ zoom !== undefined && (this.Document.followLinkZoom = zoom);
this.Document.followLinkLocation = location;
- }
- @undoBatch @action
- selectOnClick = (): void => {
- this.Document.ignoreClick = false;
- this.Document._isLinkButton = false;
- this.Document.isPushpin = false;
- this.Document.onClick = this.layoutDoc.onClick = undefined;
- }
- @undoBatch
- noOnClick = (): void => {
- this.Document.ignoreClick = false;
+ } else if (this.Document._isLinkButton && this.onClickHandler) {
this.Document._isLinkButton = false;
- }
-
- @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
- @undoBatch setToggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${StrCast(this.Document.layoutKey).replace("layout_", "")}")`, { documentView: "any" });
-
- @undoBatch @action
- drop = async (e: Event, de: DragManager.DropEvent) => {
- if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return;
- if (this.props.Document === CurrentUserUtils.ActiveDashboard) {
- alert((e.target as any)?.closest?.("*.lm_content") ?
- "You can't perform this move most likely because you don't have permission to modify the destination." :
- "Linking to document tabs not yet supported. Drop link on document content.");
- return;
+ this.Document["onClick-rawScript"] = this.dataDoc["onClick-rawScript"] = this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
+ }
+ }
+ @undoBatch @action
+ toggleTargetOnClick = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document._isLinkButton = true;
+ this.Document.isPushpin = true;
+ }
+ @undoBatch @action
+ followLinkOnClick = (location: Opt<string>, zoom: boolean,): void => {
+ this.Document.ignoreClick = false;
+ this.Document._isLinkButton = true;
+ this.Document.isPushpin = false;
+ this.Document.followLinkZoom = zoom;
+ this.Document.followLinkLocation = location;
+ }
+ @undoBatch @action
+ selectOnClick = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document._isLinkButton = false;
+ this.Document.isPushpin = false;
+ this.Document.onClick = this.layoutDoc.onClick = undefined;
+ }
+ @undoBatch
+ noOnClick = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document._isLinkButton = false;
+ }
+
+ @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
+ @undoBatch setToggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${StrCast(this.Document.layoutKey).replace("layout_", "")}")`, { documentView: "any" });
+
+ @undoBatch @action
+ drop = async (e: Event, de: DragManager.DropEvent) => {
+ if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return;
+ if (this.props.Document === CurrentUserUtils.ActiveDashboard) {
+ alert((e.target as any)?.closest?.("*.lm_content") ?
+ "You can't perform this move most likely because you don't have permission to modify the destination." :
+ "Linking to document tabs not yet supported. Drop link on document content.");
+ return;
+ }
+ const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
+ if (linkdrag) linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
+ if (linkdrag?.linkSourceDoc) {
+ e.stopPropagation();
+ if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
+ de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
}
- const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
- if (linkdrag) linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
- if (linkdrag?.linkSourceDoc) {
- e.stopPropagation();
- if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
- de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
- }
- 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]);
- }
+ 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 - 50]);
}
- }
-
- @undoBatch
- @action
- makeIntoPortal = async () => {
- const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
- if (!portalLink) {
- const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" });
- DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to:portal from");
+ }
+ }
+
+ @undoBatch
+ @action
+ makeIntoPortal = async () => {
+ const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
+ if (!portalLink) {
+ const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" });
+ DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to:portal from");
+ }
+ this.Document.followLinkLocation = "inPlace";
+ this.Document.followLinkZoom = true;
+ this.Document._isLinkButton = true;
+ }
+
+ @action
+ onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
+ if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
+ e.preventDefault();
+ e.stopPropagation();
+ //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
+ }
+ // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
+ if (e) {
+ if (e.button === 0 && !e.ctrlKey || e.isDefaultPrevented()) {
+ e.preventDefault();
+ return;
}
- this.Document.followLinkLocation = "inPlace";
- this.Document.followLinkZoom = true;
- this.Document._isLinkButton = true;
- }
-
- @action
- onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
- if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
- e.preventDefault();
- e.stopPropagation();
- //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
+ e.preventDefault();
+ e.stopPropagation();
+ e.persist();
+
+ if (!navigator.userAgent.includes("Mozilla") && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
+ return;
}
- // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
- if (e) {
- if (e.button === 0 && !e.ctrlKey || e.isDefaultPrevented()) {
- e.preventDefault();
- return;
- }
- e.preventDefault();
- e.stopPropagation();
- e.persist();
-
- if (!navigator.userAgent.includes("Mozilla") && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
- return;
- }
+ }
+
+ const cm = ContextMenu.Instance;
+ if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
+
+ if (e && !(e.nativeEvent as any).dash) {
+ const onDisplay = () => setTimeout(() => {
+ DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ setTimeout(() => {
+ const ele = document.elementFromPoint(e.clientX, e.clientY);
+ simulateMouseClick(ele, e.clientX, e.clientY, e.screenX, e.screenY);
+ });
+ });
+ if (navigator.userAgent.includes("Macintosh")) {
+ cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, onDisplay);
+ }
+ else {
+ onDisplay();
+ }
+ return;
+ }
+
+ const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
+ StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
+ cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
+ this.props.contextMenuItems?.().forEach(item =>
+ item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: item.icon as IconProp }));
+
+ if (!this.props.Document.isFolder) {
+ const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+ const appearance = cm.findByDescription("UI Controls...");
+ const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
+ !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
+ !Doc.UserDoc().noviceMode && appearanceItems.push({
+ description: "Add a Field", event: () => {
+ const alias = Doc.MakeAlias(this.rootDoc);
+ alias.layout = FormattedTextBox.LayoutString("newfield");
+ alias.title = "newfield";
+ alias._height = 35;
+ alias._width = 100;
+ alias.syncLayoutFieldWithTitle = true;
+ alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width);
+ alias.y = NumCast(this.rootDoc.y);
+ this.props.addDocument?.(alias);
+ }, icon: "eye"
+ });
+ DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
+ !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
+
+ if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
+ !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" });
+ const existingOnClick = cm.findByDescription("OnClick...");
+ const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+
+ const zorders = cm.findByDescription("ZOrder...");
+ const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
+ if (this.props.bringToFront !== emptyFunction) {
+ zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
+ zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
+ zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
+ }
+ !zorders && cm.addItem({ description: "ZOrder...", noexpand: true, subitems: zorderItems, icon: "compass" });
+
+ !Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
+ !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
+ this.props.CollectionFreeFormDocumentView && onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+
+ if (!this.Document.annotationOn) {
+ const options = cm.findByDescription("Options...");
+ const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+ !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+
+ onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+ onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
+ !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
+ onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
+ onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
+ onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
+ } else if (DocListCast(this.Document.links).length) {
+ onClicks.push({ description: "Select on Click", event: () => this.selectOnClick(), icon: "link" });
+ onClicks.push({ description: "Follow Link on Click", event: () => this.followLinkOnClick(undefined, false), icon: "link" });
+ onClicks.push({ description: "Toggle Link Target on Click", event: () => this.toggleTargetOnClick(), icon: "map-pin" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" });
+ }
}
- const cm = ContextMenu.Instance;
- if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
-
- if (e && !(e.nativeEvent as any).dash) {
- const onDisplay = () => setTimeout(() => {
- DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
- setTimeout(() => {
- const ele = document.elementFromPoint(e.clientX, e.clientY);
- simulateMouseClick(ele, e.clientX, e.clientY, e.screenX, e.screenY);
- });
- });
- if (navigator.userAgent.includes("Macintosh")) {
- cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, onDisplay);
- }
- else {
- onDisplay();
- }
- return;
+ const funcs: ContextMenuProps[] = [];
+ if (!Doc.UserDoc().noviceMode && this.layoutDoc.onDragStart) {
+ funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
+ funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
+ funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
+ cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
}
- const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
- StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
- cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
- this.props.contextMenuItems?.().forEach(item =>
- item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: item.icon as IconProp }));
-
- if (!this.props.Document.isFolder) {
- const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
- const appearance = cm.findByDescription("UI Controls...");
- const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
- !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
- !Doc.UserDoc().noviceMode && appearanceItems.push({
- description: "Add a Field", event: () => {
- 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;
- alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width);
- alias.y = NumCast(this.rootDoc.y);
- this.props.addDocument?.(alias);
- }, icon: "eye"
- });
- DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
- !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
-
- if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
- !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" });
- const existingOnClick = cm.findByDescription("OnClick...");
- const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
-
- const zorders = cm.findByDescription("ZOrder...");
- const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
- if (this.props.bringToFront !== emptyFunction) {
- zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
- zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
- zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
- }
- !zorders && cm.addItem({ description: "ZOrder...", noexpand: true, subitems: zorderItems, icon: "compass" });
-
- !Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
- !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
- this.props.CollectionFreeFormDocumentView && onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
-
- if (!this.Document.annotationOn) {
- const options = cm.findByDescription("Options...");
- const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
-
- onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
- onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
- !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
- onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
- onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
- onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
- !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
- } else if (DocListCast(this.Document.links).length) {
- onClicks.push({ description: "Select on Click", event: () => this.selectOnClick(), icon: "link" });
- onClicks.push({ description: "Follow Link on Click", event: () => this.followLinkOnClick(undefined, false), icon: "link" });
- onClicks.push({ description: "Toggle Link Target on Click", event: () => this.toggleTargetOnClick(), icon: "map-pin" });
- !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" });
- }
- }
-
- const funcs: ContextMenuProps[] = [];
- if (!Doc.UserDoc().noviceMode && this.layoutDoc.onDragStart) {
- funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
- funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
- funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
- cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
- }
-
- const more = cm.findByDescription("More...");
- const moreItems = more && "subitems" in more ? more.subitems : [];
- if (!Doc.IsSystem(this.rootDoc)) {
- (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
- if (!Doc.UserDoc().noviceMode) {
- moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
- moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" });
-
- if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
- moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
- moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
- moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
- }
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: "fingerprint" });
- }
- }
-
- if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && CurrentUserUtils.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
- moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
- }
- !more && moreItems.length && cm.addItem({ description: "More...", subitems: moreItems, icon: "compass" });
-
- const help = cm.findByDescription("Help...");
- const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
- !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
- helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
- !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
- !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
- cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
+ const more = cm.findByDescription("More...");
+ const moreItems = more && "subitems" in more ? more.subitems : [];
+ if (!Doc.IsSystem(this.rootDoc)) {
+ (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
+ if (!Doc.UserDoc().noviceMode) {
+ moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
+ moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" });
+
+ if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
+ moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
+ moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
+ moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
+ }
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: "fingerprint" });
+ }
}
- if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
- cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
- }
-
- collectionFilters = () => StrListCast(this.props.Document._docFilters);
- collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters);
- @computed get showFilterIcon() {
- return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
- this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined;
- }
- rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
- panelHeight = () => this.props.PanelHeight() - this.headerMargin;
- screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
- contentScaling = () => this.ContentScale;
- onClickFunc = () => this.onClickHandler;
- setHeight = (height: number) => this.layoutDoc._height = height;
- setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
- isContentActive = (outsideReaction?: boolean) => {
- return this.props.isContentActive() === false ? false : (
- CurrentUserUtils.SelectedTool !== InkTool.None ||
- SnappingManager.GetIsDragging() ||
- this.rootSelected() ||
- this.props.Document.forceActive ||
- this.props.isSelected(outsideReaction) ||
- this._componentView?.isAnyChildContentActive?.() ||
- this.props.isContentActive()) ? true : undefined;
- }
- @observable _retryThumb = 1;
- thumbShown = () => !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
- !this._componentView?.isAnyChildContentActive?.() ? true : false
- @computed get contents() {
- TraceMobx();
- const audioView = !this.layoutDoc._showAudio ? (null) :
- <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter} >
- <FontAwesomeIcon className="documentView-audioFont"
- style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }}
- icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" />
- </div>;
-
- 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"),
- 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()}
- width={this.props.PanelWidth()} height={this.props.PanelHeight()}
- onError={(e: any) => {
- setTimeout(action(() => this._retryThumb = 0), 0);
- setTimeout(action(() => this._retryThumb = 1), 150);
- }} />}
- <DocumentContentsView key={1}
- {...this.props}
- docViewPath={this.props.viewPath}
- thumbShown={this.thumbShown}
- isHovering={this.isHovering}
- setContentView={this.setContentView}
- scaling={this.contentScaling}
- PanelHeight={this.panelHeight}
- setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
- isContentActive={this.isContentActive}
- ScreenToLocalTransform={this.screenToLocal}
- rootSelected={this.rootSelected}
- onClick={this.onClickFunc}
- focus={this.focus}
- layoutKey={this.finalLayoutKey} />
- {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
- {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) :
- <DocumentLinksButton View={this.props.DocumentView()}
- ContentScaling={this.props.ContentScaling}
- Offset={[this.topMost ? 0 : !this.props.isSelected() ? - 15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? - 15 : -30]} />
- }
- {audioView}
- </div>;
- }
-
- get indicatorIcon() {
- if (this.props.Document["acl-Public"] !== SharingPermissions.None) return "globe-americas";
- else if (this.props.Document.numGroupsShared || NumCast(this.props.Document.numUsersShared, 0) > 1) return "users";
- else return "user";
- }
-
- @undoBatch
- hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true)
- anchorPanelWidth = () => this.props.PanelWidth() || 1;
- anchorPanelHeight = () => this.props.PanelHeight() || 1;
- anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
- switch (property) {
- case StyleProp.ShowTitle: return "";
- case StyleProp.PointerEvents: return "none";
- case StyleProp.LinkSource: return this.props.Document;// pass the LinkSource to the LinkAnchorBox
- default: return this.props.styleProvider?.(doc, props, property);
+ if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && CurrentUserUtils.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
+ moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
}
- }
- // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
- // anchors that are not rendered as DocumentViews (marked as 'unrendered' with their 'annotationOn' set to this document). e.g.,
- // - PDF text regions are rendered as an Annotations without generating a DocumentView, '
- // - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
- // - and links to PDF/Web docs at a certain scroll location never create an explicit view.
- // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
- @computed get directLinks() {
- TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(link =>
- Doc.AreProtosEqual(link.anchor1 as Doc, this.rootDoc) ||
- Doc.AreProtosEqual(link.anchor2 as Doc, this.rootDoc) ||
- ((link.anchor1 as Doc).unrendered && Doc.AreProtosEqual((link.anchor1 as Doc).annotationOn as Doc, this.rootDoc)) ||
- ((link.anchor2 as Doc).unrendered && Doc.AreProtosEqual((link.anchor2 as Doc).annotationOn as Doc, this.rootDoc))
- );
- }
- @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); }
- @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links
- TraceMobx();
- if (this.layoutDoc.unrendered || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
- if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
- return filtered.map((link, i) =>
- <div className="documentView-anchorCont" key={link[Id]}>
- <DocumentView {...this.props}
- Document={link}
- PanelWidth={this.anchorPanelWidth}
- PanelHeight={this.anchorPanelHeight}
- dontRegisterView={false}
- showTitle={returnEmptyString}
- hideCaptions={true}
- fitWidth={returnTrue}
- styleProvider={this.anchorStyleProvider}
- removeDocument={this.hideLinkAnchor}
- LayoutTemplate={undefined}
- LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(link, this.rootDoc)}`)} />
- </div >);
- }
-
- @action
- onPointerEnter = () => {
- const self = this;
- const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]);
- if (audioAnnos && audioAnnos.length && this._mediaState === 0) {
- const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
- anno.data instanceof AudioField && new Howl({
- src: [anno.data.url.href],
- format: ["mp3"],
- autoplay: true,
- loop: false,
- volume: 0.5,
- onend: function () {
- runInAction(() => self._mediaState = 0);
- }
- });
- this._mediaState = 1;
+ !more && moreItems.length && cm.addItem({ description: "More...", subitems: moreItems, icon: "compass" });
+
+ const help = cm.findByDescription("Help...");
+ const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
+ !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
+ helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
+ !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
+ !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
+ cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
+ }
+
+ if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
+ cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
+ }
+
+ collectionFilters = () => StrListCast(this.props.Document._docFilters);
+ collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters);
+ @computed get showFilterIcon() {
+ return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
+ this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined;
+ }
+ rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
+ panelHeight = () => this.props.PanelHeight() - this.headerMargin;
+ screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+ contentScaling = () => this.ContentScale;
+ onClickFunc = () => this.onClickHandler;
+ setHeight = (height: number) => this.layoutDoc._height = height;
+ setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
+ isContentActive = (outsideReaction?: boolean) => {
+ return this.props.isContentActive() === false ? false : (
+ CurrentUserUtils.SelectedTool !== InkTool.None ||
+ SnappingManager.GetIsDragging() ||
+ this.rootSelected() ||
+ this.props.Document.forceActive ||
+ this.props.isSelected(outsideReaction) ||
+ this._componentView?.isAnyChildContentActive?.() ||
+ this.props.isContentActive()) ? true : undefined;
+ }
+ @observable _retryThumb = 1;
+ 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) :
+ <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter} >
+ <FontAwesomeIcon className="documentView-audioFont"
+ style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }}
+ icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" />
+ </div>;
+
+ return <div className="documentView-contentsView"
+ style={{
+ 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: "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);
+ setTimeout(action(() => this._retryThumb = 1), 150);
+ }} />}
+ <DocumentContentsView key={1}
+ {...this.props}
+ docViewPath={this.props.viewPath}
+ thumbShown={this.thumbShown}
+ isHovering={this.isHovering}
+ setContentView={this.setContentView}
+ scaling={this.contentScaling}
+ PanelHeight={this.panelHeight}
+ setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
+ isContentActive={this.isContentActive}
+ ScreenToLocalTransform={this.screenToLocal}
+ rootSelected={this.rootSelected}
+ onClick={this.onClickFunc}
+ focus={this.focus}
+ layoutKey={this.finalLayoutKey} />
+ {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
+ {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) :
+ <DocumentLinksButton View={this.props.DocumentView()}
+ ContentScaling={this.props.ContentScaling}
+ Offset={[this.topMost ? 0 : !this.props.isSelected() ? - 15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? - 15 : -30]} />
}
- }
- recordAudioAnnotation = () => {
- let gumStream: any;
- let recorder: any;
- const self = this;
- navigator.mediaDevices.getUserMedia({
- audio: true
- }).then(function (stream) {
- gumStream = stream;
- recorder = new MediaRecorder(stream);
- recorder.ondataavailable = async (e: any) => {
- const [{ result }] = await Networking.UploadFilesToServer(e.data);
- if (!(result instanceof Error)) {
- const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: "audio test", _width: 200, _height: 32 });
- audioDoc.treeViewExpandedView = "layout";
- const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc));
- if (audioAnnos === undefined) {
- self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"] = new List([audioDoc]);
- } else {
- audioAnnos.push(audioDoc);
- }
- }
- };
- runInAction(() => self._mediaState = 2);
- recorder.start();
- setTimeout(() => {
- recorder.stop();
- runInAction(() => self._mediaState = 0);
- gumStream.getAudioTracks()[0].stop();
- }, 5000);
+ {audioView}
+ </div>;
+ }
+
+ get indicatorIcon() {
+ if (this.props.Document["acl-Public"] !== SharingPermissions.None) return "globe-americas";
+ else if (this.props.Document.numGroupsShared || NumCast(this.props.Document.numUsersShared, 0) > 1) return "users";
+ else return "user";
+ }
+
+ @undoBatch
+ hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true)
+ anchorPanelWidth = () => this.props.PanelWidth() || 1;
+ anchorPanelHeight = () => this.props.PanelHeight() || 1;
+ anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+ switch (property) {
+ case StyleProp.ShowTitle: return "";
+ case StyleProp.PointerEvents: return "none";
+ case StyleProp.LinkSource: return this.props.Document;// pass the LinkSource to the LinkAnchorBox
+ default: return this.props.styleProvider?.(doc, props, property);
+ }
+ }
+ // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
+ // anchors that are not rendered as DocumentViews (marked as 'unrendered' with their 'annotationOn' set to this document). e.g.,
+ // - PDF text regions are rendered as an Annotations without generating a DocumentView, '
+ // - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
+ // - and links to PDF/Web docs at a certain scroll location never create an explicit view.
+ // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
+ @computed get directLinks() {
+ TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(link =>
+ Doc.AreProtosEqual(link.anchor1 as Doc, this.rootDoc) ||
+ Doc.AreProtosEqual(link.anchor2 as Doc, this.rootDoc) ||
+ ((link.anchor1 as Doc).unrendered && Doc.AreProtosEqual((link.anchor1 as Doc).annotationOn as Doc, this.rootDoc)) ||
+ ((link.anchor2 as Doc).unrendered && Doc.AreProtosEqual((link.anchor2 as Doc).annotationOn as Doc, this.rootDoc))
+ );
+ }
+ @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); }
+ @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links
+ TraceMobx();
+ if (this.layoutDoc.unrendered || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
+ if (this.rootDoc.type=== DocumentType.PRES || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
+ const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
+ 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}
+ dontRegisterView={false}
+ showTitle={returnEmptyString}
+ hideCaptions={true}
+ fitWidth={returnTrue}
+ styleProvider={this.anchorStyleProvider}
+ removeDocument={this.hideLinkAnchor}
+ LayoutTemplate={undefined}
+ LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(link, this.rootDoc)}`)} />
+ </div >);
+ }
+
+ @action
+ onPointerEnter = () => {
+ const self = this;
+ const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]);
+ if (audioAnnos && audioAnnos.length && this._mediaState === 0) {
+ const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
+ anno.data instanceof AudioField && new Howl({
+ src: [anno.data.url.href],
+ format: ["mp3"],
+ autoplay: true,
+ loop: false,
+ volume: 0.5,
+ onend: function () {
+ runInAction(() => self._mediaState = 0);
+ }
});
- }
-
- captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption");
- @computed get innards() {
- TraceMobx();
- const ffscale = () => (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1);
- const showTitle = this.ShowTitle?.split(":")[0];
- const showTitleHover = this.ShowTitle?.includes(":hover");
- const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined;
- const captionView = !showCaption ? (null) :
- <div className="documentView-captionWrapper"
- style={{
- pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
- minWidth: 50 * ffscale(),
- maxHeight: `max(100%, ${20 * ffscale()}px)`
- }}>
- <FormattedTextBox {...OmitKeys(this.props, ['children']).omit}
- yPadding={10}
- xPadding={10}
- fieldKey={showCaption}
- fontSize={12 * Math.max(1, 2 * ffscale() / 3)}
- styleProvider={this.captionStyleProvider}
- dontRegisterView={true}
- noSidebar={true}
- dontScale={true}
- isContentActive={this.isContentActive}
- onClick={this.onClickFunc}
- />
- </div>;
- const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
- const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor,
- Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
- const titleView = !showTitle ? (null) :
- <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
- position: this.headerMargin ? "relative" : "absolute",
- height: this.titleHeight,
- color: lightOrDark(background),
- background,
- pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
- }}>
- <EditableView ref={this._titleRef}
- contents={showTitle.split(";").map(field => field.trim()).map(field => targetDoc[field]?.toString()).join("\\")}
- display={"block"}
- fontSize={10}
- GetValue={() => showTitle.split(";").length === 1 ? showTitle + "=" + Field.toString(targetDoc[showTitle.split(";")[0]] as any as Field) : "#" + showTitle}
- SetValue={undoBatch((input: string) => {
- if (input?.startsWith("#")) {
- if (this.props.showTitle) {
- this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined;
- } else {
- Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : "creationDate";
- }
- } else {
- var value = input.replace(new RegExp(showTitle + "="), "") as string | number;
- if (showTitle !== "title" && Number(value).toString() === value) value = Number(value);
- if (showTitle.includes("Date") || showTitle === "author") return true;
- Doc.SetInPlace(targetDoc, showTitle, value, true);
- }
- return true;
- })}
- />
- </div>;
- return this.props.hideTitle || (!showTitle && !showCaption) ?
- this.contents :
- <div className="documentView-styleWrapper" >
- {!this.headerMargin ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
- {captionView}
- </div>;
- }
- isHovering = () => this._isHovering;
- @observable _isHovering = false;
- @observable _: string = "";
- @computed get renderDoc() {
- TraceMobx();
- const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png");
- const isButton = this.props.Document.type === DocumentType.FONTICON;
- if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || (this.hidden && !this.props.treeViewDoc)) return null;
- return this.docContents ??
- <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
- id={this.props.Document[Id]}
- onPointerEnter={action(() => this._isHovering = true)}
- onPointerLeave={action(() => this._isHovering = false)}
- style={{
- background: isButton || thumb ? undefined : this.backgroundColor,
- opacity: this.opacity,
- color: StrCast(this.layoutDoc.color, "inherit"),
- fontFamily: StrCast(this.Document._fontFamily, "inherit"),
- fontSize: Cast(this.Document._fontSize, "string", null),
- transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
- transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this._animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? "in" : "out"}`,
- }}>
-
- {this.innards}
- {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : (null)}
- {this.widgetDecorations ?? null}
- </div>;
- }
- 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];
- 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
-
- const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined };
- const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc;
- const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
- this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined);
-
- // Return surrounding highlight
- return <div className={`${DocumentView.ROOT_DIV} docView-hack`} ref={this._mainCont}
- onContextMenu={this.onContextMenu}
- 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)}
- style={{
- display: this.hidden ? "inline" : undefined,
- borderRadius: this.borderRounding,
- pointerEvents: this.pointerEvents,
- outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px",
- border: highlighting && this.borderRounding && highlightStyle === "dashed" ? `${highlightStyle} ${highlightColor} ${highlightIndex}px` : undefined,
- boxShadow,
- clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined
- }}>
- {!borderPath.path ? internal :
- <>
- {/* <div style={{ clipPath: `path('${borderPath.fill}')` }}>
+ this._mediaState = 1;
+ }
+ }
+ recordAudioAnnotation = () => {
+ let gumStream: any;
+ let recorder: any;
+ const self = this;
+ navigator.mediaDevices.getUserMedia({
+ audio: true
+ }).then(function (stream) {
+ gumStream = stream;
+ recorder = new MediaRecorder(stream);
+ recorder.ondataavailable = async (e: any) => {
+ const [{ result }] = await Networking.UploadFilesToServer(e.data);
+ if (!(result instanceof Error)) {
+ const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: "audio test", _width: 200, _height: 32 });
+ audioDoc.treeViewExpandedView = "layout";
+ const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc));
+ if (audioAnnos === undefined) {
+ self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"] = new List([audioDoc]);
+ } else {
+ audioAnnos.push(audioDoc);
+ }
+ }
+ };
+ runInAction(() => self._mediaState = 2);
+ recorder.start();
+ setTimeout(() => {
+ recorder.stop();
+ runInAction(() => self._mediaState = 0);
+ gumStream.getAudioTracks()[0].stop();
+ }, 5000);
+ });
+ }
+
+ captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption");
+ @computed get innards() {
+ TraceMobx();
+ const ffscale = () => (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1);
+ const showTitle = this.ShowTitle?.split(":")[0];
+ const showTitleHover = this.ShowTitle?.includes(":hover");
+ const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined;
+ const captionView = !showCaption ? (null) :
+ <div className="documentView-captionWrapper"
+ style={{
+ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
+ minWidth: 50 * ffscale(),
+ maxHeight: `max(100%, ${20 * ffscale()}px)`
+ }}>
+ <FormattedTextBox {...OmitKeys(this.props, ['children']).omit}
+ yPadding={10}
+ xPadding={10}
+ fieldKey={showCaption}
+ fontSize={12 * Math.max(1, 2 * ffscale() / 3)}
+ styleProvider={this.captionStyleProvider}
+ dontRegisterView={true}
+ noSidebar={true}
+ dontScale={true}
+ isContentActive={this.isContentActive}
+ onClick={this.onClickFunc}
+ />
+ </div>;
+ const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
+ const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor,
+ Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
+ const titleView = !showTitle ? (null) :
+ <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
+ position: this.headerMargin ? "relative" : "absolute",
+ height: this.titleHeight,
+ color: lightOrDark(background),
+ background,
+ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
+ }}>
+ <EditableView ref={this._titleRef}
+ contents={showTitle.split(";").map(field => field.trim()).map(field => targetDoc[field]?.toString()).join("\\")}
+ display={"block"}
+ fontSize={10}
+ GetValue={() => showTitle.split(";").length === 1 ? showTitle + "=" + Field.toString(targetDoc[showTitle.split(";")[0]] as any as Field) : "#" + showTitle}
+ SetValue={undoBatch((input: string) => {
+ if (input?.startsWith("#")) {
+ if (this.props.showTitle) {
+ this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined;
+ } else {
+ Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : "creationDate";
+ }
+ } else {
+ var value = input.replace(new RegExp(showTitle + "="), "") as string | number;
+ if (showTitle !== "title" && Number(value).toString() === value) value = Number(value);
+ if (showTitle.includes("Date") || showTitle === "author") return true;
+ Doc.SetInPlace(targetDoc, showTitle, value, true);
+ }
+ return true;
+ })}
+ />
+ </div>;
+ return this.props.hideTitle || (!showTitle && !showCaption) ?
+ this.contents :
+ <div className="documentView-styleWrapper" >
+ {!this.headerMargin ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
+ {captionView}
+ </div>;
+ }
+ isHovering = () => this._isHovering;
+ @observable _isHovering = false;
+ @observable _: string = "";
+ @computed get renderDoc() {
+ TraceMobx();
+ const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png");
+ const isButton = this.props.Document.type === DocumentType.FONTICON;
+ if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || (this.hidden && !this.props.treeViewDoc)) return null;
+ return this.docContents ??
+ <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
+ id={this.props.Document[Id]}
+ onPointerEnter={action(() => this._isHovering = true)}
+ onPointerLeave={action(() => this._isHovering = false)}
+ style={{
+ background: isButton || thumb ? undefined : this.backgroundColor,
+ opacity: this.opacity,
+ color: StrCast(this.layoutDoc.color, "inherit"),
+ fontFamily: StrCast(this.Document._fontFamily, "inherit"),
+ fontSize: Cast(this.Document._fontSize, "string", null),
+ transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
+ transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this._animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? "in" : "out"}`,
+ }}>
+
+ {this.innards}
+ {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : (null)}
+ {this.widgetDecorations ?? null}
+ </div>;
+ }
+ render() {
+ TraceMobx();
+ 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
+
+ const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined };
+ const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc;
+ const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
+ this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined);
+
+ // Return surrounding highlight
+ return <div className={`${DocumentView.ROOT_DIV} docView-hack`} ref={this._mainCont}
+ onContextMenu={this.onContextMenu}
+ onKeyDown={this.onKeyDown}
+ onPointerDown={this.onPointerDown}
+ onClick={this.onClick}
+ 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,
+ pointerEvents: this.pointerEvents,
+ outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px",
+ border: highlighting && this.borderRounding && highlightStyle === "dashed" ? `${highlightStyle} ${highlightColor} ${highlightIndex}px` : undefined,
+ boxShadow,
+ clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined
+ }}>
+ {!borderPath.path ? internal :
+ <>
+ {/* <div style={{ clipPath: `path('${borderPath.fill}')` }}>
{internal}
</div> */}
- {internal}
- <div key="border2" className="documentView-customBorder" style={{ pointerEvents: "none" }} >
- <svg style={{ overflow: "visible" }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
- <path d={borderPath.path} style={{ stroke: "black", fill: "transparent", strokeWidth: borderPath.width }} />
- </svg>
- </div>
- </>
- }
- {this.showFilterIcon ?
- <FontAwesomeIcon icon={"filter"} size="lg"
- style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
- onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
- />
- : (null)}
- </div>;
- }
+ {internal}
+ <div key="border2" className="documentView-customBorder" style={{ pointerEvents: "none" }} >
+ <svg style={{ overflow: "visible" }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
+ <path d={borderPath.path} style={{ stroke: "black", fill: "transparent", strokeWidth: borderPath.width }} />
+ </svg>
+ </div>
+ </>
+ }
+ {this.showFilterIcon ?
+ <FontAwesomeIcon icon={"filter"} size="lg"
+ style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
+ onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
+ />
+ : (null)}
+ </div>;
+ }
}
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
- public static ROOT_DIV = "documentView-effectsWrapper";
- public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
- public ContentRef = React.createRef<HTMLDivElement>();
- private _disposers: { [name: string]: IReactionDisposer } = {};
-
- public static showBackLinks(linkSource: Doc) {
- const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + "-pivotish";
- DocServer.GetRefField(docid).then(docx => {
- const rootAlias = () => {
- const rootAlias = Doc.MakeAlias(linkSource);
- rootAlias.x = rootAlias.y = 0;
- return rootAlias;
- };
- const linkCollection = ((docx instanceof Doc) && docx) || Docs.Create.StackingDocument([/*rootAlias()*/], { title: linkSource.title + "-pivot", _width: 500, _height: 500, }, docid);
- linkCollection.linkSource = linkSource;
- if (!linkCollection.reactionScript) linkCollection.reactionScript = ScriptField.MakeScript("updateLinkCollection(self)");
- LightboxView.SetLightboxDoc(linkCollection);
- });
- }
-
- @observable public docView: DocumentViewInternal | undefined | null;
-
- get Document() { return this.props.Document; }
- get topMost() { return this.props.renderDepth === 0; }
- get rootDoc() { return this.docView?.rootDoc || this.Document; }
- get dataDoc() { return this.docView?.dataDoc || this.Document; }
- get finalLayoutKey() { return this.docView?.finalLayoutKey || "layout"; }
- get ContentDiv() { return this.docView?.ContentDiv; }
- get ComponentView() { return this.docView?._componentView; }
- get allLinks() { return this.docView?.allLinks || []; }
- get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
- get fitWidth() { return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth; }
-
- @computed get docViewPath(): DocumentView[] { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
- @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
- @computed get nativeWidth() {
- return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
- returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
- }
- @computed get nativeHeight() {
- return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
- returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
- }
- @computed get shouldNotScale() {
- return (this.fitWidth && !this.nativeWidth) ||
- this.props.ContainingCollectionView?.collectionViewType === CollectionViewType.Time ||
- this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
- }
- @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
- @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
- @computed get nativeScaling() {
- if (this.shouldNotScale) return 1;
- const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
- if (this.fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
- return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or fitWidth
- }
- return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
- }
-
- @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); }
- @computed get panelHeight() {
- if (this.effectiveNativeHeight) {
- return Math.min(this.props.PanelHeight(), Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling);
- }
- return this.props.PanelHeight();
- }
- @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; }
- @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; }
- @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; }
- @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; }
-
- 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())) {
- return undefined;
- }
- const xf = (this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling)).inverse();
- const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
- if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
- }
- return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
- }
-
- public iconify(finished?: () => void) {
- this.ComponentView?.updateIcon?.();
- const layoutKey = Cast(this.Document.layoutKey, "string", null);
- if (layoutKey !== "layout_icon") {
- this.switchViews(true, "icon", finished);
- if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.Document.deiconifyLayout = layoutKey.replace("layout_", "");
- } else {
- const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null);
- this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished);
- this.Document.deiconifyLayout = undefined;
- this.props.bringToFront(this.rootDoc);
- }
- }
- @undoBatch
- @action
- setCustomView = (custom: boolean, layout: string): void => {
- Doc.setNativeView(this.props.Document);
- custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
- }
- switchViews = action((custom: boolean, view: string, finished?: () => void) => {
- this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
+ public static ROOT_DIV = "documentView-effectsWrapper";
+ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
+ public ContentRef = React.createRef<HTMLDivElement>();
+ private _disposers: { [name: string]: IReactionDisposer } = {};
+
+ public static showBackLinks(linkSource: Doc) {
+ const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + "-pivotish";
+ DocServer.GetRefField(docid).then(docx => {
+ const rootAlias = () => {
+ const rootAlias = Doc.MakeAlias(linkSource);
+ rootAlias.x = rootAlias.y = 0;
+ return rootAlias;
+ };
+ const linkCollection = ((docx instanceof Doc) && docx) || Docs.Create.StackingDocument([/*rootAlias()*/], { title: linkSource.title + "-pivot", _width: 500, _height: 500, }, docid);
+ linkCollection.linkSource = linkSource;
+ if (!linkCollection.reactionScript) linkCollection.reactionScript = ScriptField.MakeScript("updateLinkCollection(self)");
+ LightboxView.SetLightboxDoc(linkCollection);
+ });
+ }
+
+ @observable public docView: DocumentViewInternal | undefined | null;
+
+ get Document() { return this.props.Document; }
+ get topMost() { return this.props.renderDepth === 0; }
+ get rootDoc() { return this.docView?.rootDoc || this.Document; }
+ get dataDoc() { return this.docView?.dataDoc || this.Document; }
+ get finalLayoutKey() { return this.docView?.finalLayoutKey || "layout"; }
+ get ContentDiv() { return this.docView?.ContentDiv; }
+ get ComponentView() { return this.docView?._componentView; }
+ get allLinks() { return this.docView?.allLinks || []; }
+ get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
+ get fitWidth() { return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth; }
+
+ @computed get docViewPath(): DocumentView[] { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
+ @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
+ @computed get nativeWidth() {
+ return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
+ returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
+ }
+ @computed get nativeHeight() {
+ return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
+ returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
+ }
+ @computed get shouldNotScale() {
+ return (this.fitWidth && !this.nativeWidth) ||
+ 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)); }
+ @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
+ @computed get nativeScaling() {
+ if (this.shouldNotScale) return 1;
+ const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
+ if (this.fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
+ return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or fitWidth
+ }
+ return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
+ }
+
+ @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); }
+ @computed get panelHeight() {
+ if (this.effectiveNativeHeight) {
+ return Math.min(this.props.PanelHeight(), Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling);
+ }
+ return this.props.PanelHeight();
+ }
+ @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; }
+ @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; }
+ @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; }
+ @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; }
+
+ 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.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();
+ const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
+ if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+ const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
+ }
+ return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
+ }
+
+ public iconify(finished?: () => void) {
+ this.ComponentView?.updateIcon?.();
+ const layoutKey = Cast(this.Document.layoutKey, "string", null);
+ if (layoutKey !== "layout_icon") {
+ this.switchViews(true, "icon", finished);
+ if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.Document.deiconifyLayout = layoutKey.replace("layout_", "");
+ } else {
+ const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null);
+ this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished);
+ this.Document.deiconifyLayout = undefined;
+ this.props.bringToFront(this.rootDoc);
+ }
+ }
+ @undoBatch
+ @action
+ setCustomView = (custom: boolean, layout: string): void => {
+ Doc.setNativeView(this.props.Document);
+ custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
+ }
+ switchViews = action((custom: boolean, view: string, finished?: () => void) => {
+ this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
+ setTimeout(action(() => {
+ this.setCustomView(custom, view);
+ this.docView && (this.docView._animateScalingTo = 1); // expand it
setTimeout(action(() => {
- this.setCustomView(custom, view);
- this.docView && (this.docView._animateScalingTo = 1); // expand it
- setTimeout(action(() => {
- this.docView && (this.docView._animateScalingTo = 0);
- finished?.();
- }), this.docView!._animateScaleTime - 10);
+ this.docView && (this.docView._animateScalingTo = 0);
+ finished?.();
}), this.docView!._animateScaleTime - 10);
- });
-
- startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
-
- docViewPathFunc = () => this.docViewPath;
- isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
- select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
- NativeWidth = () => this.effectiveNativeWidth;
- NativeHeight = () => this.effectiveNativeHeight;
- PanelWidth = () => this.panelWidth;
- PanelHeight = () => this.panelHeight;
- ContentScale = () => this.nativeScaling;
- selfView = () => this;
- screenToLocalTransform = () => {
- return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling);
- }
- componentDidMount() {
- this._disposers.reactionScript = reaction(
- () => ScriptCast(this.rootDoc.reactionScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result,
- output => output
- );
- this._disposers.height = reaction(
- () => NumCast(this.layoutDoc._height),
- action(height => {
- const docMax = NumCast(this.layoutDoc.docMaxAutoHeight);
- if (docMax && docMax < height) this.layoutDoc.docMaxAutoHeight = height;
- })
- );
- !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
- }
- componentWillUnmount() {
- Object.values(this._disposers).forEach(disposer => disposer?.());
- !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
- }
-
- render() {
- TraceMobx();
- const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
- const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
- const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
- const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
- // const isOverlay: boolean = CurrentUserUtils.OverlayDocs.includes(this.props.Document);
- return (<div className="contentFittingDocumentView">
- {!this.props.Document || !this.props.PanelWidth() ? (null) : (
- <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
- style={{
- transition: this.props.dataTransition,
- position: this.props.Document.isInkMask ? "absolute" : undefined,
- transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
- 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()}%`),
- }}>
- <DocumentViewInternal {...this.props}
- DocumentView={this.selfView}
- viewPath={this.docViewPathFunc}
- PanelWidth={this.PanelWidth}
- PanelHeight={this.PanelHeight}
- NativeWidth={this.NativeWidth}
- NativeHeight={this.NativeHeight}
- isSelected={this.isSelected}
- select={this.select}
- ContentScaling={this.ContentScale}
- ScreenToLocalTransform={this.screenToLocalTransform}
- focus={this.props.focus || emptyFunction}
- ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
- </div>)}
- </div>);
- }
+ }), this.docView!._animateScaleTime - 10);
+ });
+
+ startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
+
+ docViewPathFunc = () => this.docViewPath;
+ isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
+ select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
+ NativeWidth = () => this.effectiveNativeWidth;
+ NativeHeight = () => this.effectiveNativeHeight;
+ PanelWidth = () => this.panelWidth;
+ PanelHeight = () => this.panelHeight;
+ ContentScale = () => this.nativeScaling;
+ selfView = () => this;
+ screenToLocalTransform = () => {
+ 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(
+ () => ScriptCast(this.rootDoc.reactionScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result,
+ output => output
+ );
+ this._disposers.height = reaction(
+ () => NumCast(this.layoutDoc._height),
+ action(height => {
+ const docMax = NumCast(this.layoutDoc.docMaxAutoHeight);
+ if (docMax && docMax < height) this.layoutDoc.docMaxAutoHeight = height;
+ })
+ );
+ !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+ }
+ componentWillUnmount() {
+ Object.values(this._disposers).forEach(disposer => disposer?.());
+ !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
+ }
+
+ render() {
+ TraceMobx();
+ const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+ const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+ const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
+ const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
+ return (<div className="contentFittingDocumentView">
+ {!this.props.Document || !this.props.PanelWidth() ? (null) : (
+ <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
+ style={{
+ 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()}%`),
+ }}>
+ <DocumentViewInternal {...this.props}
+ DocumentView={this.selfView}
+ viewPath={this.docViewPathFunc}
+ PanelWidth={this.PanelWidth}
+ PanelHeight={this.PanelHeight}
+ NativeWidth={this.NativeWidth}
+ NativeHeight={this.NativeHeight}
+ isSelected={this.isSelected}
+ select={this.select}
+ ContentScaling={this.ContentScale}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ focus={this.props.focus || emptyFunction}
+ ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
+ </div>)}
+ </div>);
+ }
}
export function deiconifyViewFunc(documentView: DocumentView) {
- documentView.iconify();
- //StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
+ documentView.iconify();
+ //StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
}
ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
- documentView.iconify();
- documentView.select(false);
+ documentView.iconify();
+ documentView.select(false);
});
ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
- if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout");
- else dv.switchViews(true, detailLayoutKeySuffix);
+ if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout");
+ else dv.switchViews(true, detailLayoutKeySuffix);
});
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) {
- const linkSource = Cast(linkCollection.linkSource, Doc, null);
- const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
- let wid = linkSource[WidthSym]();
- const links = DocListCast(linkSource.links);
- links.forEach(link => {
- const other = LinkManager.getOppositeAnchor(link, linkSource);
- const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
- if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
- const alias = Doc.MakeAlias(otherdoc);
- alias.x = wid;
- alias.y = 0;
- alias._lockedPosition = false;
- wid += otherdoc[WidthSym]();
- Doc.AddDocToList(Doc.GetProto(linkCollection), "data", alias);
- }
- });
- return links;
+ const linkSource = Cast(linkCollection.linkSource, Doc, null);
+ const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
+ let wid = linkSource[WidthSym]();
+ const links = DocListCast(linkSource.links);
+ links.forEach(link => {
+ const other = LinkManager.getOppositeAnchor(link, linkSource);
+ const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
+ if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
+ const alias = Doc.MakeAlias(otherdoc);
+ alias.x = wid;
+ alias.y = 0;
+ alias._lockedPosition = false;
+ wid += otherdoc[WidthSym]();
+ Doc.AddDocToList(Doc.GetProto(linkCollection), "data", alias);
+ }
+ });
+ return links;
}); \ No newline at end of file
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 bd52eb018..67cf18d8b 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";
import { RecordingBox } from "./RecordingBox";
//
@@ -24,9 +25,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 5982d4d66..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;
}
@@ -72,10 +73,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._disposers.sizer = reaction(() => (
{
forceFull: this.props.renderDepth < 1 || this.layoutDoc._showFullRes,
- scrSize: this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0],
+ scrSize: this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth,
selected: this.props.isSelected()
}),
- ({ forceFull, scrSize, selected }) => this._curSuffix = this.fieldKey === "icon" ? "_m" : forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o",
+ ({ forceFull, scrSize, selected }) => this._curSuffix = this.fieldKey === "icon" ? "_m" : forceFull ? "_o" : scrSize < 0.25 ? "_s" : scrSize < 0.5 ? "_m" : scrSize < 0.8 || !selected ? "_l" : "_o",
{ fireImmediately: true, delay: 1000 });
this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
({ nativeSize, width }) => {
@@ -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/LabelBigText.js b/src/client/views/nodes/LabelBigText.js
index 02c36c4bc..290152cd0 100644
--- a/src/client/views/nodes/LabelBigText.js
+++ b/src/client/views/nodes/LabelBigText.js
@@ -100,7 +100,8 @@ export default function BigText(element, options) {
horizontalAlign: "center",
verticalAlign: "center",
textAlign: "center",
- whiteSpace: "nowrap"
+ whiteSpace: "nowrap",
+ singleLine: true
};
//Merge provided options and default options
@@ -111,20 +112,29 @@ export default function BigText(element, options) {
//Get variables which we will reference frequently
var style = element.style;
- var computedStyle = document.defaultView.getComputedStyle(element);
var parent = element.parentNode;
var parentStyle = parent.style;
var parentComputedStyle = document.defaultView.getComputedStyle(parent);
//hides the element to prevent "flashing"
style.visibility = "hidden";
-
//Set some properties
style.display = "inline-block";
style.clear = "both";
style.float = "left";
- style.fontSize = (1000 * options.fontSizeFactor) + "px";
- style.lineHeight = "1000px";
+ var fontSize = options.maximumFontSize;
+ if (options.singleLine) {
+ style.fontSize = (fontSize * options.fontSizeFactor) + "px";
+ style.lineHeight = fontSize + "px";
+ } else {
+ for (; fontSize > options.minimumFontSize; fontSize = fontSize - Math.min(fontSize / 2, Math.max(0, fontSize - 48) + 2)) {
+ style.fontSize = (fontSize * options.fontSizeFactor) + "px";
+ style.lineHeight = "1";
+ if (element.offsetHeight <= +parentComputedStyle.height.replace("px", "")) {
+ break;
+ }
+ }
+ }
style.whiteSpace = options.whiteSpace;
style.textAlign = options.textAlign;
style.position = "relative";
@@ -132,6 +142,7 @@ export default function BigText(element, options) {
style.margin = 0;
style.left = "50%";
style.top = "50%";
+ var computedStyle = document.defaultView.getComputedStyle(element);
//Get properties of parent to allow easier referencing later.
var parentPadding = {
@@ -175,29 +186,31 @@ export default function BigText(element, options) {
box.height = element.offsetWidth * sine + element.offsetHeight * cosine;
}
- var widthFactor = (parentInnerWidth - parentPadding.left - parentPadding.right) / box.width;
- var heightFactor = (parentInnerHeight - parentPadding.top - parentPadding.bottom) / box.height;
+ var parentWidth = (parentInnerWidth - parentPadding.left - parentPadding.right);
+ var parentHeight = (parentInnerHeight - parentPadding.top - parentPadding.bottom);
+ var widthFactor = parentWidth / box.width;
+ var heightFactor = parentHeight / box.height;
var lineHeight;
if (options.limitingDimension.toLowerCase() === "width") {
- lineHeight = Math.floor(widthFactor * 1000);
- parentStyle.height = lineHeight + "px";
+ lineHeight = Math.floor(widthFactor * fontSize);
} else if (options.limitingDimension.toLowerCase() === "height") {
- lineHeight = Math.floor(heightFactor * 1000);
+ lineHeight = Math.floor(heightFactor * fontSize);
} else if (widthFactor < heightFactor)
- lineHeight = Math.floor(widthFactor * 1000);
+ lineHeight = Math.floor(widthFactor * fontSize);
else if (widthFactor >= heightFactor)
- lineHeight = Math.floor(heightFactor * 1000);
+ lineHeight = Math.floor(heightFactor * fontSize);
var fontSize = lineHeight * options.fontSizeFactor;
if (fontSize < options.minimumFontSize) {
parentStyle.display = "flex";
parentStyle.alignItems = "center";
- style.whiteSpace = "pre-wrap";
style.textAlign = "center";
style.visibility = "";
- style.fontSize = "18px";
- style.lineHeight = "20px";
+ style.fontSize = options.minimumFontSize + "px";
+ style.lineHeight = "";
+ style.overflow = "hidden";
+ style.textOverflow = "ellipsis";
style.top = "";
style.left = "";
style.margin = "";
@@ -213,11 +226,11 @@ export default function BigText(element, options) {
style.marginBottom = "0px";
style.marginRight = "0px";
- if (options.limitingDimension.toLowerCase() === "height") {
- //this option needs the font-size to be set already so computedStyle.getPropertyValue("width") returns the right size
- //this +4 is to compensate the rounding erros that can occur due to the calls to Math.floor in the centering code
- parentStyle.width = (parseInt(computedStyle.getPropertyValue("width")) + 4) + "px";
- }
+ // if (options.limitingDimension.toLowerCase() === "height") {
+ // //this option needs the font-size to be set already so computedStyle.getPropertyValue("width") returns the right size
+ // //this +4 is to compensate the rounding erros that can occur due to the calls to Math.floor in the centering code
+ // parentStyle.width = (parseInt(computedStyle.getPropertyValue("width")) + 4) + "px";
+ // }
//Calculate the inner width and height
var innerDimensions = _calculateInnerDimensions(computedStyle);
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index d539ca9b8..b0b050cea 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
-import { Cast, StrCast, NumCast } from '../../../fields/Types';
+import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -23,18 +23,23 @@ 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;
componentDidMount() {
this.props.setContentView?.(this);
}
+ componentWillUnMount() {
+ this._timeout && clearTimeout(this._timeout);
+ }
getTitle() {
- return this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) : this.props.label ? this.props.label :
- typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title);
+ return this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) :
+ this.props.label ? this.props.label :
+ typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) :
+ StrCast(this.rootDoc.title);
}
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -73,21 +78,38 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
@observable _mouseOver = false;
@computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; }
- fitTextToBox = (r: any) => {
- BigText(r, {
+ fitTextToBox = (r: any): any => {
+ const singleLine = BoolCast(this.rootDoc._singleLine, true);
+ const params = {
rotateText: null,
fontSizeFactor: 1,
- minimumFontSize: NumCast(this.layoutDoc._minFontSize),
- maximumFontSize: NumCast(this.layoutDoc._maxFontSize),
+ minimumFontSize: NumCast(this.rootDoc._minFontSize, 8),
+ maximumFontSize: NumCast(this.rootDoc._maxFontSize, 1000),
limitingDimension: "both",
horizontalAlign: "center",
verticalAlign: "center",
textAlign: "center",
- whiteSpace: "nowrap"
- });
+ singleLine,
+ whiteSpace: singleLine ? "nowrap" : "pre-wrap"
+ };
+ this._timeout = undefined;
+ if (!r) return params;
+ if (!r.offsetHeight || !r.offsetWidth) return this._timeout = setTimeout(() => this.fitTextToBox(r));
+ const parent = r.parentNode;
+ const parentStyle = parent.style;
+ parentStyle.display = "";
+ parentStyle.alignItems = "";
+ r.setAttribute("style", "");
+ 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() {
+ 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 ...
@@ -104,16 +126,17 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit",
letterSpacing: StrCast(this.layoutDoc.letterSpacing),
textTransform: StrCast(this.layoutDoc.textTransform) as any,
+ paddingLeft: NumCast(this.rootDoc._xPadding),
+ paddingRight: NumCast(this.rootDoc._xPadding),
+ paddingTop: NumCast(this.rootDoc._yPadding),
+ paddingBottom: NumCast(this.rootDoc._yPadding),
width: this.props.PanelWidth(),
height: this.props.PanelHeight(),
- whiteSpace: this.layoutDoc._singleLine ? "pre" : "pre-wrap"
+ whiteSpace: boxParams.singleLine ? "pre" : "pre-wrap"
}} >
- <span ref={r => {
- if (r) {
- if (!r.offsetWidth || !r.offsetHeight) setTimeout(() => this.fitTextToBox(r));
- else this.fitTextToBox(r);
- }
- }}>{label.startsWith("#") ? (null) : label}</span>
+ <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>
<div className="labelBox-fieldKeyParams" >
{!missingParams?.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 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..6c7e174f7 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);
}
@@ -118,7 +121,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
LinkDocPreview.Clear();
LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
} else if (this.props.hrefs?.length) {
- this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _width: 200, _height: 400, useCors: true }), "add:right");
+ this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), "add:right");
}
}
width = () => {
@@ -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 668c6c8fc..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();
@@ -87,7 +160,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
CreateImage(
"",
document.styleSheets,
- htmlString?.replace(/docView-hack/g, 'documentView-hack'),
+ htmlString,
nativeWidth,
nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"])
@@ -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>
@@ -290,6 +367,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(Utils.prepend("") + this.pdfUrl.url.pathname), icon: "expand-arrows-alt" });
+ funcs.push({ description: "update icon", event: () => this.pdfUrl && this.updateIcon(), icon: "expand-arrows-alt" });
//funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
@@ -326,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}
@@ -342,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>
@@ -384,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/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index bd4af0a75..5e97e3eb5 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -25,7 +25,6 @@ export class RecordingBox extends ViewBoxBaseComponent() {
}
componentDidMount() {
- console.log("set native width and height")
Doc.SetNativeWidth(this.dataDoc, 1280);
Doc.SetNativeHeight(this.dataDoc, 720);
}
@@ -40,7 +39,6 @@ export class RecordingBox extends ViewBoxBaseComponent() {
@action
setResult = (info: Upload.AccessPathInfo, trackScreen: boolean) => {
- if (info == null) return
this.result = info
this.dataDoc.type = DocumentType.VID;
this.dataDoc[this.fieldKey + "-duration"] = this.videoDuration;
@@ -55,7 +53,6 @@ export class RecordingBox extends ViewBoxBaseComponent() {
}
render() {
- console.log("Proto[Is]: ", this.rootDoc.proto?.[Id])
return <div className="recordingBox" ref={this._ref}>
{!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={this.rootDoc.proto?.[Id] || ''} />}
</div>;
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss
index 0b7b1918f..2e6f6bc26 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.scss
+++ b/src/client/views/nodes/RecordingBox/RecordingView.scss
@@ -18,13 +18,13 @@ button {
width: 100%;
// display: flex;
pointer-events: all;
- background-color: grey;
+ background-color: black;
}
.video-wrapper {
// max-width: 600px;
// max-width: 700px;
- position: relative;
+ // position: relative;
display: flex;
justify-content: center;
// overflow: hidden;
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 7cff54ff2..e8737dce2 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -7,7 +7,7 @@ import { FaCheckCircle } from 'react-icons/fa';
import { IconContext } from "react-icons";
import { Networking } from '../../../Network';
import { Upload } from '../../../../server/SharedMediaTypes';
-
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
import { RecordingApi } from '../../../util/RecordingApi';
import { DashUploadUtils } from '../../../../server/DashUploadUtils';
@@ -170,28 +170,28 @@ export function RecordingView(props: IRecordingViewProps) {
}
// recording paused
- videoRecorder.current.onpause = (event: any) => {
- // append the current portion to the video pieces
- setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }])
-
- // reset the temporary chunks
- videoChunks = []
- setRecording(false);
- trackScreen && RecordingApi.Instance.pause();
- }
-
- videoRecorder.current.onresume = async (event: any) => {
- await startShowingStream();
- setRecording(true);
- trackScreen && RecordingApi.Instance.resume();
- }
+ // videoRecorder.current.onpause = (event: any) => {
+ // // append the current portion to the video pieces
+ // setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }])
+
+ // // reset the temporary chunks
+ // videoChunks = []
+ // setRecording(false);
+ // trackScreen && RecordingApi.Instance.pause();
+ // }
+
+ // videoRecorder.current.onresume = async (event: any) => {
+ // await startShowingStream();
+ // setRecording(true);
+ // trackScreen && RecordingApi.Instance.resume();
+ // }
videoRecorder.current.start(200)
}
- const stop = (e: React.MouseEvent) => {
- e.stopPropagation()
+ const stop = (e: React.PointerEvent) => {
+ e.stopPropagation();
if (videoRecorder.current) {
setFinished(true);
if (videoRecorder.current.state !== "inactive") {
@@ -201,7 +201,7 @@ export function RecordingView(props: IRecordingViewProps) {
}
}
- const pause = (e: React.MouseEvent) => {
+ const pause = (e: React.PointerEvent) => {
e.stopPropagation()
if (videoRecorder.current) {
if (videoRecorder.current.state === "recording") {
@@ -210,25 +210,24 @@ export function RecordingView(props: IRecordingViewProps) {
}
}
- const startOrResume = (e: React.MouseEvent) => {
- e.stopPropagation()
- if (!videoRecorder.current || videoRecorder.current.state === "inactive") {
- record();
- } else if (videoRecorder.current.state === "paused") {
- videoRecorder.current.start();
- }
+ const startOrResume = (e: React.PointerEvent) => {
+ // the code to start or resume does not get triggered if we start dragging the button
+ setupMoveUpEvents({}, e, returnTrue, returnFalse, e => {
+ if (!videoRecorder.current || videoRecorder.current.state === "inactive") {
+ record();
+ } else if (videoRecorder.current.state === "paused") {
+ videoRecorder.current.start();
+ }
+ return true; // cancels propagation to documentView to avoid selecting it.
+ }, false, false);
}
- const undoPrevious = (e: React.MouseEvent) => {
+ const undoPrevious = (e: React.PointerEvent) => {
e.stopPropagation();
setDoUndo(prev => !prev);
}
- const handleOnTimeUpdate = () => {
- if (playing) {
- setVideoProgressHelper(videoElementRef.current!.currentTime)
- }
- };
+ const handleOnTimeUpdate = () => playing && setVideoProgressHelper(videoElementRef.current!.currentTime);
const millisecondToMinuteSecond = (milliseconds: number) => {
const toTwoDigit = (digit: number) => {
@@ -239,7 +238,6 @@ export function RecordingView(props: IRecordingViewProps) {
return toTwoDigit(minutes) + " : " + toTwoDigit(seconds);
}
- // TODO: have the undo button only appear if there is something to undo
return (
<div className="recording-container">
<div className="video-wrapper">
@@ -257,8 +255,8 @@ export function RecordingView(props: IRecordingViewProps) {
<div className="controls-inner-container">
<div className="record-button-wrapper">
{recording ?
- <button className="stop-button" onClick={pause} /> :
- <button className="record-button" onClick={startOrResume} />
+ <button className="stop-button" onPointerDown={pause} /> :
+ <button className="record-button" onPointerDown={startOrResume} />
}
</div>
@@ -266,10 +264,10 @@ export function RecordingView(props: IRecordingViewProps) {
<div className="options-wrapper video-edit-wrapper">
<IconContext.Provider value={{ color: "grey", className: "video-edit-buttons", style: {display: canUndo ? 'inherit' : 'none'} }}>
- <MdBackspace onClick={undoPrevious} />
+ <MdBackspace onPointerDown={undoPrevious} />
</IconContext.Provider>
<IconContext.Provider value={{ color: "#cc1c08", className: "video-edit-buttons" }}>
- <FaCheckCircle onClick={stop} />
+ <FaCheckCircle onPointerDown={stop} />
</IconContext.Provider>
</div>
@@ -293,7 +291,6 @@ export function RecordingView(props: IRecordingViewProps) {
recording={recording}
doUndo={doUndo}
setCanUndo={setCanUndo}
- // playSegment={playSegment}
/>
</div>
</div>)
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index f47a71469..d4cddd65e 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;
}
@@ -87,12 +92,12 @@
background-color: $dark-gray;
color: white;
border-radius: 100px;
- top: calc(100% - 20px);
- left: 50%;
- transform: translate(-50%, -100%);
-
+ transform-origin: bottom left;
+ left: 0;
+ bottom: 0;
+
transition: top 0.5s, width 0.5s, opacity 0.2s, visibility 0s;
- height: 120px;
+ height: 24px;
padding: 0 20px;
.timecode-controls {
@@ -102,27 +107,28 @@
justify-content: center;
margin: 0 5px;
flex-grow: 2;
- font-size: 32px;
+ font-size: 12px;
.timecode {
margin: 0 5px;
}
.timeline-slider {
- margin: 0 20px 0 20px;
+ margin: 0 10px 0 10px;
flex-grow: 2;
}
}
- .toolbar-slider.volume, .toolbar-slider.zoom {
+ .toolbar-slider.volume,
+ .toolbar-slider.zoom {
width: 100px;
}
.videobox-button {
margin: 5px;
cursor: pointer;
- width: 70px;
- height: 70px;
+ width: 24px;
+ height: 24px;
border-radius: 50%;
background: $dark-gray;
display: flex;
@@ -134,8 +140,8 @@
}
svg {
- width: 40px;
- height: 40px;
+ width: 18px;
+ height: 18px;
}
}
}
@@ -151,6 +157,7 @@
pointer-events: all;
padding-right: 5px;
cursor: pointer;
+
&:hover {
background-color: $medium-gray;
}
@@ -164,7 +171,8 @@
}
}
-.videoBox-content-fullScreen, .videoBox-content-fullScreen-interactive {
+.videoBox-content-fullScreen,
+.videoBox-content-fullScreen-interactive {
display: flex;
justify-content: center;
align-items: center;
@@ -196,21 +204,21 @@ input[type="range"]:focus {
input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
- height: 20px;
+ height: 18px;
cursor: pointer;
box-shadow: 0;
background: $light-gray;
- border-radius: 20px;
+ border-radius: 18px;
}
input[type="range"]::-webkit-slider-thumb {
box-shadow: 0;
border: 0;
- height: 26px;
- width: 26px;
+ height: 20px;
+ width: 20px;
border-radius: 20px;
background: $medium-blue;
cursor: pointer;
-webkit-appearance: none;
- margin-top: -3px;
+ margin-top: -1px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index baa0ae709..b14a1f0f6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -357,7 +357,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// sets video element ref
@action
setVideoRef = (vref: HTMLVideoElement | null) => {
- console.log("Set video ref " + vref)
this._videoRef = vref;
if (vref) {
this._videoRef!.ontimeupdate = this.updateTimecode;
@@ -365,7 +364,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
this._disposers.reactionDisposer?.();
this._disposers.reactionDisposer = reaction(() => NumCast(this.layoutDoc._currentTimecode),
- time => !this._playing && (vref.currentTime = time), { fireImmediately: true });
+ time => {
+ !this._playing && (vref.currentTime = time);
+ console.log("vref time = " + vref.currentTime)
+ }, { fireImmediately: true });
}
}
@@ -858,12 +860,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(); }}>
@@ -905,7 +908,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 5a39123b9..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() || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegree(this.props.Document) ? true : false) });
this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight,
autoHeight => {
@@ -186,31 +197,26 @@ 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;
}
- componentWillUnmount() {
+ @action componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
- this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
- this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
+ // this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
+ // this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
}
@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 08a5746d1..f3f1bcf5c 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) {
@@ -214,8 +214,8 @@ var ForeignHtmlRenderer = function (styleSheets) {
.replace(/noscript/g, "div").replace(/<div class="mediaset"><\/div>/g, "") // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
.replace(/<link[^>]*>/g, "") // don't need to keep any linked style sheets because we've already processed all style sheets above
.replace(/srcset="([^ "]*)[^"]*"/g, "src=\"$1\""); // instead of converting each item in the srcset to a data url, just convert the first one and use that
- let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml);
- const fetchedResources = await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml);
+ let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith("data:"));
+ const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : [];
for (let i = 0; i < fetchedResources.length; i++) {
const r = fetchedResources[i];
if (r.resourceUrl) {
@@ -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(/\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 71b165618..a1b9023f3 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';
@@ -30,540 +33,545 @@ import { WebBox } from '../WebBox';
import { FontIconBadge } from './FontIconBadge';
import './FontIconBox.scss';
const FontIconSchema = createSchema({
- icon: "string",
+ icon: "string",
});
export enum ButtonType {
- TextButton = "textBtn",
- MenuButton = "menuBtn",
- DropdownList = "drpdownList",
- DropdownButton = "drpdownBtn",
- ClickButton = "clickBtn",
- DoubleButton = "dblBtn",
- ToggleButton = "tglBtn",
- ColorButton = "colorBtn",
- ToolButton = "toolBtn",
- NumberButton = "numBtn",
- EditableText = "editableText"
+ TextButton = "textBtn",
+ MenuButton = "menuBtn",
+ DropdownList = "drpdownList",
+ DropdownButton = "drpdownBtn",
+ ClickButton = "clickBtn",
+ DoubleButton = "dblBtn",
+ ToggleButton = "tglBtn",
+ ColorButton = "colorBtn",
+ ToolButton = "toolBtn",
+ NumberButton = "numBtn",
+ EditableText = "editableText"
}
export enum NumButtonType {
- Slider = "slider",
- DropdownOptions = "list",
- Inline = "inline"
+ Slider = "slider",
+ DropdownOptions = "list",
+ Inline = "inline"
}
export interface ButtonProps extends FieldViewProps {
- type?: ButtonType;
+ type?: ButtonType;
}
@observer
export class FontIconBox extends DocComponent<ButtonProps>() {
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); }
- showTemplate = (): void => {
- const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
- dragFactory && this.props.addDocTab(dragFactory, "add:right");
- }
- dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); };
- useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); };
-
- specificContextMenu = (): void => {
- if (!Doc.UserDoc().noviceMode) {
- const cm = ContextMenu.Instance;
- cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" });
- cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" });
- cm.addItem({ description: "Use as Prototype", event: this.useAsPrototype, icon: "tag" });
- }
- }
-
- // Determining UI Specs
- @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
- @observable private icon = StrCast(this.dataDoc.icon, "user") as any;
- @observable private dropdown: boolean = BoolCast(this.rootDoc.dropDownOpen);
- @observable private buttonList: string[] = StrListCast(this.rootDoc.btnList);
- @observable private type = StrCast(this.rootDoc.btnType);
-
- /**
- * Types of buttons in dash:
- * - Main menu button (LHS)
- * - Tool button
- * - Expandable button (CollectionLinearView)
- * - Button inside of CollectionLinearView vs. outside of CollectionLinearView
- * - Action button
- * - Dropdown button
- * - Color button
- * - Dropdown list
- * - Number button
- **/
-
- _batch: UndoManager.Batch | undefined = undefined;
- /**
- * Number button
- */
- @computed get numberButton() {
- const numBtnType: string = StrCast(this.rootDoc.numBtnType);
- const numScript = ScriptCast(this.rootDoc.script);
- const setValue = (value: number) => numScript?.script.run({ value, _readOnly_: false });
-
- // Script for checking the outcome of the toggle
- const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0;
-
- 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>;
- return (
- <div
- className={`menuButton ${this.type} ${numBtnType}`}
- onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}
- >
- {checkResult}
- {this.rootDoc.dropDownOpen ? dropdown : null}
- </div>
- );
- } else if (numBtnType === NumButtonType.DropdownOptions) {
- const items: number[] = [];
- for (let i = 0; i < 100; i++) {
- if (i % 2 === 0) {
- items.push(i);
- }
- }
- const list = items.map((value) => {
- return <div className="list-item" key={`${value}`}
- style={{
- backgroundColor: value === checkResult ? Colors.LIGHT_BLUE : undefined
- }}
- onClick={() => setValue(value)}>
- {value}
- </div>;
- });
- return (
- <div
- className={`menuButton ${this.type} ${numBtnType}`}
- >
- <div className={`button`} onClick={action((e) => setValue(Number(checkResult) - 1))}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"minus"} />
- </div>
- <div
- className={`button ${'number'}`}
- onPointerDown={(e) => {
- e.stopPropagation();
- e.preventDefault();
- }}
- onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}
- >
- <input
- style={{ width: 30 }}
- className="button-input"
- type="number"
- value={checkResult}
- onChange={action((e) => setValue(Number(e.target.value)))}
- />
- </div>
- <div className={`button`} onClick={action((e) => setValue(Number(checkResult) + 1))}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"plus"} />
- </div>
-
- {this.rootDoc.dropDownOpen ?
- <div>
- <div className="menuButton-dropdownList"
- style={{ left: "25%" }}>
- {list}
- </div>
- <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
- </div> : null}
-
- </div>
- );
- } else {
- return (
- <div>
-
- </div>
- );
- }
-
-
- }
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); }
+ showTemplate = (): void => {
+ const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
+ dragFactory && this.props.addDocTab(dragFactory, "add:right");
+ }
+ dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); };
+ useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); };
+
+ specificContextMenu = (): void => {
+ if (!Doc.UserDoc().noviceMode) {
+ const cm = ContextMenu.Instance;
+ cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" });
+ cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" });
+ cm.addItem({ description: "Use as Prototype", event: this.useAsPrototype, icon: "tag" });
+ }
+ }
+
+ // Determining UI Specs
+ @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
+ @observable private icon = StrCast(this.dataDoc.icon, "user") as any;
+ @observable private dropdown: boolean = BoolCast(this.rootDoc.dropDownOpen);
+ @observable private buttonList: string[] = StrListCast(this.rootDoc.btnList);
+ @observable private type = StrCast(this.rootDoc.btnType);
+
+ /**
+ * Types of buttons in dash:
+ * - Main menu button (LHS)
+ * - Tool button
+ * - Expandable button (CollectionLinearView)
+ * - Button inside of CollectionLinearView vs. outside of CollectionLinearView
+ * - Action button
+ * - Dropdown button
+ * - Color button
+ * - Dropdown list
+ * - Number button
+ **/
+
+ _batch: UndoManager.Batch | undefined = undefined;
+ /**
+ * Number button
+ */
+ @computed get numberButton() {
+ const numBtnType: string = StrCast(this.rootDoc.numBtnType);
+ const numScript = ScriptCast(this.rootDoc.script);
+ const setValue = (value: number) => numScript?.script.run({ value, _readOnly_: false });
+
+ // 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>;
- /**
- * Dropdown button
- */
- @computed get dropdownButton() {
- const active: string = StrCast(this.rootDoc.dropDownOpen);
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ 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>;
return (
- <div className={`menuButton ${this.type} ${active}`}
- style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
- onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- {!this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> {this.label} </div>}
- <div
- className="menuButton-dropdown"
- style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
- <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
- </div>
- {this.rootDoc.dropDownOpen ?
- <div className="menuButton-dropdownBox">
- {/* DROPDOWN BOX CONTENTS */}
- </div> : null}
- </div>
+ <div
+ className={`menuButton ${this.type} ${numBtnType}`}
+ onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}
+ >
+ {checkResult}
+ {label}
+ {this.rootDoc.dropDownOpen ? dropdown : null}
+ </div>
);
- }
-
- /**
- * Dropdown list
- */
- @computed get dropdownListButton() {
- const active: string = StrCast(this.rootDoc.dropDownOpen);
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-
- const script = ScriptCast(this.rootDoc.script);
-
- let noviceList: string[] = [];
- let text: string | undefined;
- let dropdown = true;
- let icon: IconProp = "caret-down";
- try {
- if (script.script.originalScript.startsWith('setView')) {
- const selected = SelectionManager.Docs().lastElement();
- if (selected) {
- if (StrCast(selected.type) === DocumentType.COL) {
- text = StrCast(selected._viewType);
- } else {
- dropdown = false;
- text = selected.type === DocumentType.RTF ? "Text" : StrCast(selected.type);
- icon = Doc.toIcon(selected);
- }
- } else {
- dropdown = false;
- icon = "globe-asia";
- text = "User Default";
- }
- noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
- } else if (script.script.originalScript.startsWith('setFont')) {
- const editorView = RichTextMenu.Instance?.TextView?.EditorView;
- text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
- noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia",
- "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
- }
- } catch (e) {
- console.log(e);
+ } else if (numBtnType === NumButtonType.DropdownOptions) {
+ const items: number[] = [];
+ for (let i = 0; i < 100; i++) {
+ if (i % 2 === 0) {
+ items.push(i);
+ }
}
-
- // Get items to place into the list
- const list = this.buttonList.map((value) => {
- if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) {
- return;
- }
- return <div className="list-item" key={`${value}`}
- style={{
- fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined,
- backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined
- }}
- onClick={() => script.script.run({ value }).result}>
- {value[0].toUpperCase() + value.slice(1)}
- </div>;
+ const list = items.map((value) => {
+ return <div className="list-item" key={`${value}`}
+ style={{
+ backgroundColor: value === checkResult ? Colors.LIGHT_BLUE : undefined
+ }}
+ onClick={() => setValue(value)}>
+ {value}
+ </div>;
});
-
- const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
- {this.label}
- </div>;
-
return (
- <div className={`menuButton ${this.type} ${active}`}
- style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : "flex" }}
- onClick={dropdown ? () => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}>
- {dropdown ? (null) : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
- <div className="menuButton-dropdown-header">
- {text && text[0].toUpperCase() + text.slice(1)}
- </div>
- {label}
- {!dropdown ? (null) : <div className="menuButton-dropDown">
- <FontAwesomeIcon icon={icon} color={color} size="sm" />
- </div>}
- {this.rootDoc.dropDownOpen ?
- <div>
- <div className="menuButton-dropdownList"
- style={{ left: 0 }}>
- {list}
- </div>
- <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
- </div>
- : null}
- </div>
+ <div
+ className={`menuButton ${this.type} ${numBtnType}`}
+ >
+ <div className={`button`} onClick={action((e) => setValue(Number(checkResult) - 1))}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"minus"} />
+ </div>
+ <div
+ className={`button ${'number'}`}
+ onPointerDown={(e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}
+ >
+ <input
+ style={{ width: 30 }}
+ className="button-input"
+ type="number"
+ value={checkResult}
+ onChange={action((e) => setValue(Number(e.target.value)))}
+ />
+ </div>
+ <div className={`button`} onClick={action((e) => setValue(Number(checkResult) + 1))}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"plus"} />
+ </div>
+
+ {this.rootDoc.dropDownOpen ?
+ <div>
+ <div className="menuButton-dropdownList"
+ style={{ left: "25%" }}>
+ {list}
+ </div>
+ <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
+ </div> : null}
+
+ </div>
);
- }
-
- @observable colorPickerClosed: boolean = true;
- @computed get colorScript() { return ScriptCast(this.rootDoc.script); }
-
- colorPicker = (curColor: string) => {
- const change = (value: ColorState) => {
- const s = this.colorScript;
- s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)();
- };
- const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
- '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
- '#FFFFFF', '#f1efeb', "transparent"];
- return <SketchPicker
- onChange={change}
- color={curColor}
- presetColors={presets} />;
- }
- /**
- * Color button
- */
- @computed get colorButton() {
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- 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" }}>
- {this.label}
- </div>;
-
- const dropdownCaret = <div
- className="menuButton-dropDown"
- style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
- <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
- </div>;
- setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView
+ } else {
return (
- <div className={`menuButton ${this.type} ${this.colorPickerClosed}`}
- style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
- onClick={action(() => this.colorPickerClosed = !this.colorPickerClosed)}
- onPointerDown={e => e.stopPropagation()}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- <div className="colorButton-color" style={{ backgroundColor: curColor }} />
- {label}
- {/* {dropdownCaret} */}
- {this.colorPickerClosed ? (null) :
- <div>
- <div className="menuButton-dropdownBox"
- onPointerDown={e => e.stopPropagation()}
- onClick={e => e.stopPropagation()}>
- {this.colorPicker(curColor)}
- </div>
- <div className="dropbox-background" onClick={action((e) => {
- e.stopPropagation(); this.colorPickerClosed = true;
- })} />
- </div>}
- </div>
+ <div>
+
+ </div>
);
- }
-
- @computed get toggleButton() {
- // Determine the type of toggle button
- const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle);
- const buttonText: string = StrCast(this.rootDoc.buttonText);
- // Colors
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-
- // Button label
- const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
- <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}>
- {this.label}
- </div>;
-
- if (switchToggle) {
- return (
- <div className={`menuButton ${this.type} ${'switch'}`}>
- {buttonText ? buttonText : null}
- <label className="switch">
- <input type="checkbox"
- checked={backgroundColor === Colors.MEDIUM_BLUE}
- />
- <span className="slider round"></span>
- </label>
- </div>
- );
- } else {
- return (
- <div className={`menuButton ${this.type}`}
- style={{ opacity: 1, backgroundColor, color }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- {label}
- </div>
- );
+ }
+
+
+ }
+
+ /**
+ * Dropdown button
+ */
+ @computed get dropdownButton() {
+ const active: string = StrCast(this.rootDoc.dropDownOpen);
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ return (
+ <div className={`menuButton ${this.type} ${active}`}
+ style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
+ onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {!this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> {this.label} </div>}
+ <div
+ className="menuButton-dropdown"
+ style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
+ <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
+ </div>
+ {this.rootDoc.dropDownOpen ?
+ <div className="menuButton-dropdownBox">
+ {/* DROPDOWN BOX CONTENTS */}
+ </div> : null}
+ </div>
+ );
+ }
+
+ /**
+ * Dropdown list
+ */
+ @computed get dropdownListButton() {
+ const active: string = StrCast(this.rootDoc.dropDownOpen);
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ 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;
+ let dropdown = true;
+ let icon: IconProp = "caret-down";
+ try {
+ if (script.script.originalScript.startsWith('setView')) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (selected) {
+ if (StrCast(selected.type) === DocumentType.COL) {
+ text = StrCast(selected._viewType);
+ } else {
+ dropdown = false;
+ text = selected.type === DocumentType.RTF ? "Text" : StrCast(selected.type);
+ icon = Doc.toIcon(selected);
+ }
+ } else {
+ dropdown = false;
+ icon = "globe-asia";
+ text = "User Default";
+ }
+ noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
+ } else if (script.script.originalScript.startsWith('setFont')) {
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+ noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia",
+ "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
}
- }
-
+ } catch (e) {
+ console.log(e);
+ }
+
+ // Get items to place into the list
+ const list = this.buttonList.map((value) => {
+ if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) {
+ return;
+ }
+ return <div className="list-item" key={`${value}`}
+ style={{
+ fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined,
+ backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined
+ }}
+ onClick={() => script.script.run({ value }).result}>
+ {value[0].toUpperCase() + value.slice(1)}
+ </div>;
+ });
+ const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
+ <div className="fontIconBox-label" style={{ bottom: 0, position: "absolute", color: color, backgroundColor: backgroundColor }}>
+ {this.label}
+ </div>;
- /**
- * Default
- */
- @computed get defaultButton() {
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const active: string = StrCast(this.rootDoc.dropDownOpen);
- return (
- <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu}
- 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>}
+ return (
+ <div className={`menuButton ${this.type} ${active}`}
+ style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : "flex" }}
+ onClick={dropdown ? () => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen : undefined}>
+ {dropdown ? (null) : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
+ <div className="menuButton-dropdown-header">
+ {text && text[0].toUpperCase() + text.slice(1)}
+ </div>
+ {label}
+ {!dropdown ? (null) : <div className="menuButton-dropDown">
+ <FontAwesomeIcon icon={icon} color={color} size="sm" />
+ </div>}
+ {this.rootDoc.dropDownOpen ?
+ <div>
+ <div className="menuButton-dropdownList"
+ style={{ left: 0 }}>
+ {list}
</div>
- </div>
- );
- }
+ <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
+ </div>
+ : null}
+ </div>
+ );
+ }
+
+ @observable colorPickerClosed: boolean = true;
+ @computed get colorScript() { return ScriptCast(this.rootDoc.script); }
+
+ colorPicker = (curColor: string) => {
+ const change = (value: ColorState) => {
+ const s = this.colorScript;
+ s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)();
+ };
+ const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505',
+ '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B',
+ '#FFFFFF', '#f1efeb', "transparent"];
+ return <SketchPicker
+ onChange={change}
+ color={curColor}
+ presetColors={presets} />;
+ }
+ /**
+ * Color button
+ */
+ @computed get colorButton() {
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ 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 }}>
+ {this.label}
+ </div>;
- @computed get editableText() {
- // Script for running the toggle
- const script = ScriptCast(this.rootDoc.script);
- // Function to run the script
- const checkResult = script?.script.run({ value: "", _readOnly_: true }).result;
+ const dropdownCaret = <div
+ className="menuButton-dropDown"
+ style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
+ <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
+ </div>;
+ setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView
+ return (
+ <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()}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ <div className="colorButton-color" style={{ backgroundColor: curColor }} />
+ {label}
+ {/* {dropdownCaret} */}
+ {this.colorPickerClosed ? (null) :
+ <div>
+ <div className="menuButton-dropdownBox"
+ onPointerDown={e => e.stopPropagation()}
+ onClick={e => e.stopPropagation()}>
+ {this.colorPicker(curColor)}
+ </div>
+ <div className="dropbox-background" onClick={action((e) => {
+ e.stopPropagation(); this.colorPickerClosed = true;
+ })} />
+ </div>}
+ </div>
+ );
+ }
+
+ @computed get toggleButton() {
+ // Determine the type of toggle button
+ const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle);
+ const buttonText: string = StrCast(this.rootDoc.buttonText);
+ // Colors
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+
+ // Button label
+ const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
+ <div className="fontIconBox-label" style={{ color, backgroundColor }}>
+ {this.label}
+ </div>;
- const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result;
+ if (switchToggle) {
return (
- <div className="menuButton editableText">
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"lock"} />
- <div style={{ width: "calc(100% - .875em)", paddingLeft: "4px" }}>
- <EditableView GetValue={() => script?.script.run({ value: "", _readOnly_: true }).result} SetValue={setValue} contents={checkResult} />
- </div>
- </div>
+ <div className={`menuButton ${this.type} ${'switch'}`}>
+ {buttonText ? buttonText : null}
+ <label className="switch">
+ <input type="checkbox"
+ checked={backgroundColor === Colors.MEDIUM_BLUE}
+ />
+ <span className="slider round" />
+ </label>
+ </div>
);
- }
-
- render() {
- 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" }}>
- {this.label}
- </div>;
-
- const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) :
- <div className="fontIconBox-label" style={{ color, backgroundColor: "transparent" }}>
- {this.label}
- </div>;
-
- const buttonText = StrCast(this.rootDoc.buttonText);
-
- // TODO:glr Add label of button type
- let button = this.defaultButton;
-
- switch (this.type) {
- case ButtonType.TextButton:
- button = (
- <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- {buttonText ?
- <div className="button-text">
- {buttonText}
- </div>
- : null}
- {label}
- </div>
- );
- // button = <TextButton {...buttonProps}></TextButton>
- break;
- case ButtonType.EditableText:
- button = this.editableText;
- break;
- case ButtonType.NumberButton:
- button = this.numberButton;
- break;
- case ButtonType.DropdownButton:
- button = this.dropdownButton;
- break;
- case ButtonType.DropdownList:
- button = this.dropdownListButton;
- break;
- case ButtonType.ColorButton:
- button = this.colorButton;
- break;
- case ButtonType.ToolButton:
- button = (
- <div className={`menuButton ${this.type}`} style={{ opacity: 1, backgroundColor, color }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- {label}
- </div>
- );
- break;
- case ButtonType.ToggleButton:
- button = this.toggleButton;
- // button = <ToggleButton {...buttonProps}></ToggleButton>
- break;
- case ButtonType.ClickButton:
- button = (
- <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1 }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
- {label}
- </div>
- );
- break;
- case ButtonType.MenuButton:
- const trailsIcon = <img src={`/assets/${"presTrails.png"}`}
- style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})` }} />;
- button = (
- <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
- {this.icon === "pres-trail" ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />}
- {menuLabel}
- <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} />
- </div >
- );
- break;
- default:
- break;
- }
+ } else {
+ return (
+ <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>
+ );
+ }
+ }
+
+
+
+ /**
+ * Default
+ */
+ @computed get defaultButton() {
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ const active: string = StrCast(this.rootDoc.dropDownOpen);
+ return (
+ <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu}
+ 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>}
+ </div>
+ </div>
+ );
+ }
+
+ @computed get editableText() {
+ // Script for running the toggle
+ const script = ScriptCast(this.rootDoc.script);
+ // Function to run the script
+ const checkResult = script?.script.run({ value: "", _readOnly_: true }).result;
+
+ const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result;
+ return (
+ <div className="menuButton editableText">
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"lock"} />
+ <div style={{ width: "calc(100% - .875em)", paddingLeft: "4px" }}>
+ <EditableView GetValue={() => script?.script.run({ value: "", _readOnly_: true }).result} SetValue={setValue} contents={checkResult} />
+ </div>
+ </div>
+ );
+ }
+
+ render() {
+ 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 }}>
+ {this.label}
+ </div>;
+
+ const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) :
+ <div className="fontIconBox-label" style={{ color, backgroundColor: "transparent" }}>
+ {this.label}
+ </div>;
+
+ const buttonText = StrCast(this.rootDoc.buttonText);
- 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>;
- }
+ // TODO:glr Add label of button type
+ let button: JSX.Element | null = this.defaultButton;
+
+ switch (this.type) {
+ case ButtonType.TextButton:
+ button = (
+ <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
+ <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />
+ {buttonText ?
+ <div className="button-text">
+ {buttonText}
+ </div>
+ : null}
+ {label}
+ </div>
+ );
+ // button = <TextButton {...buttonProps}></TextButton>
+ break;
+ case ButtonType.EditableText:
+ button = this.editableText;
+ break;
+ case ButtonType.NumberButton:
+ button = this.numberButton;
+ break;
+ case ButtonType.DropdownButton:
+ button = this.dropdownButton;
+ break;
+ case ButtonType.DropdownList:
+ button = this.dropdownListButton;
+ break;
+ case ButtonType.ColorButton:
+ button = this.colorButton;
+ break;
+ case ButtonType.ToolButton:
+ button = (
+ <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>
+ );
+ break;
+ case ButtonType.ToggleButton:
+ button = this.toggleButton;
+ // button = <ToggleButton {...buttonProps}></ToggleButton>
+ break;
+ case ButtonType.ClickButton:
+ button = (
+ <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>
+ );
+ break;
+ case ButtonType.MenuButton:
+ const trailsIcon = <img src={`/assets/${"presTrails.png"}`}
+ style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})` }} />;
+ button = (
+ <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
+ {this.icon === "pres-trail" ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />}
+ {menuLabel}
+ <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} />
+ </div >
+ );
+ break;
+ default:
+ break;
+ }
+
+ return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? button :
+ button !== null ?
+ <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
+ {button}
+ </Tooltip > : null
+ }
}
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setView(view: string) {
- const selected = SelectionManager.Docs().lastElement();
- selected ? selected._viewType = view : console.log("[FontIconBox.tsx] changeView failed");
+ const selected = SelectionManager.Docs().lastElement();
+ selected ? selected._viewType = view : console.log("[FontIconBox.tsx] changeView failed");
});
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
- if (checkResult) {
- return selected?._backgroundColor ?? "transparent";
- }
- if (selected) selected._backgroundColor = color;
+ const selected = SelectionManager.Docs().lastElement();
+ if (checkResult) {
+ return selected?._backgroundColor ?? "transparent";
+ }
+ if (selected) selected._backgroundColor = color;
});
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
- if (checkResult) {
- return Doc.SharingDoc().userColor;
- }
- Doc.SharingDoc().userColor = undefined;
- Doc.GetProto(Doc.SharingDoc()).userColor = color;
- Doc.UserDoc().showTitle = color === "transparent" ? undefined : StrCast(Doc.UserDoc().showTitle, "creationDate");
+ if (checkResult) {
+ return Doc.SharingDoc().userColor;
+ }
+ Doc.SharingDoc().userColor = undefined;
+ Doc.GetProto(Doc.SharingDoc()).userColor = color;
+ Doc.UserDoc().showTitle = color === "transparent" ? undefined : StrCast(Doc.UserDoc().showTitle, "creationDate");
});
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
- const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
- if (checkResult && selected) {
- if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE;
- return "transparent";
- }
- selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed");
+ const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
+ if (checkResult && selected) {
+ if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE;
+ return "transparent";
+ }
+ selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed");
});
/** TEXT
@@ -580,118 +588,200 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setFont(font: string, checkResult?: boolean) {
- SelectionManager.Docs().map(doc => doc._fontFamily = font);
- const editorView = RichTextMenu.Instance.TextView?.EditorView;
- if (checkResult) {
- return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
- }
- if (editorView) RichTextMenu.Instance.setFontFamily(font);
- else Doc.UserDoc().fontFamily = font;
+ SelectionManager.Docs().map(doc => doc._fontFamily = font);
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ if (checkResult) {
+ return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+ }
+ if (editorView) RichTextMenu.Instance.setFontFamily(font);
+ else Doc.UserDoc().fontFamily = font;
});
ScriptingGlobals.add(function getActiveTextInfo(info: "family" | "size" | "color" | "highlight") {
- const editorView = RichTextMenu.Instance.TextView?.EditorView;
- const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection();
- switch (info) {
- case "family": return style?.activeFamilies[0];
- case "size": return style?.activeSizes[0];
- case "color": return style?.activeColors[0];
- case "highlight": return style?.activeHighlights[0];
- }
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection();
+ switch (info) {
+ case "family": return style?.activeFamilies[0];
+ case "size": return style?.activeSizes[0];
+ case "color": return style?.activeColors[0];
+ case "highlight": return style?.activeHighlights[0];
+ }
});
ScriptingGlobals.add(function setAlignment(align: "left" | "right" | "center", checkResult?: boolean) {
- const editorView = RichTextMenu.Instance?.TextView?.EditorView;
- if (checkResult) {
- return (editorView ? RichTextMenu.Instance.textAlign : Doc.UserDoc().textAlign) === align ? Colors.MEDIUM_BLUE : "transparent";
- }
- if (editorView?.state) RichTextMenu.Instance.align(editorView, editorView.dispatch, align);
- else Doc.UserDoc().textAlign = align;
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ return (editorView ? RichTextMenu.Instance.textAlign : Doc.UserDoc().textAlign) === align ? Colors.MEDIUM_BLUE : "transparent";
+ }
+ if (editorView?.state) RichTextMenu.Instance.align(editorView, editorView.dispatch, align);
+ else Doc.UserDoc().textAlign = align;
});
ScriptingGlobals.add(function setBulletList(mapStyle: "bullet" | "decimal", checkResult?: boolean) {
- const editorView = RichTextMenu.Instance?.TextView?.EditorView;
- if (checkResult) {
- const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
- if (active === mapStyle) return Colors.MEDIUM_BLUE;
- return "transparent";
- }
- if (editorView) {
- const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
- editorView?.state && RichTextMenu.Instance.changeListType(
- editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }));
- }
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
+ if (active === mapStyle) return Colors.MEDIUM_BLUE;
+ return "transparent";
+ }
+ if (editorView) {
+ const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
+ editorView?.state && RichTextMenu.Instance.changeListType(
+ editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }));
+ }
});
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setFontColor(color?: string, checkResult?: boolean) {
- const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
- if (checkResult) {
- return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor;
- }
+ if (checkResult) {
+ return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor;
+ }
- if (editorView) color && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch);
- else Doc.UserDoc().fontColor = color;
+ if (editorView) color && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch);
+ else Doc.UserDoc().fontColor = color;
});
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
- const editorView = RichTextMenu.Instance.TextView?.EditorView;
-
- if (checkResult) {
- return (selected ?? Doc.UserDoc())._fontHighlight;
- }
- if (selected) {
- selected._fontColor = color;
- if (color) {
- editorView?.state && RichTextMenu.Instance.setHighlight(color, editorView, editorView?.dispatch);
- }
- }
- Doc.UserDoc()._fontHighlight = color;
+ const selected = SelectionManager.Docs().lastElement();
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+
+ if (checkResult) {
+ return (selected ?? Doc.UserDoc())._fontHighlight;
+ }
+ if (selected) {
+ selected._fontColor = color;
+ if (color) {
+ editorView?.state && RichTextMenu.Instance.setHighlight(color, editorView, editorView?.dispatch);
+ }
+ }
+ Doc.UserDoc()._fontHighlight = color;
});
// toggle: Set overlay status of selected document
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", "");
- }
- 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;
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ 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;
- if (checkResult) {
- return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === "bold") ? Colors.MEDIUM_BLUE : "transparent";
- }
- if (editorView) RichTextMenu.Instance?.toggleBold();
- else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === "bold" ? undefined : "bold";
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === "bold") ? Colors.MEDIUM_BLUE : "transparent";
+ }
+ if (editorView) RichTextMenu.Instance?.toggleBold();
+ else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === "bold" ? undefined : "bold";
});
ScriptingGlobals.add(function toggleUnderline(checkResult?: boolean) {
- const editorView = RichTextMenu.Instance?.TextView?.EditorView;
- if (checkResult) {
- return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === "underline") ? Colors.MEDIUM_BLUE : "transparent";
- }
- if (editorView) RichTextMenu.Instance?.toggleUnderline();
- else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === "underline" ? undefined : "underline";
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === "underline") ? Colors.MEDIUM_BLUE : "transparent";
+ }
+ if (editorView) RichTextMenu.Instance?.toggleUnderline();
+ else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === "underline" ? undefined : "underline";
});
ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
- const editorView = RichTextMenu.Instance?.TextView?.EditorView;
- if (checkResult) {
- return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === "italics") ? Colors.MEDIUM_BLUE : "transparent";
- }
- if (editorView) RichTextMenu.Instance?.toggleItalics();
- else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === "italics" ? undefined : "italics";
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+ if (checkResult) {
+ return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === "italics") ? Colors.MEDIUM_BLUE : "transparent";
+ }
+ if (editorView) RichTextMenu.Instance?.toggleItalics();
+ else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === "italics" ? undefined : "italics";
});
+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
@@ -701,66 +791,71 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
**/
ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boolean) {
- if (checkResult) {
- return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ?
- Colors.MEDIUM_BLUE : "transparent";
- }
- if (["circle", "square", "line"].includes(tool)) {
- if (GestureOverlay.Instance.InkShape === tool) {
- Doc.UserDoc().activeInkTool = InkTool.None;
- GestureOverlay.Instance.InkShape = InkTool.None;
- } else {
- Doc.UserDoc().activeInkTool = InkTool.Pen;
- GestureOverlay.Instance.InkShape = tool;
- }
- } else if (tool) { // pen or eraser
- if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) {
- Doc.UserDoc().activeInkTool = InkTool.None;
- } else {
- Doc.UserDoc().activeInkTool = tool;
- GestureOverlay.Instance.InkShape = "";
- }
- } else {
+ InkTranscription.Instance?.createInkGroup();
+ if (checkResult) {
+ return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ?
+ Colors.MEDIUM_BLUE : "transparent";
+ }
+ if (["circle", "square", "line"].includes(tool)) {
+ if (GestureOverlay.Instance.InkShape === tool) {
Doc.UserDoc().activeInkTool = InkTool.None;
- }
+ GestureOverlay.Instance.InkShape = InkTool.None;
+ } else {
+ Doc.UserDoc().activeInkTool = InkTool.Pen;
+ GestureOverlay.Instance.InkShape = tool;
+ }
+ } 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 = "";
+ }
+ } else {
+ Doc.UserDoc().activeInkTool = InkTool.None;
+ }
});
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
- if (checkResult) {
- if (selected?.type === DocumentType.INK) {
- return StrCast(selected.fillColor);
- }
- return ActiveFillColor();
- }
- SetActiveFillColor(StrCast(color));
- SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.fillColor = color);
+ const selected = SelectionManager.Docs().lastElement();
+ if (checkResult) {
+ if (selected?.type === DocumentType.INK) {
+ return StrCast(selected.fillColor);
+ }
+ return ActiveFillColor();
+ }
+ SetActiveFillColor(StrCast(color));
+ SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.fillColor = color);
});
ScriptingGlobals.add(function setStrokeWidth(width: number, checkResult?: boolean) {
- if (checkResult) {
- const selected = SelectionManager.Docs().lastElement();
- if (selected?.type === DocumentType.INK) {
- return NumCast(selected.strokeWidth);
- }
- return ActiveInkWidth();
- }
- SetActiveInkWidth(width.toString());
- SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.strokeWidth = Number(width));
+ if (checkResult) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (selected?.type === DocumentType.INK) {
+ return NumCast(selected.strokeWidth);
+ }
+ return ActiveInkWidth();
+ }
+ SetActiveInkWidth(width.toString());
+ SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.strokeWidth = Number(width));
});
// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setStrokeColor(color?: string, checkResult?: boolean) {
- if (checkResult) {
- const selected = SelectionManager.Docs().lastElement();
- if (selected?.type === DocumentType.INK) {
- return StrCast(selected.color);
- }
- return ActiveInkColor();
- }
- SetActiveInkColor(StrCast(color));
- SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.color = String(color));
+ if (checkResult) {
+ const selected = SelectionManager.Docs().lastElement();
+ if (selected?.type === DocumentType.INK) {
+ return StrCast(selected.color);
+ }
+ return ActiveInkColor();
+ }
+ SetActiveInkColor(StrCast(color));
+ SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.color = String(color));
});
@@ -768,28 +863,28 @@ ScriptingGlobals.add(function setStrokeColor(color?: string, checkResult?: boole
* webSetURL
**/
ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
- const selected = SelectionManager.Views().lastElement();
- if (selected?.rootDoc.type === DocumentType.WEB) {
- if (checkResult) {
- return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href);
- }
- (selected.ComponentView as WebBox).submitURL(url);
- //selected.rootDoc.data = new WebField(url);
- }
+ const selected = SelectionManager.Views().lastElement();
+ if (selected?.rootDoc.type === DocumentType.WEB) {
+ if (checkResult) {
+ return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href);
+ }
+ (selected.ComponentView as WebBox).submitURL(url);
+ //selected.rootDoc.data = new WebField(url);
+ }
});
ScriptingGlobals.add(function webForward(checkResult?: boolean) {
- const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox);
- if (checkResult) {
- return selected?.forward(checkResult) ? undefined : "lightGray";
- }
- selected?.forward();
+ const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox);
+ if (checkResult) {
+ return selected?.forward(checkResult) ? undefined : "lightGray";
+ }
+ selected?.forward();
});
ScriptingGlobals.add(function webBack(checkResult?: boolean) {
- const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox);
- if (checkResult) {
- return selected?.back(checkResult) ? undefined : "lightGray";
- }
- selected?.back();
+ const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox);
+ if (checkResult) {
+ return selected?.back(checkResult) ? undefined : "lightGray";
+ }
+ selected?.back();
});
@@ -797,30 +892,30 @@ ScriptingGlobals.add(function webBack(checkResult?: boolean) {
* toggleSchemaPreview
**/
ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
- const selected = SelectionManager.Docs().lastElement();
- if (checkResult && selected) {
- const result: boolean = NumCast(selected.schemaPreviewWidth) > 0;
- if (result) return Colors.MEDIUM_BLUE;
- else return "transparent";
- }
- else if (selected) {
- if (NumCast(selected.schemaPreviewWidth) > 0) {
- selected.schemaPreviewWidth = 200;
- } else {
- selected.schemaPreviewWidth = 0;
- }
- }
+ const selected = SelectionManager.Docs().lastElement();
+ if (checkResult && selected) {
+ const result: boolean = NumCast(selected.schemaPreviewWidth) > 0;
+ if (result) return Colors.MEDIUM_BLUE;
+ else return "transparent";
+ }
+ else if (selected) {
+ if (NumCast(selected.schemaPreviewWidth) > 0) {
+ selected.schemaPreviewWidth = 200;
+ } else {
+ selected.schemaPreviewWidth = 0;
+ }
+ }
});
/** STACK
* groupBy
*/
ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
- SelectionManager.Docs().map(doc => doc._fontFamily = key);
- const editorView = RichTextMenu.Instance.TextView?.EditorView;
- if (checkResult) {
- return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
- }
- if (editorView) RichTextMenu.Instance.setFontFamily(key);
- else Doc.UserDoc().fontFamily = key;
+ SelectionManager.Docs().map(doc => doc._fontFamily = key);
+ const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ if (checkResult) {
+ return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+ }
+ if (editorView) RichTextMenu.Instance.setFontFamily(key);
+ else Doc.UserDoc().fontFamily = key;
}); \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 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..7d0302b26 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();
@@ -1391,9 +1411,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos);
if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
if (!highlightOnly) {
- if (selectOrderedList || (!collapse && listNode.attrs.visibility)) {
+ if (selectOrderedList) {
this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
- } else if (!listNode.attrs.visibility || downNode === listNode) {
+ } else {
const tr = this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos)));
}
@@ -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;
@@ -1487,9 +1504,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._rules!.EnteringStyle = false;
}
e.stopPropagation();
- for (var i = state.selection.from; i < state.selection.to; i++) {
+ for (var i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
- if (node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
+ if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
mark.attrs.userid !== Doc.CurrentUserEmail) &&
[AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
e.preventDefault();
@@ -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..fe7b5c8c4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -10,17 +10,16 @@ import { InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
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";
import { DocumentManager } from "../../../util/DocumentManager";
-import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
import { SelectionManager } from "../../../util/SelectionManager";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
import { CollectionDockingView } from "../../collections/CollectionDockingView";
+import { MarqueeViewBounds } from "../../collections/collectionFreeForm";
import { CollectionView, CollectionViewType } from "../../collections/CollectionView";
import { TabDocView } from "../../collections/TabDocView";
import { ViewBoxBaseComponent } from "../../DocComponent";
@@ -31,11 +30,16 @@ import { FieldView, FieldViewProps } from '../FieldView';
import "./PresBox.scss";
import { PresEffect, PresMovement, PresStatus } from "./PresEnums";
-export class PinProps {
+export interface PinProps {
audioRange?: boolean;
- unpin?: boolean;
setPosition?: boolean;
hidePresBox?: boolean;
+ pinWithView?: PinViewProps;
+}
+
+export interface PinViewProps {
+ bounds: MarqueeViewBounds;
+ scale: number;
}
@observer
@@ -47,17 +51,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 +75,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 +94,20 @@ 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 isTreeOrStack() {return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any) }
+ @computed get isTree() { return this.layoutDoc._viewType === CollectionViewType.Tree;}
+ @computed get presFieldKey() { return StrCast(this.layoutDoc.presFieldKey, "data"); }
+ @computed get childDocs() { return DocListCast(this.rootDoc[this.presFieldKey]); }
+ @observable _treeViewMap: Map<Doc, number> = new Map();
+
@computed get tagDocs() {
const tagDocs: Doc[] = [];
for (const doc of this.childDocs) {
@@ -124,14 +135,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"
}));
- // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement
- // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent
- // the preselement docs from being part of multiple presentations since they would all have the same field, or we'd have to keep per-presentation data
- // stored on each pres element.
- (this.presElement as Doc).lookupField = ScriptField.MakeFunction("lookupPresBoxField(container, field, data)",
- { 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];
@@ -148,8 +154,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ _unmounting = false;
@action
componentWillUnmount() {
+ this._unmounting = true;
document.removeEventListener("keydown", PresBox.keyEventsWrapper, true);
this._presKeyEventsActive = false;
this.resetPresentation();
@@ -160,7 +168,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
componentDidMount() {
- this.rootDoc.presBox = this.rootDoc;
+ this._unmounting = false;
this.rootDoc._forceRenderEngine = "timeline";
this.layoutDoc.presStatus = PresStatus.Edit;
this.layoutDoc._gridGap = 0;
@@ -212,6 +220,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 +306,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 +318,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
}
});
@@ -323,8 +333,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
bestTarget._scrollTop = activeItem.presPinViewScroll;
} else if (bestTarget.type === DocumentType.COMPARISON) {
bestTarget._clipWidth = activeItem.presPinClipWidth;
- } else if (bestTarget.type === DocumentType.VID) {
- bestTarget._currentTimecode = activeItem.presPinTimecode;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) {
+ bestTarget._currentTimecode = activeItem.presStartTime;
} else {
bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
bestTarget._panX = activeItem.presPinViewX;
@@ -346,7 +356,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 +384,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,17 +397,20 @@ 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 ? [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 ? [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.
if (activeItem.presPinView) {
+ console.log(targetDoc.title);
+ console.log("presPinView in PresBox.tsx:420");
// if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target
this.navigateToView(targetDoc, activeItem);
}
@@ -587,27 +600,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);
}
}
@@ -615,14 +622,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* Called when the user changes the view type
* Either 'List' (stacking) or 'Slides' (carousel)
*/
- // @undoBatch
+ @undoBatch
viewChanged = action((e: React.ChangeEvent) => {
//@ts-ignore
const viewType = e.target.selectedOptions[0].value as CollectionViewType;
+ this.layoutDoc.presFieldKey = this.fieldKey+(viewType === CollectionViewType.Tree ?"-linearized":"");
// pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
[CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType) && (this.rootDoc._pivotField = undefined);
this.rootDoc._viewType = viewType;
- if ([CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType)) this.layoutDoc._gridGap = 0;
+ if (this.isTreeOrStack) {
+ this.layoutDoc._gridGap = 0;
+ }
});
/**
@@ -696,11 +706,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
return true;
}
- childLayoutTemplate = () => ![CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.rootDoc._viewType as any) ? undefined : this.presElement;
+ childLayoutTemplate = () => !this.isTreeOrStack ? undefined : this.presElement;
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)
/**
@@ -795,13 +805,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
//regular click
@action
- regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean) => {
+ regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, selectPres = true) => {
this._selectedArray.clear();
this._selectedArray.set(doc, undefined);
this._eleArray.splice(0, this._eleArray.length, ref);
this._dragArray.splice(0, this._dragArray.length, drag);
focus && this.selectElement(doc);
- this.selectPres();
+ selectPres && this.selectPres();
}
modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, cmdClick: boolean, shiftClick: boolean) => {
@@ -1089,7 +1099,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 +1125,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 +1162,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 +1214,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 +1254,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 +1286,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 +1301,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 +1320,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 +1336,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);
@@ -1355,8 +1367,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const scroll = targetDoc._scrollTop;
activeItem.presPinView = true;
activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
+ 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;
@@ -1377,8 +1390,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
const scroll = targetDoc._scrollTop;
activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
+ 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 +1422,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 +1432,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 +1442,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 +1463,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 +1510,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 +1529,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 +2423,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)));
+ }
+ if (this._treeViewMap.get(treeViewDoc) !== indexNum) {
+ this._treeViewMap.set(treeViewDoc, indexNum);
+ const sorted = this.sort(this._treeViewMap);
+ const curList = DocListCast(this.dataDoc[this.presFieldKey]);
+ if (sorted.length !== curList.length || sorted.some((doc,ind) => doc !== curList[ind])) {
+ this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
+ }
+ }
+ return this.childDocs;
+ }
+
+ RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+ if (!this._unmounting && this.isTree) {
+ this._treeViewMap.delete(treeViewDoc);
+ this.dataDoc[this.presFieldKey] = 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<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0).map(kv => kv[0]);
+
render() {
// calling this method for keyEvents
this.isPres;
@@ -2413,56 +2490,67 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1);
const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0);
return CurrentUserUtils.OverlayDocs.includes(this.rootDoc) ?
- <div className="miniPres">
+ <div className="miniPres" onClick={e => e.stopPropagation()}>
<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}
+ scriptContext={this}
+ ScreenToLocalTransform={this.getTransform}
+ 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;
- return undefined;
-}); \ No newline at end of file
+} \ 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..969f034a8 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -5,155 +5,157 @@ $slide-background: #d5dce2;
$slide-active: #5B9FDD;
.presItem-container {
- cursor: grab;
- display: grid;
- grid-template-columns: 20px auto;
- font-family: Roboto;
- letter-spacing: normal;
- position: relative;
- pointer-events: all;
- width: 100%;
- height: 100%;
- font-weight: 400;
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- align-items: center;
+ cursor: grab;
+ display: flex;
+ grid-template-columns: 20px auto;
+ font-family: Roboto;
+ letter-spacing: normal;
+ position: relative;
+ pointer-events: all;
+ width: 100%;
+ height: 100%;
+ font-weight: 400;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ align-items: center;
- .presItem-number {
- margin-top: 3.5px;
- font-size: 12px;
- font-weight: 700;
- text-align: center;
- justify-self: center;
- align-self: flex-start;
- position: relative;
- display: inline-block;
- overflow: hidden;
- }
+ // .presItem-number {
+ // margin-top: 3.5px;
+ // font-size: 12px;
+ // font-weight: 700;
+ // text-align: center;
+ // justify-self: center;
+ // align-self: flex-start;
+ // position: relative;
+ // display: inline-block;
+ // overflow: hidden;
+ // }
}
.presItem-slide {
- position: relative;
- background-color: #d5dce2;
- border-radius: 5px;
- height: calc(100% - 7px);
- width: 100%;
- display: grid;
- grid-template-rows: 16px 10px auto;
- grid-template-columns: max-content max-content max-content max-content auto;
+ position: relative;
+ height: 100%;
+ width: 100%;
+ border-bottom: .5px solid grey;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ grid-template-rows: 16px 10px auto;
+ grid-template-columns: max-content max-content max-content max-content auto;
- .presItem-name {
- min-width: 20px;
- z-index: 300;
- top: 2px;
- align-self: center;
- font-size: 11px;
- font-family: Roboto;
- font-weight: 500;
- position: relative;
- padding-left: 10px;
- padding-right: 10px;
- letter-spacing: normal;
- width: max-content;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: pre;
- }
-
- .presItem-docName {
- min-width: 20px;
- z-index: 300;
- align-self: center;
- font-size: 9px;
- font-family: Roboto;
- font-weight: 300;
- position: relative;
- padding-left: 10px;
- padding-right: 10px;
- letter-spacing: normal;
- width: max-content;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: pre;
- grid-row: 2;
- grid-column: 1/6;
- }
-
- .presItem-time {
- align-self: center;
- position: relative;
- padding-right: 10px;
- top: 1px;
- font-size: 10;
- font-weight: 300;
- font-family: Roboto;
- z-index: 300;
- letter-spacing: normal;
- }
-
- .presItem-embedded {
- overflow: hidden;
- grid-row: 3;
- grid-column: 1/8;
- position: relative;
- display: flex;
- width: auto;
- justify-content: center;
- margin: auto;
- margin-bottom: 2px;
- border-bottom-left-radius: 5px;
- border-bottom-right-radius: 5px;
- }
-
- .presItem-embeddedMask {
- width: 100%;
- height: 100%;
- position: absolute;
- border-radius: 3px;
- top: 0;
- left: 0;
- z-index: 1;
- overflow: hidden;
- }
-
-
- .presItem-slideButtons {
- display: flex;
- grid-column: 7;
- grid-row: 1/3;
- width: max-content;
- justify-self: right;
- justify-content: flex-end;
-
- .slideButton {
- cursor: pointer;
+ .presItem-name {
+ display: flex;
+ min-width: 20px;
+ z-index: 300;
+ top: 2px;
+ align-self: center;
+ font-size: 11px;
+ font-family: Roboto;
+ font-weight: 500;
position: relative;
- border-radius: 100px;
+ padding-left: 10px;
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: max-content;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: pre;
+ }
+
+ .presItem-docName {
+ min-width: 20px;
z-index: 300;
- width: 18px;
- height: 18px;
- display: flex;
- font-size: 12px;
- justify-self: center;
align-self: center;
- background-color: rgba(0, 0, 0, 0.5);
- color: white;
+ font-size: 9px;
+ font-family: Roboto;
+ font-weight: 300;
+ position: relative;
+ padding-left: 10px;
+ padding-right: 10px;
+ letter-spacing: normal;
+ width: max-content;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: pre;
+ grid-row: 2;
+ grid-column: 1/6;
+ }
+
+ .presItem-time {
+ align-self: center;
+ position: relative;
+ padding-right: 10px;
+ top: 1px;
+ font-size: 10;
+ font-weight: 300;
+ font-family: Roboto;
+ z-index: 300;
+ letter-spacing: normal;
+ }
+
+ .presItem-embedded {
+ overflow: hidden;
+ grid-row: 3;
+ grid-column: 1/8;
+ position: relative;
+ display: flex;
+ width: auto;
justify-content: center;
- align-items: center;
- transition: 0.2s;
- margin-right: 3px;
- }
-
- .slideButton:hover {
- background-color: rgba(0, 0, 0, 1);
- transform: scale(1.2);
- }
- }
+ margin: auto;
+ margin-bottom: 2px;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+ }
+
+ .presItem-embeddedMask {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ border-radius: 3px;
+ top: 0;
+ left: 0;
+ z-index: 1;
+ overflow: hidden;
+ }
+
+
+ .presItem-slideButtons {
+ display: flex;
+ grid-column: 7;
+ grid-row: 1/3;
+ width: max-content;
+ justify-self: right;
+ justify-content: flex-end;
+
+ .slideButton {
+ cursor: pointer;
+ position: relative;
+ border-radius: 100px;
+ z-index: 300;
+ width: 18px;
+ height: 18px;
+ display: flex;
+ font-size: 12px;
+ justify-self: center;
+ align-self: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ color: white;
+ justify-content: center;
+ align-items: center;
+ transition: 0.2s;
+ margin-right: 3px;
+ }
+
+ .slideButton:hover {
+ background-color: rgba(0, 0, 0, 1);
+ transform: scale(1.2);
+ }
+ }
}
// .presItem-slide:hover {
@@ -192,44 +194,116 @@ $slide-active: #5B9FDD;
// }
.presItem-slide.active {
- box-shadow: 0 0 0px 1.5px $dark-blue;
+ box-shadow: 0 0 0px 2.5px $dark-blue;
}
-.presItem-multiDrag {
- font-family: Roboto;
- font-weight: 600;
- color: white;
- text-align: center;
- justify-content: center;
- align-content: center;
- width: 100px;
- height: 30px;
+.presItem-slide.group {
+ border-radius: 5px;
+}
+
+.presItem-slide.activeGroup {
+ border-radius: 5px;
+ box-shadow: 0 0 0px 2.5px $dark-blue;
+}
+
+.presItem-groupSlideContainer {
position: absolute;
- background-color: $dark-blue;
- z-index: 4000;
- border-radius: 10px;
- box-shadow: black 0.4vw 0.4vw 0.8vw;
- line-height: 30px;
+ /* 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-miniSlide {
- font-weight: 700;
- font-size: 12;
- grid-column: 1/8;
- align-self: center;
- justify-self: center;
+.presItem-groupSlide {
+ position: relative;
background-color: #d5dce2;
- width: 26px;
- text-align: center;
- height: 26px;
- line-height: 28px;
- border-radius: 100%;
+ 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;
+ color: white;
+ text-align: center;
+ justify-content: center;
+ align-content: center;
+ width: 100px;
+ height: 30px;
+ position: absolute;
+ background-color: $dark-blue;
+ z-index: 4000;
+ border-radius: 10px;
+ box-shadow: black 0.4vw 0.4vw 0.8vw;
+ line-height: 30px;
+}
+
+.presItem-miniSlide {
+ font-weight: 700;
+ font-size: 12;
+ grid-column: 1/8;
+ align-self: center;
+ justify-self: center;
+ background-color: #d5dce2;
+ width: 26px;
+ text-align: center;
+ height: 26px;
+ line-height: 28px;
+ border-radius: 100%;
}
.presItem-miniSlide.active {
- box-shadow: 0 0 0px 1.5px $dark-blue;
+ 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 6725307db..fba7e7af7 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction } from "mobx"
import { observer } from "mobx-react";
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 { Docs, DocUtils } from "../../../documents/Documents";
import { DocumentType } from "../../../documents/DocumentTypes";
@@ -34,244 +34,279 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
_heightDisposer: IReactionDisposer | undefined;
- @observable _dragging = false;
- // these fields are conditionally computed fields on the layout document that take this document as a parameter
- @computed get indexInPres() { return Number(this.lookupField("indexInPres")); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
- @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
- @computed get presStatus() { return StrCast(this.lookupField("presStatus")); }
- @computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); }
- @computed get presBox() { return Cast(this.lookupField("presBox"), Doc, null); }
- @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
-
-
- @observable isShowingVideo = false;
- @action setIsShowingVideo(shown: boolean) {
- this.isShowingVideo = shown
- }
-
- componentDidMount() {
- this.layoutDoc.hideLinkButton = true;
- this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
- params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
- }
- componentWillUnmount() {
- this._heightDisposer?.();
- }
-
- /**
- * Returns a local transformed coordinate array for given coordinates.
- */
- ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
-
- embedHeight = (): number => 97;
- // embedWidth = () => this.props.PanelWidth();
- // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
- embedWidth = (): number => this.props.PanelWidth() - 35;
- styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
- if (property === StyleProp.Opacity) return 1;
- return this.props.styleProvider?.(doc, props, property);
- }
- /**
- * The function that is responsible for rendering a preview or not for this
- * presentation element.
- */
- @computed get renderEmbeddedInline() {
- return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) :
- <div className="presItem-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
- <DocumentView
- 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}
- removeDocument={returnFalse}
- isContentActive={this.props.isContentActive}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- fitContentsToDoc={returnTrue}
- PanelWidth={this.embedWidth}
- PanelHeight={this.embedHeight}
- ScreenToLocalTransform={Transform.Identity}
- moveDocument={this.props.moveDocument!}
- renderDepth={this.props.renderDepth + 1}
- focus={DocUtils.DefaultFocus}
- whenChildContentsActiveChanged={returnFalse}
- bringToFront={returnFalse}
- docFilters={this.props.docFilters}
- docRangeFilters={this.props.docRangeFilters}
- searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- hideLinkButton={true}
- />
- <div className="presItem-embeddedMask" />
- </div>;
- }
- @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; }
- else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000;
- else durationInS = 2;
- return "D: " + durationInS + "s";
- }
-
- @computed get transition() {
- let transitionInS: number;
- if (this.rootDoc.presTransition) transitionInS = NumCast(this.rootDoc.presTransition) / 1000;
- else transitionInS = 0.5;
- return this.rootDoc.presMovement === PresMovement.Jump || this.rootDoc.presMovement === PresMovement.None ? (null) : "M: " + transitionInS + "s";
- }
-
- private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
- private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
- private _titleRef: React.RefObject<EditableView> = React.createRef();
-
-
- @action
- headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
- const element = e.target as any;
- e.stopPropagation();
- e.preventDefault();
- if (element && !(e.ctrlKey || e.metaKey)) {
- if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
- PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
- setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
- } else {
- setupMoveUpEvents(this, e, ((e: PointerEvent) => {
- PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
- return this.startDrag(e);
- }), emptyFunction, emptyFunction);
- }
- }
- }
-
- headerUp = (e: React.PointerEvent<HTMLDivElement>) => {
- e.stopPropagation();
- e.preventDefault();
- }
-
- /**
- * Function to drag and drop the pres element to a diferent location
- */
- startDrag = (e: PointerEvent) => {
- const miniView: boolean = this.toolbarWidth <= 100;
- const activeItem = this.rootDoc;
- const dragArray = PresBox.Instance._dragArray;
- const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray());
- if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
- dragData.dropAction = "move";
- dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc;
- dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument;
- const dragItem: HTMLElement[] = [];
- if (dragArray.length === 1) {
- const doc = dragArray[0];
- doc.className = miniView ? "presItem-miniSlide" : "presItem-slide";
- dragItem.push(doc);
- } else if (dragArray.length >= 1) {
- const doc = document.createElement('div');
- doc.className = "presItem-multiDrag";
- doc.innerText = "Move " + PresBox.Instance._selectedArray.size + " slides";
- doc.style.position = 'absolute';
- doc.style.top = (e.clientY) + 'px';
- doc.style.left = (e.clientX - 50) + 'px';
- dragItem.push(doc);
- }
-
- // const dropEvent = () => runInAction(() => this._dragging = false);
- if (activeItem) {
- DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined);
- // runInAction(() => this._dragging = true);
- return true;
- }
- return false;
- }
-
- onPointerOver = (e: any) => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- }
-
- onPointerMove = (e: PointerEvent) => {
- const slide = this._itemRef.current!;
- let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false;
- for (const doc of DragManager.docsBeingDragged) {
- if (!doc.presentationTargetDoc) dragIsPresItem = false;
- }
- if (slide && dragIsPresItem) {
- const rect = slide.getBoundingClientRect();
- const y = e.clientY - rect.top; //y position within the element.
- const height = slide.clientHeight;
- const halfLine = height / 2;
- if (y <= halfLine) {
- slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
- slide.style.borderBottom = "0px";
- } else if (y > halfLine) {
- slide.style.borderTop = "0px";
- slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
- }
- }
- document.removeEventListener("pointermove", this.onPointerMove);
- }
-
- onPointerLeave = (e: any) => {
- this._itemRef.current!.style.borderTop = "0px";
- this._itemRef.current!.style.borderBottom = "0px";
- document.removeEventListener("pointermove", this.onPointerMove);
- }
-
- @action
- toggleProperties = () => {
- if (CurrentUserUtils.propertiesWidth < 5) {
- action(() => (CurrentUserUtils.propertiesWidth = 250));
- }
- }
-
- @undoBatch
- removeItem = action((e: React.MouseEvent) => {
- this.props.removeDocument?.(this.rootDoc);
+ @observable _dragging = false;
+ @computed get indexInPres() { return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, "data")]).indexOf(this.rootDoc); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
+ @computed get collapsedHeight() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(this.presBox._viewType as any) ? 35 : 31; } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
+ @computed get presStatus() { return this.presBox.presStatus; }
+ @computed get presBox() { return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!; }
+ @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
+
+ componentDidMount() {
+ this.layoutDoc.hideLinkButton = true;
+ this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
+ params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
+ }
+ componentWillUnmount() {
+ this._heightDisposer?.();
+ }
+
+ /**
+ * Returns a local transformed coordinate array for given coordinates.
+ */
+ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
+
+ @action
+ presExpandDocumentClick = () => {
+ this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton;
+ }
+
+ embedHeight = (): number => 97;
+ // embedWidth = () => this.props.PanelWidth();
+ // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
+ embedWidth = (): number => this.props.PanelWidth() - 35;
+ styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+ if (property === StyleProp.Opacity) return 1;
+ return this.props.styleProvider?.(doc, props, property);
+ }
+ /**
+ * The function that is responsible for rendering a preview or not for this
+ * presentation element.
+ */
+ @computed get renderEmbeddedInline() {
+ return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) :
+ <div className="presItem-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
+ <DocumentView
+ Document={this.targetDoc}
+ DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
+ styleProvider={this.styleProvider}
+ docViewPath={returnEmptyDoclist}
+ rootSelected={returnTrue}
+ addDocument={returnFalse}
+ removeDocument={returnFalse}
+ isContentActive={this.props.isContentActive}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ fitContentsToDoc={returnTrue}
+ PanelWidth={this.embedWidth}
+ PanelHeight={this.embedHeight}
+ ScreenToLocalTransform={Transform.Identity}
+ moveDocument={this.props.moveDocument!}
+ renderDepth={this.props.renderDepth + 1}
+ focus={DocUtils.DefaultFocus}
+ whenChildContentsActiveChanged={returnFalse}
+ bringToFront={returnFalse}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ hideLinkButton={true}
+ />
+ <div className="presItem-embeddedMask" />
+ </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; }
+ else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000;
+ else durationInS = 2;
+ return "D: " + durationInS + "s";
+ }
+
+ @computed get transition() {
+ let transitionInS: number;
+ if (this.rootDoc.presTransition) transitionInS = NumCast(this.rootDoc.presTransition) / 1000;
+ else transitionInS = 0.5;
+ return this.rootDoc.presMovement === PresMovement.Jump || this.rootDoc.presMovement === PresMovement.None ? (null) : "M: " + transitionInS + "s";
+ }
+
+ private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _titleRef: React.RefObject<EditableView> = React.createRef();
+
+
+ @action
+ headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+ const element = e.target as any;
+ e.stopPropagation();
+ e.preventDefault();
+ if (element && !(e.ctrlKey || e.metaKey)) {
if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
- PresBox.Instance._selectedArray.delete(this.rootDoc);
+ PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
+ setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
+ } else {
+ setupMoveUpEvents(this, e, ((e: PointerEvent) => {
+ PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
+ return this.startDrag(e);
+ }), emptyFunction, emptyFunction);
}
- this.hideRecording();
- e.stopPropagation();
- });
+ }
+ }
+
+ headerUp = (e: React.PointerEvent<HTMLDivElement>) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
- // set the value/title of the individual pres element
- @undoBatch
- @action
- onSetValue = (value: string) => {
- this.rootDoc.title = !value.trim().length ? "-untitled-" : value;
- return true;
- }
/**
- * Method called for updating the view of the currently selected document
- *
- * @param targetDoc
- * @param activeItem
+ * Function to drag and drop the pres element to a diferent location
*/
- @undoBatch
- @action
- updateView = (targetDoc: Doc, activeItem: Doc) => {
- if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
- const scroll = targetDoc._scrollTop;
- activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const clipWidth = targetDoc._clipWidth;
- activeItem.presPinClipWidth = clipWidth;
- } else {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeItem.presPinViewX = x;
- activeItem.presPinViewY = y;
- activeItem.presPinViewScale = scale;
+ startDrag = (e: PointerEvent) => {
+ const miniView: boolean = this.toolbarWidth <= 100;
+ const activeItem = this.rootDoc;
+ const dragArray = PresBox.Instance._dragArray;
+ const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray());
+ if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
+ dragData.dropAction = "move";
+ dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc;
+ dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument;
+ const dragItem: HTMLElement[] = [];
+ if (dragArray.length === 1) {
+ const doc = this._itemRef.current || dragArray[0];
+ doc.className = miniView ? "presItem-miniSlide" : "presItem-slide";
+ dragItem.push(doc);
+ } else if (dragArray.length >= 1) {
+ const doc = document.createElement('div');
+ doc.className = "presItem-multiDrag";
+ doc.innerText = "Move " + PresBox.Instance._selectedArray.size + " slides";
+ doc.style.position = 'absolute';
+ doc.style.top = (e.clientY) + 'px';
+ doc.style.left = (e.clientX - 50) + 'px';
+ dragItem.push(doc);
+ }
+
+ // const dropEvent = () => runInAction(() => this._dragging = false);
+ if (activeItem) {
+ DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined);
+ // runInAction(() => this._dragging = true);
+ return true;
+ }
+ return false;
+ }
+
+ onPointerOver = (e: any) => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ }
+
+ onPointerMove = (e: PointerEvent) => {
+ const slide = this._itemRef.current!;
+ let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false;
+ for (const doc of DragManager.docsBeingDragged) {
+ if (!doc.presentationTargetDoc) dragIsPresItem = false;
+ }
+ if (slide && dragIsPresItem) {
+ const rect = slide.getBoundingClientRect();
+ const y = e.clientY - rect.top; //y position within the element.
+ const height = slide.clientHeight;
+ const halfLine = height / 2;
+ if (y <= halfLine) {
+ slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ slide.style.borderBottom = "0px";
+ } else if (y > halfLine) {
+ slide.style.borderTop = "0px";
+ slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
}
- }
+ }
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+
+ onPointerLeave = (e: any) => {
+ this._itemRef.current!.style.borderTop = "0px";
+ this._itemRef.current!.style.borderBottom = "0px";
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+
+ @action
+ toggleProperties = () => {
+ if (CurrentUserUtils.propertiesWidth < 5) {
+ action(() => (CurrentUserUtils.propertiesWidth = 250));
+ }
+ }
+
+ @undoBatch
+ removeItem = action((e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.props.removeDocument?.(this.rootDoc);
+ if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
+ PresBox.Instance._selectedArray.delete(this.rootDoc);
+ }
+ this.removeAllRecordingInOverlay()
+ });
+
+ // set the value/title of the individual pres element
+ @undoBatch
+ @action
+ onSetValue = (value: string) => {
+ this.rootDoc.title = !value.trim().length ? "-untitled-" : value;
+ return true;
+ }
+
+ /**
+ * Method called for updating the view of the currently selected document
+ *
+ * @param targetDoc
+ * @param activeItem
+ */
+ @undoBatch
+ @action
+ updateView = (targetDoc: Doc, activeItem: Doc) => {
+ switch (targetDoc.type) {
+ case DocumentType.PDF: case DocumentType.WEB: case DocumentType.RTF :
+ const scroll = targetDoc._scrollTop;
+ activeItem.presPinViewScroll = scroll;
+ break;
+ case DocumentType.VID: case DocumentType.AUDIO:
+ activeItem.presStartTime = targetDoc._currentTimecode;
+ break;
+ case DocumentType.COMPARISON :
+ const clipWidth = targetDoc._clipWidth;
+ activeItem.presPinClipWidth = clipWidth;
+ break;
+ default:
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeItem.presPinViewX = x;
+ activeItem.presPinViewY = y;
+ activeItem.presPinViewScale = scale;
+ }
+ }
@computed get recordingIsInOverlay() {
let isInOverlay = false
@@ -284,10 +319,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return isInOverlay
}
- @undoBatch
- @action
- hideRecording = () => {
- console.log("hide recording")
+ removeAllRecordingInOverlay = () => {
DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => {
if (doc.slides === this.rootDoc) {
Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc);
@@ -297,17 +329,17 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
@undoBatch
@action
+ hideRecording = (e: React.PointerEvent) => {
+ e.stopPropagation()
+ this.removeAllRecordingInOverlay()
+ }
+
+ @undoBatch
+ @action
showRecording = (activeItem: Doc) => {
- // Remove every recording that already exists in overlay view
- DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => {
- if (doc.slides !== null) {
- Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc);
- }
- })
+ this.removeAllRecordingInOverlay()
if (activeItem.recording) {
// if we already have an existing recording
- console.log(activeItem.recording)
- console.log(Cast(activeItem.recording, Doc, null))
Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null));
}
@@ -325,25 +357,27 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (activeItem.recording) {
// if we already have an existing recording
- console.log("recording exists")
- console.log(activeItem.recording)
- console.log(Cast(activeItem.recording, Doc, null))
Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null));
} else {
- console.log("creating new recording")
// if we dont have any recording
- const recording = Docs.Create.WebCamDocument("", { _width: 400, _height: 200, title: "recording", cloneFieldFilter: new List<string>(["system"]) });
+ const recording = Docs.Create.WebCamDocument("", {
+ _width: 384, _height: 216,
+ hideDocumentButtonBar: true,
+ hideDecorationTitle: true,
+ hideOpenButton: true,
+ // hideDeleteButton: true,
+ cloneFieldFilter:
+ new List<string>(["system"])
+ });
// attach the recording to the slide, and attach the slide to the recording
recording.slides = activeItem
activeItem.recording = recording
- // TODO: Figure out exactly where we want the video to appear
- const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- recording.x = pt[0];
- recording.y = pt[1];
- console.log(pt)
+ // make recording box appear in the bottom right corner of the screen
+ recording.x = window.innerWidth - recording._width - 20;
+ recording.y = window.innerHeight - recording._height - 20;
Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, recording);
}
}
@@ -390,7 +424,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
onPointerDown={this.headerDown}
onPointerUp={this.headerUp}
>
- {miniView ?
+ {/* {miniView ?
// when width is LESS than 110 px
<div className={`presItem-miniSlide ${isSelected ? "active" : ""}`} ref={miniView ? this._dragRef : null}>
{`${this.indexInPres + 1}.`}
@@ -399,13 +433,17 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// when width is MORE than 110 px
<div className="presItem-number">
{`${this.indexInPres + 1}.`}
- </div>}
+ </div>} */}
+ {/* <div className="presItem-number">
+ {`${this.indexInPres + 1}.`}
+ </div> */}
{miniView ? (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
}}>
<div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
+ <div>{`${this.indexInPres + 1}. `}</div>
<EditableView
ref={this._titleRef}
editing={!isSelected ? false : undefined}
@@ -415,8 +453,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
SetValue={this.onSetValue}
/>
</div>
- <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip>
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
<div className={"presItem-slideButtons"}>
<Tooltip title={<><div className="dash-tooltip">{"Update view"}</div></>}>
<div className="slideButton"
@@ -442,7 +480,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
- {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{activeItem.groupWithUp ? "Ungroup" : "Group with up"}</div></>}>
+ {/* {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={{
@@ -456,7 +494,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
<FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} />
</div>
</div>
- </Tooltip>}
+ </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> */}
@@ -466,7 +504,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
<FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} />
</div></Tooltip>
</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="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div> */}
{this.renderEmbeddedInline}
</div>}
</div >);
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..0ded1bb3c 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/);
@@ -310,6 +274,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
}
+ @observable private _scrollTimer: any;
+
onScroll = (e: React.UIEvent<HTMLElement>) => {
if (this._mainCont.current && !this._forcedScroll) {
this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
@@ -317,6 +283,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.props.layoutDoc._scrollTop = this._mainCont.current.scrollTop;
}
this._ignoreScroll = false;
+ if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio
+ this._scrollTimer = setTimeout(() => {
+ DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!()!, false);
+ this._scrollTimer = undefined;
+ }, 200);
}
}
@@ -369,10 +340,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 +361,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 +372,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 +394,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 +431,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 +438,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 +461,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 +474,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 +541,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 +551,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..e486bcb52 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" />
+ 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 b07b3a7aa..6abc27b23 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -27,37 +27,37 @@ import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, m
import JSZip = require("jszip");
export namespace Field {
- export function toKeyValueString(doc: Doc, key: string): string {
- const onDelegate = Object.keys(doc).includes(key);
- const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- return !Field.IsField(field) ? "" : (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field));
- }
- export function toScriptString(field: Field): string {
- if (typeof field === "string") return `"${field}"`;
- if (typeof field === "number" || typeof field === "boolean") return String(field);
- if (field === undefined || field === null) return "null";
- return field[ToScriptString]();
- }
- export function toString(field: Field): string {
- if (typeof field === "string") return field;
- if (typeof field === "number" || typeof field === "boolean") return String(field);
- if (field instanceof ObjectField) return field[ToString]();
- if (field instanceof RefField) return field[ToString]();
- return "";
- }
- export function IsField(field: any): field is Field;
- export function IsField(field: any, includeUndefined: true): field is Field | undefined;
- export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
- return (typeof field === "string")
- || (typeof field === "number")
- || (typeof field === "boolean")
- || (field instanceof ObjectField)
- || (field instanceof RefField)
- || (includeUndefined && field === undefined);
- }
- export function Copy(field: any) {
- return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
- }
+ export function toKeyValueString(doc: Doc, key: string): string {
+ const onDelegate = Object.keys(doc).includes(key);
+ const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ return !Field.IsField(field) ? "" : (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field));
+ }
+ export function toScriptString(field: Field): string {
+ if (typeof field === "string") return `"${field}"`;
+ if (typeof field === "number" || typeof field === "boolean") return String(field);
+ if (field === undefined || field === null) return "null";
+ return field[ToScriptString]();
+ }
+ export function toString(field: Field): string {
+ if (typeof field === "string") return field;
+ if (typeof field === "number" || typeof field === "boolean") return String(field);
+ if (field instanceof ObjectField) return field[ToString]();
+ if (field instanceof RefField) return field[ToString]();
+ return "";
+ }
+ export function IsField(field: any): field is Field;
+ export function IsField(field: any, includeUndefined: true): field is Field | undefined;
+ export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
+ return (typeof field === "string")
+ || (typeof field === "number")
+ || (typeof field === "boolean")
+ || (field instanceof ObjectField)
+ || (field instanceof RefField)
+ || (includeUndefined && field === undefined);
+ }
+ export function Copy(field: any) {
+ return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
+ }
}
export type Field = number | string | boolean | ObjectField | RefField;
export type Opt<T> = T | undefined;
@@ -73,8 +73,8 @@ export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract
export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
- const list = Cast(field, listSpec(Doc));
- return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
+ const list = Cast(field, listSpec(Doc));
+ return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
}
export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> { return Cast(field, Doc); }
@@ -104,1304 +104,1325 @@ export const ForceServerWrite = Symbol("ForceServerWrite");
export const CachedUpdates = Symbol("Cached updates");
const AclMap = new Map<string, symbol>([
- ["None", AclUnset],
- [SharingPermissions.None, AclPrivate],
- [SharingPermissions.View, AclReadonly],
- [SharingPermissions.Augment, AclAugment],
- [SharingPermissions.SelfEdit, AclSelfEdit],
- [SharingPermissions.Edit, AclEdit],
- [SharingPermissions.Admin, AclAdmin]
+ ["None", AclUnset],
+ [SharingPermissions.None, AclPrivate],
+ [SharingPermissions.View, AclReadonly],
+ [SharingPermissions.Augment, AclAugment],
+ [SharingPermissions.SelfEdit, AclSelfEdit],
+ [SharingPermissions.Edit, AclEdit],
+ [SharingPermissions.Admin, AclAdmin]
]);
// caches the document access permissions for the current user.
// this recursively updates all protos as well.
export function updateCachedAcls(doc: Doc) {
- if (!doc) return;
- const permissions: { [key: string]: symbol } = {};
+ if (!doc) return;
+ const permissions: { [key: string]: symbol } = {};
- doc[UpdatingFromServer] = true;
- Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!));
- doc[UpdatingFromServer] = false;
+ doc[UpdatingFromServer] = true;
+ Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!));
+ doc[UpdatingFromServer] = false;
- if (Object.keys(permissions).length) {
- doc[AclSym] = permissions;
- }
+ if (Object.keys(permissions).length) {
+ doc[AclSym] = permissions;
+ }
- if (doc.proto instanceof Promise) {
- doc.proto.then(updateCachedAcls);
- return doc.proto;
- }
+ if (doc.proto instanceof Promise) {
+ doc.proto.then(updateCachedAcls);
+ return doc.proto;
+ }
}
@scriptingGlobal
@Deserializable("Doc", updateCachedAcls).withFields(["id"])
export class Doc extends RefField {
- constructor(id?: FieldId, forceSave?: boolean) {
- super(id);
- const doc = new Proxy<this>(this, {
- set: setter,
- get: getter,
- // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
- has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields,
- ownKeys: target => {
- const obj = {} as any;
- if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys);
- runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
- return Object.keys(obj);
- },
- getOwnPropertyDescriptor: (target, prop) => {
- if (prop.toString() === "__LAYOUT__") {
- return Reflect.getOwnPropertyDescriptor(target, prop);
- }
- if (prop in target.__fieldKeys) {
- return {
- configurable: true,//TODO Should configurable be true?
- enumerable: true,
- value: 0//() => target.__fields[prop])
- };
- }
- return Reflect.getOwnPropertyDescriptor(target, prop);
- },
- deleteProperty: deleteProperty,
- defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
- });
- this[SelfProxy] = doc;
- if (!id || forceSave) {
- DocServer.CreateField(doc);
- }
- return doc;
- }
-
- proto: Opt<Doc>;
- [key: string]: FieldResult;
-
- @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize })))
- private get __fields() { return this.___fields; }
- private set __fields(value) {
- this.___fields = value;
- for (const key in value) {
- const field = value[key];
- (field !== undefined) && (this.__fieldKeys[key] = true);
- if (!(field instanceof ObjectField)) continue;
- field[Parent] = this[Self];
- field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
- }
- }
- private get __fieldKeys() { return this.___fieldKeys; }
- private set __fieldKeys(value) { this.___fieldKeys = value; }
-
- @observable private ___fields: any = {};
- @observable private ___fieldKeys: any = {};
- @observable public [AclSym]: { [key: string]: symbol };
- @observable public [DirectLinksSym]: Set<Doc> = new Set();
-
- private [UpdatingFromServer]: boolean = false;
- private [ForceServerWrite]: boolean = false;
- public [Initializing]: boolean = false;
-
- private [Update] = (diff: any) => {
- (!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff);
- }
-
- private [Self] = this;
- private [SelfProxy]: any;
- public [FieldsSym] = () => this[Self].___fields; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
- public [WidthSym] = () => NumCast(this[SelfProxy]._width);
- public [HeightSym] = () => NumCast(this[SelfProxy]._height);
- public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
- public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? "-inaccessible-" : this[SelfProxy].title})`;
- public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
- public get [DataSym]() {
- const self = this[SelfProxy];
- return self.resolvedDataDoc && !self.isTemplateForField ? self :
- Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
- }
- @computed get __LAYOUT__(): Doc | undefined {
- const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null);
- if (templateLayoutDoc) {
- let renderFieldKey: any;
- const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")];
- if (typeof layoutField === "string") {
- renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1];
- } else {
- return Cast(layoutField, Doc, null);
- }
- return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc;
+ constructor(id?: FieldId, forceSave?: boolean) {
+ super(id);
+ const doc = new Proxy<this>(this, {
+ set: setter,
+ get: getter,
+ // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
+ has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields,
+ ownKeys: target => {
+ const obj = {} as any;
+ if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys);
+ runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
+ return Object.keys(obj);
+ },
+ getOwnPropertyDescriptor: (target, prop) => {
+ if (prop.toString() === "__LAYOUT__") {
+ return Reflect.getOwnPropertyDescriptor(target, prop);
+ }
+ if (prop in target.__fieldKeys) {
+ return {
+ configurable: true,//TODO Should configurable be true?
+ enumerable: true,
+ value: 0//() => target.__fields[prop])
+ };
+ }
+ return Reflect.getOwnPropertyDescriptor(target, prop);
+ },
+ deleteProperty: deleteProperty,
+ defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+ });
+ this[SelfProxy] = doc;
+ if (!id || forceSave) {
+ DocServer.CreateField(doc);
+ }
+ return doc;
+ }
+
+ proto: Opt<Doc>;
+ [key: string]: FieldResult;
+
+ @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize })))
+ private get __fields() { return this.___fields; }
+ private set __fields(value) {
+ this.___fields = value;
+ for (const key in value) {
+ const field = value[key];
+ (field !== undefined) && (this.__fieldKeys[key] = true);
+ if (!(field instanceof ObjectField)) continue;
+ field[Parent] = this[Self];
+ field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ }
+ }
+ private get __fieldKeys() { return this.___fieldKeys; }
+ private set __fieldKeys(value) { this.___fieldKeys = value; }
+
+ @observable private ___fields: any = {};
+ @observable private ___fieldKeys: any = {};
+ @observable public [AclSym]: { [key: string]: symbol };
+ @observable public [DirectLinksSym]: Set<Doc> = new Set();
+
+ private [UpdatingFromServer]: boolean = false;
+ private [ForceServerWrite]: boolean = false;
+ public [Initializing]: boolean = false;
+
+ private [Update] = (diff: any) => {
+ (!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff);
+ }
+
+ private [Self] = this;
+ private [SelfProxy]: any;
+ public [FieldsSym] = () => this[Self].___fields; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
+ public [WidthSym] = () => NumCast(this[SelfProxy]._width);
+ public [HeightSym] = () => NumCast(this[SelfProxy]._height);
+ public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
+ public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? "-inaccessible-" : this[SelfProxy].title})`;
+ public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
+ public get [DataSym]() {
+ const self = this[SelfProxy];
+ return self.resolvedDataDoc && !self.isTemplateForField ? self :
+ Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
+ }
+ @computed get __LAYOUT__(): Doc | undefined {
+ const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null);
+ if (templateLayoutDoc) {
+ let renderFieldKey: any;
+ const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")];
+ if (typeof layoutField === "string") {
+ renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1];
+ } else {
+ return Cast(layoutField, Doc, null);
}
- return undefined;
-
- }
-
- private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
- public static CurrentUserEmail: string = "";
- public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); }
- public async [HandleUpdate](diff: any) {
- const set = diff.$set;
- const sameAuthor = this.author === Doc.CurrentUserEmail;
- if (set) {
- for (const key in set) {
- const fprefix = "fields.";
- if (!key.startsWith(fprefix)) {
- continue;
- }
- const fKey = key.substring(fprefix.length);
- const fn = async () => {
- const value = await SerializationHelper.Deserialize(set[key]);
- const prev = GetEffectiveAcl(this);
- this[UpdatingFromServer] = true;
- this[fKey] = value;
- this[UpdatingFromServer] = false;
- if (fKey.startsWith("acl")) {
- updateCachedAcls(this);
- }
- if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
- DocServer.GetRefField(this[Id], true);
- }
- };
- const writeMode = DocServer.getFieldWriteMode(fKey);
- if (fKey.startsWith("acl") || writeMode !== DocServer.WriteMode.Playground) {
- delete this[CachedUpdates][fKey];
- await fn();
- } else {
- this[CachedUpdates][fKey] = fn;
- }
- }
+ return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc;
+ }
+ return undefined;
+
+ }
+
+ private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
+ public static CurrentUserEmail: string = "";
+ public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); }
+ public async [HandleUpdate](diff: any) {
+ const set = diff.$set;
+ const sameAuthor = this.author === Doc.CurrentUserEmail;
+ if (set) {
+ for (const key in set) {
+ const fprefix = "fields.";
+ if (!key.startsWith(fprefix)) {
+ continue;
+ }
+ const fKey = key.substring(fprefix.length);
+ const fn = async () => {
+ const value = await SerializationHelper.Deserialize(set[key]);
+ const prev = GetEffectiveAcl(this);
+ this[UpdatingFromServer] = true;
+ this[fKey] = value;
+ this[UpdatingFromServer] = false;
+ if (fKey.startsWith("acl")) {
+ updateCachedAcls(this);
+ }
+ if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
+ DocServer.GetRefField(this[Id], true);
+ }
+ };
+ const writeMode = DocServer.getFieldWriteMode(fKey);
+ if (fKey.startsWith("acl") || writeMode !== DocServer.WriteMode.Playground) {
+ delete this[CachedUpdates][fKey];
+ await fn();
+ } else {
+ this[CachedUpdates][fKey] = fn;
+ }
}
- const unset = diff.$unset;
- if (unset) {
- for (const key in unset) {
- if (!key.startsWith("fields.")) {
- continue;
- }
- const fKey = key.substring(7);
- const fn = () => {
- this[UpdatingFromServer] = true;
- delete this[fKey];
- this[UpdatingFromServer] = false;
- };
- if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
- delete this[CachedUpdates][fKey];
- await fn();
- } else {
- this[CachedUpdates][fKey] = fn;
- }
- }
+ }
+ const unset = diff.$unset;
+ if (unset) {
+ for (const key in unset) {
+ if (!key.startsWith("fields.")) {
+ continue;
+ }
+ const fKey = key.substring(7);
+ const fn = () => {
+ this[UpdatingFromServer] = true;
+ delete this[fKey];
+ this[UpdatingFromServer] = false;
+ };
+ if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
+ delete this[CachedUpdates][fKey];
+ await fn();
+ } else {
+ this[CachedUpdates][fKey] = fn;
+ }
}
- }
+ }
+ }
}
export namespace Doc {
- // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
- // const self = doc[Self];
- // return new Promise(res => getField(self, key, ignoreProto, res));
- // }
- // export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
- // return new Promise(async res => {
- // const field = await GetAsync(doc, key, ignoreProto);
- // return Cast(field, ctor);
- // });
- // }
-
- export function RunCachedUpdate(doc: Doc, field: string) {
- const update = doc[CachedUpdates][field];
- if (update) {
- update();
- delete doc[CachedUpdates][field];
+ // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
+ // const self = doc[Self];
+ // return new Promise(res => getField(self, key, ignoreProto, res));
+ // }
+ // export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
+ // return new Promise(async res => {
+ // const field = await GetAsync(doc, key, ignoreProto);
+ // return Cast(field, ctor);
+ // });
+ // }
+
+ export function RunCachedUpdate(doc: Doc, field: string) {
+ const update = doc[CachedUpdates][field];
+ if (update) {
+ update();
+ delete doc[CachedUpdates][field];
+ }
+ }
+ export function AddCachedUpdate(doc: Doc, field: string, oldValue: any) {
+ const val = oldValue;
+ doc[CachedUpdates][field] = () => {
+ doc[UpdatingFromServer] = true;
+ doc[field] = val;
+ doc[UpdatingFromServer] = false;
+ };
+ }
+ export function MakeReadOnly(): { end(): void } {
+ makeReadOnly();
+ return {
+ end() {
+ makeEditable();
}
- }
- export function AddCachedUpdate(doc: Doc, field: string, oldValue: any) {
- const val = oldValue;
- doc[CachedUpdates][field] = () => {
- doc[UpdatingFromServer] = true;
- doc[field] = val;
- doc[UpdatingFromServer] = false;
- };
- }
- export function MakeReadOnly(): { end(): void } {
- makeReadOnly();
- return {
- end() {
- makeEditable();
- }
- };
- }
+ };
+ }
- export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
- try {
- return getField(doc[Self], key, ignoreProto);
- } catch {
- return doc;
- }
- }
- export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
- return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
- }
- export function IsPrototype(doc: Doc) {
- return GetT(doc, "isPrototype", "boolean", true);
- }
- export function IsBaseProto(doc: Doc) {
- return GetT(doc, "baseProto", "boolean", true);
- }
- export function IsSystem(doc: Doc) {
- return GetT(doc, "system", "boolean", true);
- }
- export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
- if (key.startsWith("_")) key = key.substring(1);
- const hasProto = doc.proto instanceof Doc;
- const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
- const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1;
- if (onDeleg || !hasProto || (!onProto && !defaultProto)) {
- doc[key] = value;
- } else doc.proto![key] = value;
- }
- export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
- const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
-
- if (proto) {
- proto[key] = value;
- }
- }
- export function GetAllPrototypes(doc: Doc): Doc[] {
- const protos: Doc[] = [];
- let d: Opt<Doc> = doc;
- while (d) {
- protos.push(d);
- d = FieldValue(d.proto);
- }
- return protos;
- }
-
- /**
- * This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies
- * the values of the properties of a source object into the target.
- *
- * This is just a specific, Dash-authored version that serves the same role for our
- * Doc class.
- *
- * @param doc the target document into which you'd like to insert the new fields
- * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
- * to a potentially undefined field, where each entry in this mapping is optional.
- */
- export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>, skipUndefineds: boolean = false, isInitializing = false) {
- isInitializing && (doc[Initializing] = true);
- for (const key in fields) {
- if (fields.hasOwnProperty(key)) {
- const value = fields[key];
- if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds?
- doc[key] = value;
- }
- }
- }
- isInitializing && (doc[Initializing] = false);
+ export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
+ try {
+ return getField(doc[Self], key, ignoreProto);
+ } catch {
return doc;
- }
-
- // compare whether documents or their protos match
- export function AreProtosEqual(doc?: Doc, other?: Doc) {
- if (!doc || !other) return false;
- const r = (doc === other);
- const r2 = (Doc.GetProto(doc) === other);
- const r3 = (Doc.GetProto(other) === doc);
- const r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined);
- return r || r2 || r3 || r4;
- }
-
- // Gets the data document for the document. Note: this is mis-named -- it does not specifically
- // return the doc's proto, but rather recursively searches through the proto inheritance chain
- // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype').
- export function GetProto(doc: Doc): Doc {
- if (doc instanceof Promise) {
- // console.log("GetProto: warning: got Promise insead of Doc");
+ }
+ }
+ export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
+ return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
+ }
+ export function IsPrototype(doc: Doc) {
+ return GetT(doc, "isPrototype", "boolean", true);
+ }
+ export function IsBaseProto(doc: Doc) {
+ return GetT(doc, "baseProto", "boolean", true);
+ }
+ export function IsSystem(doc: Doc) {
+ return GetT(doc, "system", "boolean", true);
+ }
+ export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
+ if (key.startsWith("_")) key = key.substring(1);
+ const hasProto = doc.proto instanceof Doc;
+ const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
+ const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1;
+ if (onDeleg || !hasProto || (!onProto && !defaultProto)) {
+ doc[key] = value;
+ } else doc.proto![key] = value;
+ }
+ export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
+ const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
+
+ if (proto) {
+ proto[key] = value;
+ }
+ }
+ export function GetAllPrototypes(doc: Doc): Doc[] {
+ const protos: Doc[] = [];
+ let d: Opt<Doc> = doc;
+ while (d) {
+ protos.push(d);
+ d = FieldValue(d.proto);
+ }
+ return protos;
+ }
+
+ /**
+ * This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies
+ * the values of the properties of a source object into the target.
+ *
+ * This is just a specific, Dash-authored version that serves the same role for our
+ * Doc class.
+ *
+ * @param doc the target document into which you'd like to insert the new fields
+ * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
+ * to a potentially undefined field, where each entry in this mapping is optional.
+ */
+ export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>, skipUndefineds: boolean = false, isInitializing = false) {
+ isInitializing && (doc[Initializing] = true);
+ for (const key in fields) {
+ if (fields.hasOwnProperty(key)) {
+ const value = fields[key];
+ if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds?
+ doc[key] = value;
+ }
}
- const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
- return proto === doc ? proto : Doc.GetProto(proto);
- }
- export function GetDataDoc(doc: Doc): Doc {
- const proto = Doc.GetProto(doc);
- return proto === doc ? proto : Doc.GetDataDoc(proto);
- }
-
- export function allKeys(doc: Doc): string[] {
- const results: Set<string> = new Set;
-
- let proto: Doc | undefined = doc;
- while (proto) {
- Object.keys(proto).forEach(key => results.add(key));
- proto = proto.proto;
+ }
+ isInitializing && (doc[Initializing] = false);
+ return doc;
+ }
+
+ // compare whether documents or their protos match
+ export function AreProtosEqual(doc?: Doc, other?: Doc) {
+ if (!doc || !other) return false;
+ const r = (doc === other);
+ const r2 = (Doc.GetProto(doc) === other);
+ const r3 = (Doc.GetProto(other) === doc);
+ const r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined);
+ return r || r2 || r3 || r4;
+ }
+
+ // Gets the data document for the document. Note: this is mis-named -- it does not specifically
+ // return the doc's proto, but rather recursively searches through the proto inheritance chain
+ // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype').
+ export function GetProto(doc: Doc): Doc {
+ if (doc instanceof Promise) {
+ // console.log("GetProto: warning: got Promise insead of Doc");
+ }
+ const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
+ return proto === doc ? proto : Doc.GetProto(proto);
+ }
+ export function GetDataDoc(doc: Doc): Doc {
+ const proto = Doc.GetProto(doc);
+ return proto === doc ? proto : Doc.GetDataDoc(proto);
+ }
+
+ export function allKeys(doc: Doc): string[] {
+ const results: Set<string> = new Set;
+
+ let proto: Doc | undefined = doc;
+ while (proto) {
+ Object.keys(proto).forEach(key => results.add(key));
+ proto = proto.proto;
+ }
+
+ return Array.from(results);
+ }
+
+ /**
+ * @returns the index of doc toFind in list of docs, -1 otherwise
+ */
+ export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
+ let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1);
+ index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1);
+ return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
+ }
+
+ /**
+ * Removes doc from the list of Docs at listDoc[fieldKey]
+ * @returns true if successful, false otherwise.
+ */
+ export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) {
+ const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
+ if (listDoc[key] === undefined) {
+ Doc.GetProto(listDoc)[key] = new List<Doc>();
+ }
+ const list = Cast(listDoc[key], listSpec(Doc));
+ if (list) {
+ const ind = list.indexOf(doc);
+ if (ind !== -1) {
+ list.splice(ind, 1);
+ return true;
}
-
- return Array.from(results);
- }
-
- /**
- * @returns the index of doc toFind in list of docs, -1 otherwise
- */
- export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
- let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1);
- index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1);
- return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
- }
-
- /**
- * Removes doc from the list of Docs at listDoc[fieldKey]
- * @returns true if successful, false otherwise.
- */
- export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) {
- const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
- if (listDoc[key] === undefined) {
- Doc.GetProto(listDoc)[key] = new List<Doc>();
+ }
+ return false;
+ }
+
+ /**
+ * Adds doc to the list of Docs stored at listDoc[fieldKey].
+ * @returns true if successful, false otherwise.
+ */
+ export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) {
+ const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
+ if (listDoc[key] === undefined) {
+ Doc.GetProto(listDoc)[key] = new List<Doc>();
+ }
+ const list = Cast(listDoc[key], listSpec(Doc));
+ if (list) {
+ if (allowDuplicates !== true) {
+ const pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1);
+ if (pind !== -1) {
+ return true;
+ //list.splice(pind, 1); // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection
+ }
}
- const list = Cast(listDoc[key], listSpec(Doc));
- if (list) {
- const ind = list.indexOf(doc);
- if (ind !== -1) {
- list.splice(ind, 1);
- return true;
- }
+ if (first) {
+ list.splice(0, 0, doc);
}
- return false;
- }
-
- /**
- * Adds doc to the list of Docs stored at listDoc[fieldKey].
- * @returns true if successful, false otherwise.
- */
- export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) {
- const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
- if (listDoc[key] === undefined) {
- Doc.GetProto(listDoc)[key] = new List<Doc>();
+ else {
+ const ind = relativeTo ? list.indexOf(relativeTo) : -1;
+ if (ind === -1) {
+ if (reversed) list.splice(0, 0, doc);
+ else list.push(doc);
+ }
+ else {
+ if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc);
+ else list.splice(before ? ind : ind + 1, 0, doc);
+ }
}
- const list = Cast(listDoc[key], listSpec(Doc));
- if (list) {
- if (allowDuplicates !== true) {
- const pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1);
- if (pind !== -1) {
- return true;
- //list.splice(pind, 1); // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection
- }
- }
- if (first) {
- list.splice(0, 0, doc);
- }
- else {
- const ind = relativeTo ? list.indexOf(relativeTo) : -1;
- if (ind === -1) {
- if (reversed) list.splice(0, 0, doc);
- else list.push(doc);
- }
- else {
- if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc);
- else list.splice(before ? ind : ind + 1, 0, doc);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Computes the bounds of the contents of a set of documents.
+ */
+ export function ComputeContentBounds(docList: Doc[]) {
+ const bounds = docList.reduce((bounds, doc) => {
+ const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
+ const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
+ return {
+ x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
+ r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
+ };
+ }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
+ return bounds;
+ }
+
+ export function MakeAlias(doc: Doc, id?: string) {
+ const alias = !GetT(doc, "isPrototype", "boolean", true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
+ const layout = Doc.LayoutField(alias);
+ if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) {
+ Doc.SetLayout(alias, Doc.MakeAlias(layout));
+ }
+ alias.aliasOf = doc;
+ alias.aliasNumber = Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1;
+ alias.title = ComputedField.MakeFunction(`renameAlias(this)`);
+ alias.author = Doc.CurrentUserEmail;
+
+ Doc.AddDocToList(Doc.GetProto(doc)[DataSym], "aliases", alias);
+
+ return alias;
+ }
+
+ export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
+ if (Doc.IsBaseProto(doc)) return doc;
+ if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
+ const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true);
+ cloneMap.set(doc[Id], copy);
+ const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== "annotationOn") : exclusions;
+ const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
+ await Promise.all(Object.keys(doc).map(async key => {
+ if (filter.includes(key)) return;
+ const assignKey = (val: any) => !dontCreate && (copy[key] = val);
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const field = ProxyField.WithoutProxy(() => doc[key]);
+ const copyObjectField = async (field: ObjectField) => {
+ const list = await Cast(doc[key], listSpec(Doc));
+ const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
+ if (docs !== undefined && docs.length) {
+ const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
+ !dontCreate && assignKey(new List<Doc>(clones));
+ } else if (doc[key] instanceof Doc) {
+ assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
+ } else {
+ !dontCreate && assignKey(ObjectField.MakeCopy(field));
+ if (field instanceof RichTextField) {
+ if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
+ rtfs.push({ copy, key, field });
}
- }
- return true;
+ }
+ }
+ };
+ if (key === "proto") {
+ if (doc[key] instanceof Doc) {
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
+ }
+ } else if (key === "anchor1" || key === "anchor2") {
+ if (doc[key] instanceof Doc) {
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch));
+ }
+ } else {
+ if (field instanceof RefField) {
+ assignKey(field);
+ } else if (cfield instanceof ComputedField) {
+ !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
+ } else if (field instanceof ObjectField) {
+ await copyObjectField(field);
+ } else if (field instanceof Promise) {
+ debugger; //This shouldn't happen...
+ } else {
+ assignKey(field);
+ }
}
- return false;
- }
-
- /**
- * Computes the bounds of the contents of a set of documents.
- */
- export function ComputeContentBounds(docList: Doc[]) {
- const bounds = docList.reduce((bounds, doc) => {
- const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
- const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
- return {
- x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
- r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
- };
- }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
- return bounds;
- }
-
- export function MakeAlias(doc: Doc, id?: string) {
- const alias = !GetT(doc, "isPrototype", "boolean", true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
- const layout = Doc.LayoutField(alias);
- if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) {
- Doc.SetLayout(alias, Doc.MakeAlias(layout));
+ }));
+ for (const link of Array.from(doc[DirectLinksSym])) {
+ const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch);
+ linkMap.set(link, linkClone);
+ }
+ if (!dontCreate) {
+ Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true);
+ asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
+ if (!Doc.IsPrototype(copy)) {
+ Doc.AddDocToList(doc, "branches", Doc.GetProto(copy));
}
- alias.aliasOf = doc;
- alias.aliasNumber = Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1;
- alias.title = ComputedField.MakeFunction(`renameAlias(this)`);
- alias.author = Doc.CurrentUserEmail;
-
- Doc.AddDocToList(Doc.GetProto(doc)[DataSym], "aliases", alias);
-
- return alias;
- }
-
- export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
- if (Doc.IsBaseProto(doc)) return doc;
- if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
- const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
- const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== "annotationOn") : exclusions;
- const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
- await Promise.all(Object.keys(doc).map(async key => {
- if (filter.includes(key)) return;
- const assignKey = (val: any) => !dontCreate && (copy[key] = val);
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = ProxyField.WithoutProxy(() => doc[key]);
- const copyObjectField = async (field: ObjectField) => {
- const list = await Cast(doc[key], listSpec(Doc));
- const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
- if (docs !== undefined && docs.length) {
- const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
- !dontCreate && assignKey(new List<Doc>(clones));
- } else if (doc[key] instanceof Doc) {
- assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
- } else {
- !dontCreate && assignKey(ObjectField.MakeCopy(field));
- if (field instanceof RichTextField) {
- if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
- rtfs.push({ copy, key, field });
- }
- }
- }
- };
- if (key === "proto") {
- if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
- }
- } else if (key === "anchor1" || key === "anchor2") {
- if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch));
- }
- } else {
- if (field instanceof RefField) {
- assignKey(field);
- } else if (cfield instanceof ComputedField) {
- !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
- } else if (field instanceof ObjectField) {
- await copyObjectField(field);
- } else if (field instanceof Promise) {
- debugger; //This shouldn't happen...
- } else {
- assignKey(field);
- }
- }
- }));
- for (const link of Array.from(doc[DirectLinksSym])) {
- const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch);
- linkMap.set(link, linkClone);
- }
- if (!dontCreate) {
- Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true);
- asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
- if (!Doc.IsPrototype(copy)) {
- Doc.AddDocToList(doc, "branches", Doc.GetProto(copy));
- }
- cloneMap.set(doc[Id], copy);
+ }
+ return copy;
+ }
+ export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
+ const linkMap = new Map<Doc, Doc>();
+ const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
+ const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["cloneOf", "branches", "branchOf"], dontCreate, asBranch);
+ Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
+ rtfMap.map(({ copy, key, field }) => {
+ const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
+ const mapped = cloneMap.get(id);
+ return attr + "\"" + (mapped ? mapped[Id] : id) + "\"";
+ };
+ const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
+ const mapped = cloneMap.get(id);
+ return href + (mapped ? mapped[Id] : id);
+ };
+ const regex = `(${Doc.localServerPath()})([^"]*)`;
+ const re = new RegExp(regex, "g");
+ copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
+ });
+ return { clone: copy, map: cloneMap };
+ }
+
+ export async function Zip(doc: Doc) {
+ // const a = document.createElement("a");
+ // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+ // a.href = url;
+ // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+ // a.click();
+ const { clone, map } = await Doc.MakeClone(doc, true);
+ function replacer(key: any, value: any) {
+ if (["branchOf", "cloneOf", "context", "cursors"].includes(key)) return undefined;
+ else if (value instanceof Doc) {
+ if (key !== "field" && Number.isNaN(Number(key))) {
+ const __fields = value[FieldsSym]();
+ return { id: value[Id], __type: "Doc", fields: __fields };
+ } else {
+ return { fieldId: value[Id], __type: "proxy" };
+ }
}
- return copy;
- }
- export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
- const linkMap = new Map<Doc, Doc>();
- const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["cloneOf", "branches", "branchOf"], dontCreate, asBranch);
- Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
- rtfMap.map(({ copy, key, field }) => {
- const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
- const mapped = cloneMap.get(id);
- return attr + "\"" + (mapped ? mapped[Id] : id) + "\"";
- };
- const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
- const mapped = cloneMap.get(id);
- return href + (mapped ? mapped[Id] : id);
- };
- const regex = `(${Doc.localServerPath()})([^"]*)`;
- const re = new RegExp(regex, "g");
- copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
+ else if (value instanceof ScriptField) return { script: value.script, __type: "script" };
+ else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" };
+ else if (value instanceof ImageField) return { url: value.url.href, __type: "image" };
+ else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" };
+ else if (value instanceof AudioField) return { url: value.url.href, __type: "audio" };
+ else if (value instanceof VideoField) return { url: value.url.href, __type: "video" };
+ else if (value instanceof WebField) return { url: value.url.href, __type: "web" };
+ else if (value instanceof MapField) return { url: value.url.href, __type: "map" };
+ else if (value instanceof DateField) return { date: value.toString(), __type: "date" };
+ else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: "proxy" };
+ else if (value instanceof Array && key !== "fields") return { fields: value, __type: "list" };
+ else if (value instanceof ComputedField) return { script: value.script, __type: "computed" };
+ else return value;
+ }
+
+ const docs: { [id: string]: any } = {};
+ Array.from(map.entries()).forEach(f => docs[f[0]] = f[1]);
+ const docString = JSON.stringify({ id: doc[Id], docs }, replacer);
+
+ const zip = new JSZip();
+
+ zip.file(doc.title + ".json", docString);
+
+ // // Generate a directory within the Zip file structure
+ // var img = zip.folder("images");
+
+ // // Add a file to the directory, in this case an image with data URI as contents
+ // img.file("smile.gif", imgData, {base64: true});
+
+ // Generate the zip file asynchronously
+ zip.generateAsync({ type: "blob" })
+ .then((content: any) => {
+ // Force down of the Zip file
+ saveAs(content, doc.title + ".zip"); // glr: Possibly change the name of the document to match the title?
});
- return { clone: copy, map: cloneMap };
- }
-
- export async function Zip(doc: Doc) {
- // const a = document.createElement("a");
- // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
- // a.href = url;
- // a.download = `DocExport-${this.props.Document[Id]}.zip`;
- // a.click();
- const { clone, map } = await Doc.MakeClone(doc, true);
- function replacer(key: any, value: any) {
- if (["branchOf", "cloneOf", "context", "cursors"].includes(key)) return undefined;
- else if (value instanceof Doc) {
- if (key !== "field" && Number.isNaN(Number(key))) {
- const __fields = value[FieldsSym]();
- return { id: value[Id], __type: "Doc", fields: __fields };
- } else {
- return { fieldId: value[Id], __type: "proxy" };
+ }
+ //
+ // Determines whether the layout needs to be expanded (as a template).
+ // template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template
+ //
+ export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) {
+ return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc;
+ }
+
+ const _pendingMap: Map<string, boolean> = new Map();
+ //
+ // Returns an expanded template layout for a target data document if there is a template relationship
+ // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
+ // of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
+ // templateArgs should be equivalent to the layout key that generates the template since that's where the template parameters are stored in ()'s at the end of the key.
+ // NOTE: the template will have references to "@params" -- the template arguments will be assigned to the '@params' field
+ // so that when the @params key is accessed, it will be rewritten as the key that is stored in the 'params' field and
+ // the derefence will then occur on the rootDocument (the original document).
+ // in the future, field references could be written as @<someparam> and then arguments would be passed in the layout key as:
+ // layout_mytemplate(somparam=somearg).
+ // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument
+ export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) {
+ const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS);
+ if (!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc) || !targetDoc) return templateLayoutDoc;
+
+ const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders
+ // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc
+ // using the template layout doc's id as the field key.
+ // If it doesn't find the expanded layout, then it makes a delegate of the template layout and
+ // saves it on the data doc indexed by the template layout's id.
+ //
+ const params = args.split("=").length > 1 ? args.split("=")[0] : "PARAMS";
+ const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc);
+ const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + (args ? `(${args})` : "") + "]";
+ let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
+
+ if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
+ expandedTemplateLayout = undefined;
+ _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
+ }
+ else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) {
+ if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) {
+ expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
+ } else {
+ templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype
+ if (!targetDoc[expandedLayoutFieldKey]) {
+ _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true);
+ setTimeout(action(() => {
+ const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]");
+ // the template's arguments are stored in params which is derefenced to find
+ // the actual field key where the parameterized template data is stored.
+ newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances
+ newLayoutDoc.rootDocument = targetDoc;
+ const dataDoc = Doc.GetProto(targetDoc);
+ newLayoutDoc.resolvedDataDoc = dataDoc;
+ if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) {
+ dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
}
- }
- else if (value instanceof ScriptField) return { script: value.script, __type: "script" };
- else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" };
- else if (value instanceof ImageField) return { url: value.url.href, __type: "image" };
- else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" };
- else if (value instanceof AudioField) return { url: value.url.href, __type: "audio" };
- else if (value instanceof VideoField) return { url: value.url.href, __type: "video" };
- else if (value instanceof WebField) return { url: value.url.href, __type: "web" };
- else if (value instanceof MapField) return { url: value.url.href, __type: "map" };
- else if (value instanceof DateField) return { date: value.toString(), __type: "date" };
- else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: "proxy" };
- else if (value instanceof Array && key !== "fields") return { fields: value, __type: "list" };
- else if (value instanceof ComputedField) return { script: value.script, __type: "computed" };
- else return value;
- }
+ targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
- const docs: { [id: string]: any } = {};
- Array.from(map.entries()).forEach(f => docs[f[0]] = f[1]);
- const docString = JSON.stringify({ id: doc[Id], docs }, replacer);
-
- const zip = new JSZip();
-
- zip.file(doc.title + ".json", docString);
-
- // // Generate a directory within the Zip file structure
- // var img = zip.folder("images");
-
- // // Add a file to the directory, in this case an image with data URI as contents
- // img.file("smile.gif", imgData, {base64: true});
-
- // Generate the zip file asynchronously
- zip.generateAsync({ type: "blob" })
- .then((content: any) => {
- // Force down of the Zip file
- saveAs(content, doc.title + ".zip"); // glr: Possibly change the name of the document to match the title?
- });
- }
- //
- // Determines whether the layout needs to be expanded (as a template).
- // template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template
- //
- export function WillExpandTemplateLayout(layoutDoc: Doc, dataDoc?: Doc) {
- return (layoutDoc.isTemplateForField || layoutDoc.isTemplateDoc) && dataDoc && layoutDoc !== dataDoc;
- }
-
- const _pendingMap: Map<string, boolean> = new Map();
- //
- // Returns an expanded template layout for a target data document if there is a template relationship
- // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
- // of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
- // templateArgs should be equivalent to the layout key that generates the template since that's where the template parameters are stored in ()'s at the end of the key.
- // NOTE: the template will have references to "@params" -- the template arguments will be assigned to the '@params' field
- // so that when the @params key is accessed, it will be rewritten as the key that is stored in the 'params' field and
- // the derefence will then occur on the rootDocument (the original document).
- // in the future, field references could be written as @<someparam> and then arguments would be passed in the layout key as:
- // layout_mytemplate(somparam=somearg).
- // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument
- export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) {
- const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS);
- if (!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc) || !targetDoc) return templateLayoutDoc;
-
- const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders
- // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc
- // using the template layout doc's id as the field key.
- // If it doesn't find the expanded layout, then it makes a delegate of the template layout and
- // saves it on the data doc indexed by the template layout's id.
- //
- const params = args.split("=").length > 1 ? args.split("=")[0] : "PARAMS";
- const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc);
- const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + (args ? `(${args})` : "") + "]";
- let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
-
- if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
- expandedTemplateLayout = undefined;
- _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
+ _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args);
+ }));
+ }
}
- else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) {
- if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) {
- expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
- } else {
- templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype
- if (!targetDoc[expandedLayoutFieldKey]) {
- _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true);
- setTimeout(action(() => {
- const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]");
- // the template's arguments are stored in params which is derefenced to find
- // the actual field key where the parameterized template data is stored.
- newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances
- newLayoutDoc.rootDocument = targetDoc;
- const dataDoc = Doc.GetProto(targetDoc);
- newLayoutDoc.resolvedDataDoc = dataDoc;
- if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) {
- dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
- }
- targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
-
- _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args);
- }));
- }
- }
- }
- return expandedTemplateLayout instanceof Doc ? expandedTemplateLayout : undefined; // layout is undefined if the expandedTemplateLayout is pending.
- }
-
- // if the childDoc is a template for a field, then this will return the expanded layout with its data doc.
- // otherwise, it just returns the childDoc
- export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc) {
- if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) {
- console.log("No, no, no!");
- return { layout: childDoc, data: childDoc };
- }
- const resolvedDataDoc = (Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc);
- return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, "(" + StrCast(containerDoc.PARAMS) + ")"), data: resolvedDataDoc };
- }
-
- export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
- Object.keys(doc).forEach(key => {
- const field = ProxyField.WithoutProxy(() => doc[key]);
- if (key === "proto" && copyProto) {
- if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) {
- overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto);
- }
- } else {
- if (field instanceof RefField) {
- overwrite[key] = field;
- } else if (field instanceof ObjectField) {
- overwrite[key] = ObjectField.MakeCopy(field);
- } else if (field instanceof Promise) {
- debugger; //This shouldn't happend...
- } else {
- overwrite[key] = field;
- }
- }
- });
-
- return overwrite;
- }
-
- export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
- const copy = new Doc(copyProtoId, true);
- const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []);
- Object.keys(doc).forEach(key => {
- if (exclude.includes(key)) return;
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = ProxyField.WithoutProxy(() => doc[key]);
- if (key === "proto" && copyProto) {
- if (doc[key] instanceof Doc) {
- copy[key] = Doc.MakeCopy(doc[key]!, false);
- }
- } else {
- if (field instanceof RefField) {
- copy[key] = field;
- } else if (cfield instanceof ComputedField) {
- copy[key] = cfield[Copy]();// ComputedField.MakeFunction(cfield.script.originalScript);
- } else if (field instanceof ObjectField) {
- copy[key] = doc[key] instanceof Doc ?
- key.includes("layout[") ? undefined : doc[key] : // reference documents except remove documents that are expanded teplate fields
- ObjectField.MakeCopy(field);
- } else if (field instanceof Promise) {
- debugger; //This shouldn't happend...
- } else {
- copy[key] = field;
- }
- }
- });
- copy.author = Doc.CurrentUserEmail;
- if (copyProto) {
- Doc.GetProto(copy).context = undefined;
- Doc.GetProto(copy).aliases = new List<Doc>([copy]);
+ }
+ return expandedTemplateLayout instanceof Doc ? expandedTemplateLayout : undefined; // layout is undefined if the expandedTemplateLayout is pending.
+ }
+
+ // if the childDoc is a template for a field, then this will return the expanded layout with its data doc.
+ // otherwise, it just returns the childDoc
+ export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc) {
+ if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) {
+ console.log("No, no, no!");
+ return { layout: childDoc, data: childDoc };
+ }
+ const resolvedDataDoc = (Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc);
+ return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, "(" + StrCast(containerDoc.PARAMS) + ")"), data: resolvedDataDoc };
+ }
+
+ export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
+ Object.keys(doc).forEach(key => {
+ const field = ProxyField.WithoutProxy(() => doc[key]);
+ if (key === "proto" && copyProto) {
+ if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) {
+ overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto);
+ }
} else {
- Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy);
- }
- copy.context = undefined;
- Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared");
- if (retitle) {
- copy.title = incrementTitleCopy(StrCast(copy.title));
+ if (field instanceof RefField) {
+ overwrite[key] = field;
+ } else if (field instanceof ObjectField) {
+ overwrite[key] = ObjectField.MakeCopy(field);
+ } else if (field instanceof Promise) {
+ debugger; //This shouldn't happend...
+ } else {
+ overwrite[key] = field;
+ }
}
- return copy;
- }
-
-
- export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
- export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
- export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc> {
- if (doc) {
- const delegate = new Doc(id, true);
- delegate[Initializing] = true;
- delegate.proto = doc;
- delegate.author = Doc.CurrentUserEmail;
- if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate);
- title && (delegate.title = title);
- delegate[Initializing] = false;
- return delegate;
+ });
+
+ return overwrite;
+ }
+
+ export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
+ const copy = new Doc(copyProtoId, true);
+ const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []);
+ Object.keys(doc).forEach(key => {
+ if (exclude.includes(key)) return;
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const field = ProxyField.WithoutProxy(() => doc[key]);
+ if (key === "proto" && copyProto) {
+ if (doc[key] instanceof Doc) {
+ copy[key] = Doc.MakeCopy(doc[key]!, false);
+ }
+ } else {
+ if (field instanceof RefField) {
+ copy[key] = field;
+ } else if (cfield instanceof ComputedField) {
+ copy[key] = cfield[Copy]();// ComputedField.MakeFunction(cfield.script.originalScript);
+ } else if (field instanceof ObjectField) {
+ copy[key] = doc[key] instanceof Doc ?
+ key.includes("layout[") ? undefined : doc[key] : // reference documents except remove documents that are expanded teplate fields
+ ObjectField.MakeCopy(field);
+ } else if (field instanceof Promise) {
+ debugger; //This shouldn't happend...
+ } else {
+ copy[key] = field;
+ }
}
- return undefined;
- }
-
- // Makes a delegate of a document by first creating a delegate where data should be stored
- // (ie, the 'data' doc), and then creates another delegate of that (ie, the 'layout' doc).
- // This is appropriate if you're trying to create a document that behaves like all
- // regularly created documents (e.g, text docs, pdfs, etc which all have data/layout docs)
- export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string): Doc {
- const delegateProto = new Doc();
- delegateProto[Initializing] = true;
- delegateProto.proto = doc;
- delegateProto.author = Doc.CurrentUserEmail;
- delegateProto.isPrototype = true;
- title && (delegateProto.title = title);
+ });
+ copy.author = Doc.CurrentUserEmail;
+ if (copyProto) {
+ Doc.GetProto(copy).context = undefined;
+ Doc.GetProto(copy).aliases = new List<Doc>([copy]);
+ } else {
+ Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy);
+ }
+ copy.context = undefined;
+ Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared");
+ if (retitle) {
+ copy.title = incrementTitleCopy(StrCast(copy.title));
+ }
+ return copy;
+ }
+
+
+ export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
+ export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
+ export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc> {
+ if (doc) {
const delegate = new Doc(id, true);
delegate[Initializing] = true;
- delegate.proto = delegateProto;
+ delegate.proto = doc;
delegate.author = Doc.CurrentUserEmail;
- Doc.AddDocToList(delegateProto[DataSym], "aliases", delegate);
+ 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;
- delegateProto[Initializing] = false;
return delegate;
- }
-
- let _applyCount: number = 0;
- export function ApplyTemplate(templateDoc: Doc) {
- if (templateDoc) {
- const proto = new Doc();
- proto.author = Doc.CurrentUserEmail;
- const target = Doc.MakeDelegate(proto);
- const targetKey = StrCast(templateDoc.layoutKey, "layout");
- const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")");
- target.layoutKey = targetKey;
- applied && (Doc.GetProto(applied).type = templateDoc.type);
- Doc.UserDoc().defaultAclPrivate && (applied["acl-Public"] = "Not Shared");
- return applied;
- }
- return undefined;
- }
- export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) {
- if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) {
- if (target.resolvedDataDoc) {
- target[targetKey] = new PrefetchProxy(templateDoc);
- } else {
- titleTarget && (Doc.GetProto(target).title = titleTarget);
- const setDoc = [AclAdmin, AclEdit].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
- setDoc[targetKey] = new PrefetchProxy(templateDoc);
- }
+ }
+ return undefined;
+ }
+
+ // Makes a delegate of a document by first creating a delegate where data should be stored
+ // (ie, the 'data' doc), and then creates another delegate of that (ie, the 'layout' doc).
+ // This is appropriate if you're trying to create a document that behaves like all
+ // regularly created documents (e.g, text docs, pdfs, etc which all have data/layout docs)
+ export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string): Doc {
+ const delegateProto = new Doc();
+ delegateProto[Initializing] = true;
+ delegateProto.proto = doc;
+ delegateProto.author = Doc.CurrentUserEmail;
+ delegateProto.isPrototype = true;
+ title && (delegateProto.title = title);
+ const delegate = new Doc(id, true);
+ delegate[Initializing] = true;
+ delegate.proto = delegateProto;
+ delegate.author = Doc.CurrentUserEmail;
+ Doc.AddDocToList(delegateProto[DataSym], "aliases", delegate);
+ delegate[Initializing] = false;
+ delegateProto[Initializing] = false;
+ return delegate;
+ }
+
+ let _applyCount: number = 0;
+ export function ApplyTemplate(templateDoc: Doc) {
+ if (templateDoc) {
+ const proto = new Doc();
+ proto.author = Doc.CurrentUserEmail;
+ const target = Doc.MakeDelegate(proto);
+ const targetKey = StrCast(templateDoc.layoutKey, "layout");
+ const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")");
+ target.layoutKey = targetKey;
+ applied && (Doc.GetProto(applied).type = templateDoc.type);
+ Doc.UserDoc().defaultAclPrivate && (applied["acl-Public"] = "Not Shared");
+ return applied;
+ }
+ return undefined;
+ }
+ export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) {
+ if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) {
+ if (target.resolvedDataDoc) {
+ target[targetKey] = new PrefetchProxy(templateDoc);
+ } else {
+ titleTarget && (Doc.GetProto(target).title = titleTarget);
+ const setDoc = [AclAdmin, AclEdit].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
+ setDoc[targetKey] = new PrefetchProxy(templateDoc);
}
- return target;
- }
-
- //
- // This function converts a generic field layout display into a field layout that displays a specific
- // metadata field indicated by the title of the template field (not the default field that it was rendering)
- //
- export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
-
- // find the metadata field key that this template field doc will display (indicated by its title)
- const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, "");
-
- // update the original template to mark it as a template
- templateField.isTemplateForField = metadataFieldKey;
- templateField.title = metadataFieldKey;
-
- const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
- const templateCaptionValue = templateField.caption;
- // move any data that the template field had been rendering over to the template doc so that things will still be rendered
- // when the template field is adjusted to point to the new metadatafield key.
- // note 1: if the template field contained a list of documents, each of those documents will be converted to templates as well.
- // note 2: this will not overwrite any field that already exists on the template doc at the field key
- if (!templateDoc?.[metadataFieldKey] && templateFieldValue instanceof ObjectField) {
- Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc));
- (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue));
+ }
+ return target;
+ }
+
+ //
+ // This function converts a generic field layout display into a field layout that displays a specific
+ // metadata field indicated by the title of the template field (not the default field that it was rendering)
+ //
+ export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
+
+ // find the metadata field key that this template field doc will display (indicated by its title)
+ const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, "");
+
+ // update the original template to mark it as a template
+ templateField.isTemplateForField = metadataFieldKey;
+ templateField.title = metadataFieldKey;
+
+ const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
+ const templateCaptionValue = templateField.caption;
+ // move any data that the template field had been rendering over to the template doc so that things will still be rendered
+ // when the template field is adjusted to point to the new metadatafield key.
+ // note 1: if the template field contained a list of documents, each of those documents will be converted to templates as well.
+ // note 2: this will not overwrite any field that already exists on the template doc at the field key
+ if (!templateDoc?.[metadataFieldKey] && templateFieldValue instanceof ObjectField) {
+ Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc));
+ (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue));
+ }
+ // get the layout string that the template uses to specify its layout
+ const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField)));
+
+ // change it to render the target metadata field instead of what it was rendering before and assign it to the template field layout document.
+ Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
+
+ // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino)
+ Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc);
+
+ return true;
+ }
+
+
+ // converts a document id to a url path on the server
+ export function globalServerPath(doc: Doc | string = ""): string {
+ return Utils.prepend("/doc/" + (doc instanceof Doc ? doc[Id] : doc));
+ }
+ // converts a document id to a url path on the server
+ export function localServerPath(doc?: Doc): string {
+ return "/doc/" + (doc ? doc[Id] : "");
+ }
+
+ export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
+ const doc2Layout = Doc.Layout(doc2);
+ const doc1Layout = Doc.Layout(doc1);
+ const x2 = NumCast(doc2.x) - clusterDistance;
+ const y2 = NumCast(doc2.y) - clusterDistance;
+ const w2 = NumCast(doc2Layout._width) + clusterDistance;
+ const h2 = NumCast(doc2Layout._height) + clusterDistance;
+ const x = NumCast(doc1.x) - clusterDistance;
+ const y = NumCast(doc1.y) - clusterDistance;
+ const w = NumCast(doc1Layout._width) + clusterDistance;
+ const h = NumCast(doc1Layout._height) + clusterDistance;
+ return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+ }
+
+ export function isBrushedHighlightedDegree(doc: Doc) {
+ return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc);
+ }
+
+ export class DocBrush {
+ BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
+ SearchMatchDoc: ObservableMap<Doc, { searchMatch: number }> = new ObservableMap();
+ }
+ const brushManager = new DocBrush();
+
+ export class DocData {
+ @observable _user_doc: Doc = undefined!;
+ @observable _sharing_doc: Doc = undefined!;
+ @observable _searchQuery: string = "";
+ }
+
+ // the document containing the view layout information - will be the Document itself unless the Document has
+ // a layout field or 'layout' is given.
+ export function Layout(doc: Doc, layout?: Doc): Doc {
+ const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null);
+ return overrideLayout || doc[LayoutSym] || doc;
+ }
+ export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; }
+ export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; }
+ export function LayoutFieldKey(doc: Doc): string { return StrCast(Doc.Layout(doc).layout).split("'")[1]; }
+ export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
+ return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
+ }
+ export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeWidth"], useWidth ? doc[WidthSym]() : 0)); }
+ export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
+ const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeHeight"], useHeight ? doc[HeightSym]() : 0) : 0;
+ const nheight = doc ? Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]() / doc[WidthSym]() : 0;
+ return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight);
+ }
+ export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeWidth"] = width; }
+ export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeHeight"] = height; }
+
+
+ const manager = new DocData();
+ export function SearchQuery(): string { return manager._searchQuery; }
+ export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); }
+ export function UserDoc(): Doc { return manager._user_doc; }
+ export function SharingDoc(): Doc { return Cast(Doc.UserDoc().mySharedDocs, Doc, null); }
+ export function LinkDBDoc(): Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); }
+ export function SetUserDoc(doc: Doc) { return (manager._user_doc = doc); }
+
+ const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
+ brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ });
+ export function IsSearchMatch(doc: Doc) { return isSearchMatchCache(doc); }
+ export function IsSearchMatchUnmemoized(doc: Doc) {
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
+ brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ }
+ export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
+ if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+ brushManager.SearchMatchDoc.set(doc, results);
+ }
+ return doc;
+ }
+ export function SearchMatchNext(doc: Doc, backward: boolean) {
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+ const result = brushManager.SearchMatchDoc.get(doc);
+ const num = Math.abs(result?.searchMatch || 0) + 1;
+ runInAction(() => result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num }));
+ return doc;
+ }
+ export function ClearSearchMatches() {
+ brushManager.SearchMatchDoc.clear();
+ }
+
+ const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); });
+ export function IsBrushed(doc: Doc) { return isBrushedCache(doc); }
+
+ export enum DocBrushStatus {
+ unbrushed = 0,
+ protoBrushed = 1,
+ selfBrushed = 2,
+ 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;
+ 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;
+ }
+ }
}
- // get the layout string that the template uses to specify its layout
- const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField)));
-
- // change it to render the target metadata field instead of what it was rendering before and assign it to the template field layout document.
- Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
-
- // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino)
- Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc);
-
- return true;
- }
-
-
- // converts a document id to a url path on the server
- export function globalServerPath(doc: Doc | string = ""): string {
- return Utils.prepend("/doc/" + (doc instanceof Doc ? doc[Id] : doc));
- }
- // converts a document id to a url path on the server
- export function localServerPath(doc?: Doc): string {
- return "/doc/" + (doc ? doc[Id] : "");
- }
-
- export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
- const doc2Layout = Doc.Layout(doc2);
- const doc1Layout = Doc.Layout(doc1);
- const x2 = NumCast(doc2.x) - clusterDistance;
- const y2 = NumCast(doc2.y) - clusterDistance;
- const w2 = NumCast(doc2Layout._width) + clusterDistance;
- const h2 = NumCast(doc2Layout._height) + clusterDistance;
- const x = NumCast(doc1.x) - clusterDistance;
- const y = NumCast(doc1.y) - clusterDistance;
- const w = NumCast(doc1Layout._width) + clusterDistance;
- const h = NumCast(doc1Layout._height) + clusterDistance;
- return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
- }
-
- export function isBrushedHighlightedDegree(doc: Doc) {
- return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc);
- }
-
- export class DocBrush {
- BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
- SearchMatchDoc: ObservableMap<Doc, { searchMatch: number }> = new ObservableMap();
- }
- const brushManager = new DocBrush();
-
- export class DocData {
- @observable _user_doc: Doc = undefined!;
- @observable _sharing_doc: Doc = undefined!;
- @observable _searchQuery: string = "";
- }
-
- // the document containing the view layout information - will be the Document itself unless the Document has
- // a layout field or 'layout' is given.
- export function Layout(doc: Doc, layout?: Doc): Doc {
- const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null);
- return overrideLayout || doc[LayoutSym] || doc;
- }
- export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; }
- export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; }
- export function LayoutFieldKey(doc: Doc): string { return StrCast(Doc.Layout(doc).layout).split("'")[1]; }
- export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
- return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
- }
- export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeWidth"], useWidth ? doc[WidthSym]() : 0)); }
- export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
- const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeHeight"], useHeight ? doc[HeightSym]() : 0) : 0;
- const nheight = doc ? Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]() / doc[WidthSym]() : 0;
- return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight);
- }
- export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeWidth"] = width; }
- export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeHeight"] = height; }
-
-
- const manager = new DocData();
- export function SearchQuery(): string { return manager._searchQuery; }
- export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); }
- export function UserDoc(): Doc { return manager._user_doc; }
- export function SharingDoc(): Doc { return Cast(Doc.UserDoc().mySharedDocs, Doc, null); }
- export function LinkDBDoc(): Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); }
- export function SetUserDoc(doc: Doc) { return (manager._user_doc = doc); }
-
- const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
- brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
- });
- export function IsSearchMatch(doc: Doc) { return isSearchMatchCache(doc); }
- export function IsSearchMatchUnmemoized(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
- brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
- }
- export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
- if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
- brushManager.SearchMatchDoc.set(doc, results);
- }
- return doc;
- }
- export function SearchMatchNext(doc: Doc, backward: boolean) {
- if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
- const result = brushManager.SearchMatchDoc.get(doc);
- const num = Math.abs(result?.searchMatch || 0) + 1;
- runInAction(() => result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num }));
- return doc;
- }
- export function ClearSearchMatches() {
- brushManager.SearchMatchDoc.clear();
- }
-
- const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); });
- export function IsBrushed(doc: Doc) { return isBrushedCache(doc); }
-
- export enum DocBrushStatus {
- unbrushed = 0,
- protoBrushed = 1,
- selfBrushed = 2,
- highlighted = 3
- }
- // 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;
- }
- export function IsBrushedDegree(doc: Doc) {
- return computedFn(function IsBrushDegree(doc: Doc) {
- return Doc.IsBrushedDegreeUnmemoized(doc);
- })(doc);
- }
- export function BrushDoc(doc: Doc) {
- if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+ }
+ return status;
+ }
+ export function IsBrushedDegree(doc: Doc) {
+ return computedFn(function IsBrushDegree(doc: Doc) {
+ return Doc.IsBrushedDegreeUnmemoized(doc);
+ })(doc);
+ }
+ export function BrushDoc(doc: Doc) {
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+ 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;
+ });
+ return doc;
+ }
+ export function UnBrushDoc(doc: Doc) {
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+ runInAction(() => {
brushManager.BrushedDoc.delete(doc);
brushManager.BrushedDoc.delete(Doc.GetProto(doc));
- return doc;
- }
-
- export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
- return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) ||
- Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? "1" : "2";
- }
-
- export function linkFollowUnhighlight() {
- Doc.UnhighlightAll();
- document.removeEventListener("pointerdown", linkFollowUnhighlight);
- }
-
- let _lastDate = 0;
- export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) {
- linkFollowUnhighlight();
- Doc.HighlightDoc(destDoc, dataAndDisplayDocs);
- document.removeEventListener("pointerdown", linkFollowUnhighlight);
- document.addEventListener("pointerdown", linkFollowUnhighlight);
- const lastDate = _lastDate = Date.now();
- window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000);
- }
-
- export class HighlightBrush {
- @observable HighlightedDoc: Map<Doc, boolean> = new Map();
- }
- const highlightManager = new HighlightBrush();
- export function IsHighlighted(doc: Doc) {
- if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false;
- return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
- }
- export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
- runInAction(() => {
- highlightManager.HighlightedDoc.set(doc, true);
- dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true);
- });
- }
- export function UnHighlightDoc(doc: Doc) {
- runInAction(() => {
- highlightManager.HighlightedDoc.set(doc, false);
- highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false);
- });
- }
- export function UnhighlightAll() {
- const mapEntries = highlightManager.HighlightedDoc.keys();
- let docEntry: IteratorResult<Doc>;
- while (!(docEntry = mapEntries.next()).done) {
- const targetDoc = docEntry.value;
- targetDoc && Doc.UnHighlightDoc(targetDoc);
+ });
+ return doc;
+ }
+
+ export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
+ return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) ||
+ Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? "1" : "2";
+ }
+
+ export function linkFollowUnhighlight() {
+ Doc.UnhighlightAll();
+ document.removeEventListener("pointerdown", linkFollowUnhighlight);
+ }
+
+ let _lastDate = 0;
+ export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true) {
+ linkFollowUnhighlight();
+ (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs));
+ document.removeEventListener("pointerdown", linkFollowUnhighlight);
+ document.addEventListener("pointerdown", linkFollowUnhighlight);
+ const lastDate = _lastDate = Date.now();
+ window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000);
+ }
+
+ export class HighlightBrush {
+ @observable HighlightedDoc: Map<Doc, boolean> = new Map();
+ }
+ const highlightManager = new HighlightBrush();
+ export function IsHighlighted(doc: Doc) {
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false;
+ return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
+ }
+ export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
+ runInAction(() => {
+ highlightManager.HighlightedDoc.set(doc, true);
+ dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true);
+ });
+ }
+ export function UnHighlightDoc(doc: Doc) {
+ runInAction(() => {
+ highlightManager.HighlightedDoc.set(doc, false);
+ highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false);
+ });
+ }
+ export function UnhighlightAll() {
+ const mapEntries = highlightManager.HighlightedDoc.keys();
+ let docEntry: IteratorResult<Doc>;
+ while (!(docEntry = mapEntries.next()).done) {
+ const targetDoc = docEntry.value;
+ targetDoc && Doc.UnHighlightDoc(targetDoc);
+ }
+
+ }
+ export function UnBrushAllDocs() {
+ brushManager.BrushedDoc.clear();
+ }
+
+ export function getDocTemplate(doc?: Doc) {
+ return !doc ? undefined :
+ doc.isTemplateDoc ? doc :
+ Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory :
+ Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc ?
+ (Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc ? Doc.Layout(doc).proto : Doc.Layout(doc)) :
+ undefined;
+ }
+
+ export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
+ if (Utils.HasTransparencyFilter(value)) {
+ const isTransparent = (color: string) => color !== "" && (DashColor(color).alpha() !== 1);
+ return isTransparent(StrCast(doc[key]));
+ }
+ if (typeof value === "string") {
+ value = value.replace(`,${Utils.noRecursionHack}`, "");
+ }
+ const fieldVal = key === "#" ? (StrCast(doc.tags).includes(":#" + value + ":") ? StrCast(doc.tags) : undefined) : doc[key];
+ if (Cast(fieldVal, listSpec("string"), []).length) {
+ const vals = Cast(fieldVal, listSpec("string"), []);
+ const docs = vals.some(v => (v as any) instanceof Doc);
+ if (docs) return value === Field.toString(fieldVal as Field);
+ return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ }
+ const fieldStr = Field.toString(fieldVal as Field);
+ return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ }
+
+ export function deiconifyView(doc: Doc) {
+ StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
+ }
+
+ export function setNativeView(doc: any) {
+ const prevLayout = StrCast(doc.layoutKey).split("_")[1];
+ const deiconify = prevLayout === "icon" && StrCast(doc.deiconifyLayout) ? "layout_" + StrCast(doc.deiconifyLayout) : "";
+ prevLayout === "icon" && (doc.deiconifyLayout = undefined);
+ doc.layoutKey = deiconify || "layout";
+ }
+ export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: number[]) {
+ if (!container) return;
+ const docRangeFilters = Cast(container._docRangeFilters, listSpec("string"), []);
+ for (let i = 0; i < docRangeFilters.length; i += 3) {
+ if (docRangeFilters[i] === key) {
+ docRangeFilters.splice(i, 3);
+ break;
}
-
- }
- export function UnBrushAllDocs() {
- brushManager.BrushedDoc.clear();
- }
-
- export function getDocTemplate(doc?: Doc) {
- return !doc ? undefined :
- doc.isTemplateDoc ? doc :
- Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory :
- Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc ?
- (Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc ? Doc.Layout(doc).proto : Doc.Layout(doc)) :
- undefined;
- }
-
- export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
- if (Utils.HasTransparencyFilter(value)) {
- const isTransparent = (color: string) => color !== "" && (DashColor(color).alpha() !== 1);
- return isTransparent(StrCast(doc[key]));
+ }
+ if (range !== undefined) {
+ docRangeFilters.push(key);
+ docRangeFilters.push(range[0].toString());
+ docRangeFilters.push(range[1].toString());
+ container._docRangeFilters = new List<string>(docRangeFilters);
+ }
+ }
+
+ // filters document in a container collection:
+ // all documents with the specified value for the specified key are included/excluded
+ // based on the modifiers :"check", "x", undefined
+ export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists" | "unset", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
+ if (!container) return;
+ const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters";
+ const docFilters = Cast(container[filterField], listSpec("string"), []);
+ runInAction(() => {
+ for (let i = 0; i < docFilters.length; i++) {
+ const fields = docFilters[i].split(":"); // split key:value:modifier
+ if (fields[0] === key && (fields[1] === value || modifiers === "match")) {
+ if (fields[2] === modifiers && modifiers && fields[1] === value) {
+ if (toggle) modifiers = "remove";
+ else return;
+ }
+ docFilters.splice(i, 1);
+ container[filterField] = new List<string>(docFilters);
+ break;
+ }
}
- if (typeof value === "string") {
- value = value.replace(`,${Utils.noRecursionHack}`, "");
+ if (!docFilters.length && modifiers === "match" && value === undefined) {
+ container[filterField] = undefined;
+ } else if (modifiers !== "remove") {
+ !append && (docFilters.length = 0);
+ docFilters.push(key + ":" + value + ":" + modifiers);
+ container[filterField] = new List<string>(docFilters);
}
- const fieldVal = key === "#" ? (StrCast(doc.tags).includes(":#" + value + ":") ? StrCast(doc.tags) : undefined) : doc[key];
- if (Cast(fieldVal, listSpec("string"), []).length) {
- const vals = Cast(fieldVal, listSpec("string"), []);
- const docs = vals.some(v => (v as any) instanceof Doc);
- if (docs) return value === Field.toString(fieldVal as Field);
- return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ });
+ }
+ export function readDocRangeFilter(doc: Doc, key: string) {
+ const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []);
+ for (let i = 0; i < docRangeFilters.length; i += 3) {
+ if (docRangeFilters[i] === key) {
+ return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])];
}
- const fieldStr = Field.toString(fieldVal as Field);
- return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
- }
-
- export function deiconifyView(doc: Doc) {
- StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
- }
-
- export function setNativeView(doc: any) {
- const prevLayout = StrCast(doc.layoutKey).split("_")[1];
- const deiconify = prevLayout === "icon" && StrCast(doc.deiconifyLayout) ? "layout_" + StrCast(doc.deiconifyLayout) : "";
- prevLayout === "icon" && (doc.deiconifyLayout = undefined);
- doc.layoutKey = deiconify || "layout";
- }
- export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: number[]) {
- if (!container) return;
- const docRangeFilters = Cast(container._docRangeFilters, listSpec("string"), []);
- for (let i = 0; i < docRangeFilters.length; i += 3) {
- if (docRangeFilters[i] === key) {
- docRangeFilters.splice(i, 3);
- break;
- }
+ }
+ }
+ export function assignDocToField(doc: Doc, field: string, id: string) {
+ DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout));
+ return id;
+ }
+
+ export function toggleNativeDimensions(layoutDoc: Doc, contentScale: number, panelWidth: number, panelHeight: number) {
+ runInAction(() => {
+ if (Doc.NativeWidth(layoutDoc) || Doc.NativeHeight(layoutDoc)) {
+ layoutDoc._viewScale = NumCast(layoutDoc._viewScale, 1) * contentScale;
+ layoutDoc._nativeWidth = undefined;
+ layoutDoc._nativeHeight = undefined;
}
- if (range !== undefined) {
- docRangeFilters.push(key);
- docRangeFilters.push(range[0].toString());
- docRangeFilters.push(range[1].toString());
- container._docRangeFilters = new List<string>(docRangeFilters);
+ else {
+ layoutDoc._autoHeight = false;
+ if (!Doc.NativeWidth(layoutDoc)) {
+ layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth);
+ layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight);
+ }
}
- }
-
- // filters document in a container collection:
- // all documents with the specified value for the specified key are included/excluded
- // based on the modifiers :"check", "x", undefined
- export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists" | "unset", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
- if (!container) return;
- const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters";
- const docFilters = Cast(container[filterField], listSpec("string"), []);
- runInAction(() => {
- for (let i = 0; i < docFilters.length; i++) {
- const fields = docFilters[i].split(":"); // split key:value:modifier
- if (fields[0] === key && (fields[1] === value || modifiers === "match")) {
- if (fields[2] === modifiers && modifiers && fields[1] === value) {
- if (toggle) modifiers = "remove";
- else return;
- }
- docFilters.splice(i, 1);
- container[filterField] = new List<string>(docFilters);
- break;
- }
- }
- if (!docFilters.length && modifiers === "match" && value === undefined) {
- container[filterField] = undefined;
- } else if (modifiers !== "remove") {
- !append && (docFilters.length = 0);
- docFilters.push(key + ":" + value + ":" + modifiers);
- container[filterField] = new List<string>(docFilters);
- }
- });
- }
- export function readDocRangeFilter(doc: Doc, key: string) {
- const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []);
- for (let i = 0; i < docRangeFilters.length; i += 3) {
- if (docRangeFilters[i] === key) {
- return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])];
- }
+ });
+ }
+
+ export function isDocPinned(doc: Doc) {
+ //add this new doc to props.Document
+ const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+ return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1;
+ }
+
+ export function copyDragFactory(dragFactory: Doc) {
+ const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
+ ndoc && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(ndoc));
+ if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
+ dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
+ Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
+ }
+
+ if (ndoc) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc);
+
+ return ndoc;
+ }
+ export function delegateDragFactory(dragFactory: Doc) {
+ const ndoc = Doc.MakeDelegateWithProto(dragFactory);
+ if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
+ dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
+ Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString();
+ }
+ return ndoc;
+ }
+
+ export function toIcon(doc?: Doc, isOpen?: boolean) {
+ switch (StrCast(doc?.type)) {
+ case DocumentType.IMG: return "image";
+ case DocumentType.COMPARISON: return "columns";
+ case DocumentType.RTF: return "sticky-note";
+ case DocumentType.COL:
+ const folder: IconProp = isOpen ? "folder-open" : "folder";
+ const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right";
+ return !doc?.isFolder ? folder : chevron;
+ case DocumentType.WEB: return "globe-asia";
+ case DocumentType.SCREENSHOT: return "photo-video";
+ case DocumentType.WEBCAM: return "video";
+ case DocumentType.AUDIO: return "microphone";
+ case DocumentType.BUTTON: return "bolt";
+ case DocumentType.PRES: return "tv";
+ case DocumentType.SCRIPTING: return "terminal";
+ case DocumentType.IMPORT: return "cloud-upload-alt";
+ case DocumentType.VID: return "video";
+ case DocumentType.INK: return "pen-nib";
+ case DocumentType.PDF: return "file-pdf";
+ case DocumentType.LINK: return "link";
+ case DocumentType.MAP: return "map-marker-alt";
+ default: return "question";
+ }
+ }
+
+
+ export namespace Get {
+
+ const primitives = ["string", "number", "boolean"];
+
+ export interface JsonConversionOpts {
+ data: any;
+ title?: string;
+ appendToExisting?: { targetDoc: Doc, fieldKey?: string };
+ excludeEmptyObjects?: boolean;
+ }
+
+ const defaultKey = "json";
+
+ /**
+ * This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily
+ * deep levels of nesting, converts the data and structure into nested documents with the appropriate fields.
+ *
+ * After building a hierarchy within / below a top-level document, it then returns that top-level parent.
+ *
+ * If we've received a string, treat it like valid JSON and try to parse it into an object. If this fails, the
+ * string is invalid JSON, so we should assume that the input is the result of a JSON.parse()
+ * call that returned a regular string value to be stored as a Field.
+ *
+ * If we've received something other than a string, since the caller might also pass in the results of a
+ * JSON.parse() call, valid input might be an object, an array (still typeof object), a boolean or a number.
+ * Anything else (like a function, etc. passed in naively as any) is meaningless for this operation.
+ *
+ * All TS/JS objects get converted directly to documents, directly preserving the key value structure. Everything else,
+ * lacking the key value structure, gets stored as a field in a wrapper document.
+ *
+ * @param data for convenience and flexibility, either a valid JSON string to be parsed,
+ * or the result of any JSON.parse() call.
+ * @param title an optional title to give to the highest parent document in the hierarchy.
+ * If whether this function creates a new document or appendToExisting is specified and that document already has a title,
+ * because this title field can be left undefined for the opposite behavior, including a title will overwrite the existing title.
+ * @param appendToExisting **if specified**, there are two cases, both of which return the target document:
+ *
+ * 1) the json to be converted can be represented as a document, in which case the target document will act as the root
+ * of the tree and receive all the conversion results as new fields on itself
+ * 2) the json can't be represented as a document, in which case the function will assign the field-level conversion
+ * results to either the specified key on the target document, or to its "json" key by default.
+ *
+ * If not specified, the function creates and returns a new entirely generic document (different from the Doc.Create calls)
+ * to act as the root of the tree.
+ *
+ * One might choose to specify this field if you want to write to a document returned from a Document.Create function call,
+ * say a TreeView document that will be rendered, not just an untyped, identityless doc that would otherwise be created
+ * from a default call to new Doc.
+ *
+ * @param excludeEmptyObjects whether non-primitive objects (TypeScript objects and arrays) should be converted even
+ * if they contain no data. By default, empty objects and arrays are ignored.
+ */
+ export function FromJson({ data, title, appendToExisting, excludeEmptyObjects }: JsonConversionOpts): Opt<Doc> {
+ if (excludeEmptyObjects === undefined) {
+ excludeEmptyObjects = true;
}
- }
- export function assignDocToField(doc: Doc, field: string, id: string) {
- DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout));
- return id;
- }
-
- export function toggleNativeDimensions(layoutDoc: Doc, contentScale: number, panelWidth: number, panelHeight: number) {
- runInAction(() => {
- if (Doc.NativeWidth(layoutDoc) || Doc.NativeHeight(layoutDoc)) {
- layoutDoc._viewScale = NumCast(layoutDoc._viewScale, 1) * contentScale;
- layoutDoc._nativeWidth = undefined;
- layoutDoc._nativeHeight = undefined;
- }
- else {
- layoutDoc._autoHeight = false;
- if (!Doc.NativeWidth(layoutDoc)) {
- layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth);
- layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight);
- }
- }
- });
- }
-
- export function isDocPinned(doc: Doc) {
- //add this new doc to props.Document
- const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
- return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1;
- }
-
- export function copyDragFactory(dragFactory: Doc) {
- const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
- ndoc && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(ndoc));
- if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
- dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
- Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true);
- }
-
- if (ndoc) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc);
-
- return ndoc;
- }
- export function delegateDragFactory(dragFactory: Doc) {
- const ndoc = Doc.MakeDelegateWithProto(dragFactory);
- if (ndoc && dragFactory["dragFactory-count"] !== undefined) {
- dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1;
- Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString();
+ if (data === undefined || data === null || ![...primitives, "object"].includes(typeof data)) {
+ return undefined;
}
- return ndoc;
- }
-
- export function toIcon(doc?: Doc, isOpen?: boolean) {
- switch (StrCast(doc?.type)) {
- case DocumentType.IMG: return "image";
- case DocumentType.COMPARISON: return "columns";
- case DocumentType.RTF: return "sticky-note";
- case DocumentType.COL:
- const folder: IconProp = isOpen ? "folder-open" : "folder";
- const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right";
- return !doc?.isFolder ? folder : chevron;
- case DocumentType.WEB: return "globe-asia";
- case DocumentType.SCREENSHOT: return "photo-video";
- case DocumentType.WEBCAM: return "video";
- case DocumentType.AUDIO: return "microphone";
- case DocumentType.BUTTON: return "bolt";
- case DocumentType.PRES: return "tv";
- case DocumentType.SCRIPTING: return "terminal";
- case DocumentType.IMPORT: return "cloud-upload-alt";
- case DocumentType.VID: return "video";
- case DocumentType.INK: return "pen-nib";
- case DocumentType.PDF: return "file-pdf";
- case DocumentType.LINK: return "link";
- case DocumentType.MAP: return "map-marker-alt";
- default: return "question";
+ let resolved: any;
+ try {
+ resolved = JSON.parse(typeof data === "string" ? data : JSON.stringify(data));
+ } catch (e) {
+ return undefined;
}
- }
-
-
- export namespace Get {
-
- const primitives = ["string", "number", "boolean"];
-
- export interface JsonConversionOpts {
- data: any;
- title?: string;
- appendToExisting?: { targetDoc: Doc, fieldKey?: string };
- excludeEmptyObjects?: boolean;
+ let output: Opt<Doc>;
+ if (typeof resolved === "object" && !(resolved instanceof Array)) {
+ output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc);
+ } else {
+ // give the proper types to the data extracted from the JSON
+ const result = toField(resolved, excludeEmptyObjects);
+ if (appendToExisting) {
+ (output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result;
+ } else {
+ (output = new Doc).json = result;
+ }
}
-
- const defaultKey = "json";
-
- /**
- * This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily
- * deep levels of nesting, converts the data and structure into nested documents with the appropriate fields.
- *
- * After building a hierarchy within / below a top-level document, it then returns that top-level parent.
- *
- * If we've received a string, treat it like valid JSON and try to parse it into an object. If this fails, the
- * string is invalid JSON, so we should assume that the input is the result of a JSON.parse()
- * call that returned a regular string value to be stored as a Field.
- *
- * If we've received something other than a string, since the caller might also pass in the results of a
- * JSON.parse() call, valid input might be an object, an array (still typeof object), a boolean or a number.
- * Anything else (like a function, etc. passed in naively as any) is meaningless for this operation.
- *
- * All TS/JS objects get converted directly to documents, directly preserving the key value structure. Everything else,
- * lacking the key value structure, gets stored as a field in a wrapper document.
- *
- * @param data for convenience and flexibility, either a valid JSON string to be parsed,
- * or the result of any JSON.parse() call.
- * @param title an optional title to give to the highest parent document in the hierarchy.
- * If whether this function creates a new document or appendToExisting is specified and that document already has a title,
- * because this title field can be left undefined for the opposite behavior, including a title will overwrite the existing title.
- * @param appendToExisting **if specified**, there are two cases, both of which return the target document:
- *
- * 1) the json to be converted can be represented as a document, in which case the target document will act as the root
- * of the tree and receive all the conversion results as new fields on itself
- * 2) the json can't be represented as a document, in which case the function will assign the field-level conversion
- * results to either the specified key on the target document, or to its "json" key by default.
- *
- * If not specified, the function creates and returns a new entirely generic document (different from the Doc.Create calls)
- * to act as the root of the tree.
- *
- * One might choose to specify this field if you want to write to a document returned from a Document.Create function call,
- * say a TreeView document that will be rendered, not just an untyped, identityless doc that would otherwise be created
- * from a default call to new Doc.
- *
- * @param excludeEmptyObjects whether non-primitive objects (TypeScript objects and arrays) should be converted even
- * if they contain no data. By default, empty objects and arrays are ignored.
- */
- export function FromJson({ data, title, appendToExisting, excludeEmptyObjects }: JsonConversionOpts): Opt<Doc> {
- if (excludeEmptyObjects === undefined) {
- excludeEmptyObjects = true;
- }
- if (data === undefined || data === null || ![...primitives, "object"].includes(typeof data)) {
- return undefined;
- }
- let resolved: any;
- try {
- resolved = JSON.parse(typeof data === "string" ? data : JSON.stringify(data));
- } catch (e) {
- return undefined;
- }
- let output: Opt<Doc>;
- if (typeof resolved === "object" && !(resolved instanceof Array)) {
- output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc);
- } else {
- // give the proper types to the data extracted from the JSON
- const result = toField(resolved, excludeEmptyObjects);
- if (appendToExisting) {
- (output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result;
- } else {
- (output = new Doc).json = result;
+ title && output && (output.title = title);
+ return output;
+ }
+
+ /**
+ * For each value of the object, recursively convert it to its appropriate field value
+ * and store the field at the appropriate key in the document if it is not undefined
+ * @param object the object to convert
+ * @returns the object mapped from JSON to field values, where each mapping
+ * might involve arbitrary recursion (since toField might itself call convertObject)
+ */
+ const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt<Doc> => {
+ const hasEntries = Object.keys(object).length;
+ if (hasEntries || !excludeEmptyObjects) {
+ const resolved = target ?? new Doc;
+ if (hasEntries) {
+ let result: Opt<Field>;
+ Object.keys(object).map(key => {
+ // if excludeEmptyObjects is true, any qualifying conversions from toField will
+ // be undefined, and thus the results that would have
+ // otherwise been empty (List or Doc)s will just not be written
+ if (result = toField(object[key], excludeEmptyObjects, key)) {
+ resolved[key] = result;
}
- }
- title && output && (output.title = title);
- return output;
+ });
+ }
+ title && (resolved.title = title);
+ return resolved;
}
+ };
+
+ /**
+ * For each element in the list, recursively convert it to a document or other field
+ * and push the field to the list if it is not undefined
+ * @param list the list to convert
+ * @returns the list mapped from JSON to field values, where each mapping
+ * might involve arbitrary recursion (since toField might itself call convertList)
+ */
+ const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
+ const target = new List();
+ let result: Opt<Field>;
+ // if excludeEmptyObjects is true, any qualifying conversions from toField will
+ // be undefined, and thus the results that would have
+ // otherwise been empty (List or Doc)s will just not be written
+ list.map(item => (result = toField(item, excludeEmptyObjects)) && target.push(result));
+ if (target.length || !excludeEmptyObjects) {
+ return target;
+ }
+ };
- /**
- * For each value of the object, recursively convert it to its appropriate field value
- * and store the field at the appropriate key in the document if it is not undefined
- * @param object the object to convert
- * @returns the object mapped from JSON to field values, where each mapping
- * might involve arbitrary recursion (since toField might itself call convertObject)
- */
- const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt<Doc> => {
- const hasEntries = Object.keys(object).length;
- if (hasEntries || !excludeEmptyObjects) {
- const resolved = target ?? new Doc;
- if (hasEntries) {
- let result: Opt<Field>;
- Object.keys(object).map(key => {
- // if excludeEmptyObjects is true, any qualifying conversions from toField will
- // be undefined, and thus the results that would have
- // otherwise been empty (List or Doc)s will just not be written
- if (result = toField(object[key], excludeEmptyObjects, key)) {
- resolved[key] = result;
- }
- });
- }
- title && (resolved.title = title);
- return resolved;
- }
- };
-
- /**
- * For each element in the list, recursively convert it to a document or other field
- * and push the field to the list if it is not undefined
- * @param list the list to convert
- * @returns the list mapped from JSON to field values, where each mapping
- * might involve arbitrary recursion (since toField might itself call convertList)
- */
- const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
- const target = new List();
- let result: Opt<Field>;
- // if excludeEmptyObjects is true, any qualifying conversions from toField will
- // be undefined, and thus the results that would have
- // otherwise been empty (List or Doc)s will just not be written
- list.map(item => (result = toField(item, excludeEmptyObjects)) && target.push(result));
- if (target.length || !excludeEmptyObjects) {
- return target;
- }
- };
-
- const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<Field> => {
- if (data === null || data === undefined) {
- return undefined;
- }
- if (primitives.includes(typeof data)) {
- return data;
- }
- if (typeof data === "object") {
- return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined);
- }
- throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`);
- };
- }
+ const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<Field> => {
+ if (data === null || data === undefined) {
+ return undefined;
+ }
+ if (primitives.includes(typeof data)) {
+ return data;
+ }
+ if (typeof data === "object") {
+ return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined);
+ }
+ throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`);
+ };
+ }
}
@@ -1423,14 +1444,14 @@ ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a docum
ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); });
ScriptingGlobals.add(function docCast(doc: FieldResult): any { return DocCastAsync(doc); });
ScriptingGlobals.add(function activePresentationItem() {
- const curPres = Doc.UserDoc().activePresentation as Doc;
- return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
+ const curPres = Doc.UserDoc().activePresentation as Doc;
+ return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
});
ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
- const docs = SelectionManager.Views().map(dv => dv.props.Document).
- filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP &&
- (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
- return docs.length ? new List(docs) : prevValue;
+ const docs = SelectionManager.Views().map(dv => dv.props.Document).
+ filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP &&
+ (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
+ return docs.length ? new List(docs) : prevValue;
});
ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: "match" | "check" | "x" | "remove") { Doc.setDocFilter(container, key, value, modifiers); });
ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) { Doc.setDocRangeFilter(container, key, range); });
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 0ee0a34df..217c88107 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -260,48 +260,50 @@ export default class UploadManager extends ApiManager {
}
});
- register({
- method: Method.POST,
- subscription: "/uploadURI",
- secureHandler: ({ req, res }) => {
- const uri = req.body.uri;
- const filename = req.body.name;
- const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original;
- const deleteFiles = req.body.replaceRootFilename;
- if (!uri || !filename) {
- res.status(401).send("incorrect parameters specified");
- return;
- }
- if (deleteFiles) {
- const path = serverPathToFile(Directory.images, "");
- const regex = new RegExp(`${deleteFiles}.*`);
- fs.readdirSync(path).filter((f: any) => regex.test(f)).map((f: any) => fs.unlinkSync(path + f));
- }
- 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" },
- ];
- let isImage = false;
- if (pngs.includes(ext)) {
- resizers.forEach(element => {
- element.resizer = element.resizer.png();
- });
- isImage = true;
- } else if (jpgs.includes(ext)) {
- resizers.forEach(element => {
- element.resizer = element.resizer.jpeg();
- });
- isImage = true;
- }
- if (isImage) {
- resizers.forEach(resizer => {
- const path = serverPathToFile(Directory.images, filename + resizer.suffix + ext);
- createReadStream(savedName).pipe(resizer.resizer).pipe(createWriteStream(path));
- });
+ register({
+ method: Method.POST,
+ subscription: "/uploadURI",
+ secureHandler: ({ req, res }) => {
+ const uri = req.body.uri;
+ const filename = req.body.name;
+ const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original;
+ const deleteFiles = req.body.replaceRootFilename;
+ if (!uri || !filename) {
+ res.status(401).send("incorrect parameters specified");
+ return;
+ }
+ if (deleteFiles) {
+ const path = serverPathToFile(Directory.images, "");
+ const regex = new RegExp(`${deleteFiles}.*`);
+ fs.readdirSync(path).filter((f: any) => regex.test(f)).map((f: any) => fs.unlinkSync(path + f));
+ }
+ 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: 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)) {
+ resizers.forEach(element => {
+ element.resizer = element.resizer.png();
+ });
+ isImage = true;
+ } else if (jpgs.includes(ext)) {
+ resizers.forEach(element => {
+ element.resizer = element.resizer.jpeg();
+ });
+ isImage = true;
+ }
+ if (isImage) {
+ resizers.forEach(resizer => {
+ 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)));
+ });
}
res.send(clientPathToFile(Directory.images, filename + ext));
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 24cc3b796..fd000a83c 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -171,54 +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 htmlText = zipToStringDecoder.write(zlib.gunzipSync(htmlBodyMemoryStream.read()).toString('utf8')
- // .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
- // .replace(/href="http([^"]*)"/g, replacer)
- // .replace(/target="_blank"/g, ""));
- // rewrittenHtmlBody = zlib.gzipSync(htmlText);
- 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="http([^"]*)"/g, replacer)
- .replace(/target="_blank"/g, ""));
- rewrittenHtmlBody = zlib.gzipSync(htmlText);
- } else {
- console.log("EMPTY body: href");
- }
- } catch (e) { console.log(e); }
- }
- });
- })
- .on('data', (e: any) => {
- rewrittenHtmlBody && response.send(rewrittenHtmlBody);
- rewrittenHtmlBody = undefined;
- })
- .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) {
@@ -226,12 +225,17 @@ function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
const relativeUrl = req.originalUrl;
if (!req.user) res.redirect("/home"); // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home
else if (!res.headersSent && req.headers.referer?.includes("corsProxy")) { // a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here.
- 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 absoluteTargetBaseUrl = actualReferUrl.match(/http[s]?:\/\/[^\/]*/)![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>)
- res.redirect(redirectedProxiedUrl);
+ 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., : 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>)
+ if (relativeUrl.startsWith("//")) res.redirect("http:" + relativeUrl);
+ else res.redirect(redirectedProxiedUrl);
+ } catch (e) {
+ console.log("Error embed: ", e);
+ }
} else if (relativeUrl.startsWith("/search") && !req.headers.referer?.includes("corsProxy")) { // detect search query and use default search engine
res.redirect(req.headers.referer + "corsProxy/" + encodeURIComponent("http://www.google.com" + relativeUrl));
} else {
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';