aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts57
-rw-r--r--src/client/documents/Documents.ts25
-rw-r--r--src/client/util/CurrentUserUtils.ts39
-rw-r--r--src/client/util/DragManager.ts4
-rw-r--r--src/client/views/DocComponent.tsx13
-rw-r--r--src/client/views/DocumentButtonBar.tsx16
-rw-r--r--src/client/views/GestureOverlay.tsx9
-rw-r--r--src/client/views/InkingStroke.tsx17
-rw-r--r--src/client/views/LightboxView.tsx7
-rw-r--r--src/client/views/MarqueeAnnotator.tsx67
-rw-r--r--src/client/views/PropertiesButtons.scss1
-rw-r--r--src/client/views/PropertiesButtons.tsx590
-rw-r--r--src/client/views/TemplateMenu.tsx7
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx2
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx6
-rw-r--r--src/client/views/collections/CollectionPileView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx4
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx8
-rw-r--r--src/client/views/collections/CollectionSubView.tsx21
-rw-r--r--src/client/views/collections/CollectionView.tsx14
-rw-r--r--src/client/views/collections/SchemaTable.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx10
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx13
-rw-r--r--src/client/views/nodes/FontIconBox.tsx10
-rw-r--r--src/client/views/nodes/ImageBox.tsx84
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/nodes/PresBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.tsx9
-rw-r--r--src/client/views/nodes/WebBox.tsx414
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx23
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx1
-rw-r--r--src/client/views/pdf/PDFViewer.tsx13
33 files changed, 510 insertions, 986 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index f22df0da2..ef9c51b8b 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -482,31 +482,20 @@ const easeInOutQuad = (currentTime: number, start: number, change: number, durat
return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
};
-export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, finish?: () => void, reset?: { resetGoTo: { to: number, duration: number } | undefined }) {
+export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number) {
const elements = (element instanceof HTMLElement ? [element] : element);
- let starts = elements.map(element => element.scrollTop);
- let startDate = new Date().getTime();
+ const starts = elements.map(element => element.scrollTop);
+ const startDate = new Date().getTime();
const animateScroll = () => {
const currentDate = new Date().getTime();
- let currentTime = currentDate - startDate;
- const resetParams = reset?.resetGoTo;
- if (resetParams) {
- reset!.resetGoTo = undefined;
- const { to: newTo, duration: newDuration } = resetParams;
- to = newTo;
- starts = starts.map(start => easeInOutQuad(currentTime, start, to - start, duration));
- startDate = currentDate;
- duration = newDuration;
- currentTime = currentDate - startDate;
- }
+ const currentTime = currentDate - startDate;
elements.map((element, i) => element.scrollTop = easeInOutQuad(currentTime, starts[i], to - starts[i], duration));
if (currentTime < duration) {
requestAnimationFrame(animateScroll);
} else {
elements.forEach(element => element.scrollTop = to);
- finish?.();
}
};
animateScroll();
@@ -609,6 +598,44 @@ export function lightOrDark(color: any) {
}
}
+
+export function getWordAtPoint(elem: any, x: number, y: number): string | undefined {
+ if (elem.nodeType === elem.TEXT_NODE) {
+ const range = elem.ownerDocument.createRange();
+ range.selectNodeContents(elem);
+ var currentPos = 0;
+ const endPos = range.endOffset;
+ while (currentPos + 1 < endPos) {
+ range.setStart(elem, currentPos);
+ range.setEnd(elem, currentPos + 1);
+ const rangeRect = range.getBoundingClientRect();
+ if (rangeRect.left <= x && rangeRect.right >= x &&
+ rangeRect.top <= y && rangeRect.bottom >= y) {
+ range.expand?.("word"); // doesn't exist in firefox
+ const ret = range.toString();
+ range.detach();
+ return (ret);
+ }
+ currentPos += 1;
+ }
+ } else {
+ for (const childNode of elem.childNodes) {
+ const range = childNode.ownerDocument.createRange();
+ range.selectNodeContents(childNode);
+ const rangeRect = range.getBoundingClientRect();
+ if (rangeRect.left <= x && rangeRect.right >= x &&
+ rangeRect.top <= y && rangeRect.bottom >= y) {
+ range.detach();
+ const word = getWordAtPoint(childNode, x, y);
+ if (word) return word;
+ } else {
+ range.detach();
+ }
+ }
+ }
+ return undefined;
+}
+
export function hasDescendantTarget(x: number, y: number, target: HTMLDivElement | null) {
let entered = false;
for (let child = document.elementFromPoint(x, y); !entered && child; child = child.parentElement) {
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index baa7aa43b..0f1abb3ac 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -175,12 +175,11 @@ export class DocumentOptions {
hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden
isTemplateForField?: string; // the field key for which the containing document is a rendering template
isTemplateDoc?: boolean;
- watchedDocuments?: Doc; // list of documents to "watch" in an icon doc to display a badge
+ watchedDocuments?: Doc; // list of documents an icon doc monitors in order to display a badge count
targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc)
templates?: List<string>;
hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images)
caption?: RichTextField;
- isAnnotating?: boolean; // whether we web document is annotation mode where links can't be clicked to allow annotations to be created
opacity?: number;
defaultBackgroundColor?: string;
isLinkButton?: boolean;
@@ -325,7 +324,7 @@ export namespace Docs {
}],
[DocumentType.WEB, {
layout: { view: WebBox, dataField: defaultDataKey },
- options: { _height: 300, scrollHeight: 100000, _fitWidth: true }
+ options: { _height: 300, _fitWidth: true }
}],
[DocumentType.COL, {
layout: { view: CollectionView, dataField: defaultDataKey },
@@ -823,7 +822,7 @@ export namespace Docs {
const webProto = Prototypes.get(DocumentType.WEB);
webProto.scrollHeight = 100000; // backward compatibility -- can be removed after db is reset
webProto._fitWidth = true; // backward compatibility -- can be removed after db is reset
- return InstanceFromProto(webProto, url ? new WebField(new URL(url)) : undefined, { _chromeStatus: url ? "disabled" : "enabled", isAnnotating: false, _lockedTransform: true, ...options });
+ return InstanceFromProto(webProto, url ? new WebField(new URL(url)) : undefined, { _chromeStatus: url ? undefined : "enabled", _lockedTransform: true, ...options });
}
export function HtmlDocument(html: string, options: DocumentOptions = {}) {
@@ -1373,6 +1372,24 @@ export namespace DocUtils {
}
}
+ export function LeavePushpin(doc: Doc) {
+ if (doc.isPushpin) return undefined;
+ const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null);
+ const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context));
+ if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
+ const pushpin = Docs.Create.FontIconDocument({
+ title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true,
+ icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), backgroundColor: "#ACCEF7",
+ _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null)
+ });
+ Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
+ const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
+ doc._timecodeToShow = undefined;
+ return pushpin;
+ }
+ 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)) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 23cb9634b..31c70427c 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -81,7 +81,7 @@ export class CurrentUserUtils {
Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }),
Docs.Create.TextDocument("", { title: "text", _height: 100, system: true })
],
- { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, system: true }
+ { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true }
);
slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
doc["template-button-slides"] = CurrentUserUtils.ficon({
@@ -193,7 +193,7 @@ export class CurrentUserUtils {
};
details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" "));
- const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 };
+ const shared = { _autoHeight: true, _xMargin: 0 };
const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12px" };
const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title", system: true };
@@ -228,7 +228,7 @@ export class CurrentUserUtils {
title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title",
hidden: ComputedField.MakeFunction("IsNoviceMode()") as any,
_stayInCollection: true, _hideContextMenu: true,
- _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, _chromeStatus: "disabled",
+ _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 {
@@ -499,7 +499,7 @@ export class CurrentUserUtils {
if (dragCreatorSet === undefined) {
doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true,
- _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, _chromeStatus: "disabled",
+ _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 {
@@ -528,7 +528,7 @@ export class CurrentUserUtils {
if (doc.mySearchPanelDoc === undefined) {
doc.mySearchPanelDoc = new PrefetchProxy(Docs.Create.SearchDocument({
_width: 500, _height: 300, backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true,
- childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, _chromeStatus: "disabled", title: "sidebar search stack", system: true
+ childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "sidebar search stack", system: true
})) as any as Doc;
}
}
@@ -563,7 +563,7 @@ export class CurrentUserUtils {
backgroundColor: "black", ignoreClick: true,
_gridGap: 0,
_yMargin: 0,
- _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, _chromeStatus: "disabled", system: true
+ _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true
}));
}
// this resets all sidebar buttons to being deactivated
@@ -596,7 +596,7 @@ export class CurrentUserUtils {
// Sets up mobileMenu stacking document
static setupMobileMenu() {
const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), {
- _width: 980, ignoreClick: true, _lockedPosition: false, _chromeStatus: "disabled", title: "home", _yMargin: 100, system: true
+ _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true
}));
return menu;
}
@@ -627,26 +627,26 @@ export class CurrentUserUtils {
static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
...opts,
dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
- borderRounding: "5px", boxShadow: "0 0", _chromeStatus: "disabled", system: true
+ borderRounding: "5px", boxShadow: "0 0", system: true
}) as any as Doc
// sets up the text container for the information contained within the mobile button
static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, {
...opts,
dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
- backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", _chromeStatus: "disabled", ignoreClick: true, system: true
+ backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", ignoreClick: true, system: true
}) as any as Doc
// Sets up the title of the button
static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
...opts,
- dropAction: undefined, title: buttonTitle, _fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", system: true
+ dropAction: undefined, title: buttonTitle, _fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", system: true
}) as any as Doc
// Sets up the description of the button
static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
...opts,
- dropAction: undefined, title: "info", _fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true
+ dropAction: undefined, title: "info", _fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true
}) as any as Doc
@@ -671,11 +671,11 @@ export class CurrentUserUtils {
static setupThumbDoc(userDoc: Doc) {
if (!userDoc.thumbDoc) {
const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
- _width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, _chromeStatus: "disabled", title: "buttons",
+ _width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, title: "buttons",
_autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true
});
thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], {
- _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", linearViewIsExpanded: true, flexDirection: "column", system: true
+ _width: 300, _height: 25, _autoHeight: true, linearViewIsExpanded: true, flexDirection: "column", system: true
});
userDoc.thumbDoc = thumbDoc;
}
@@ -695,7 +695,7 @@ export class CurrentUserUtils {
title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true
});
return Docs.Create.StackingDocument([webDoc, uploadDoc], {
- _width: screen.width, _lockedPosition: true, _chromeStatus: "disabled", title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true
+ _width: screen.width, _lockedPosition: true, title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true
});
}
@@ -715,7 +715,7 @@ export class CurrentUserUtils {
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, _chromeStatus: "disabled", system: true
+ _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true
}));
}
// setup a color picker
@@ -728,7 +728,7 @@ export class CurrentUserUtils {
if (doc.myTools === undefined) {
const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], {
- title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _chromeStatus: "disabled", _forceActive: true, system: true, _stayInCollection: true, _hideContextMenu: true,
+ title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, system: true, _stayInCollection: true, _hideContextMenu: true,
})) as any as Doc;
doc.myTools = toolsStack;
@@ -830,7 +830,6 @@ export class CurrentUserUtils {
static setupSidebarContainer(doc: Doc) {
if (doc.sidebar === undefined) {
const sidebarContainer = new Doc();
- sidebarContainer._chromeStatus = "disabled";
sidebarContainer.system = true;
doc.sidebar = new PrefetchProxy(sidebarContainer);
}
@@ -852,7 +851,7 @@ export class CurrentUserUtils {
static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, {
...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", _forceActive: true,
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
- backgroundColor: "black", treeViewPreventOpen: true, _lockedPosition: true, _chromeStatus: "disabled", linearViewIsExpanded: true, system: true
+ backgroundColor: "black", treeViewPreventOpen: true, _lockedPosition: true, linearViewIsExpanded: true, system: true
})) as any as Doc
static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({
@@ -922,7 +921,7 @@ export class CurrentUserUtils {
if (doc.myImportDocs === undefined) {
doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
title: "My ImportDocuments", _forceActive: true, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
- childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, _chromeStatus: "disabled", system: true
+ childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, system: true
}));
}
if (doc.myImportPanel === undefined) {
@@ -1200,7 +1199,7 @@ export class CurrentUserUtils {
_width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
_fontFamily: StrCast(Doc.UserDoc().fontFamily), title
});
- const template = FormattedTextBox.DefaultLayout;
+ const template = Doc.UserDoc().defaultTextLayout;
if (template instanceof Doc) {
tbox._width = NumCast(template._width);
tbox.layoutKey = "layout_" + StrCast(template.title);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0d154bc3a..dc95193ea 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -157,8 +157,8 @@ export namespace DragManager {
// used by PDFs,Text,Image,Video,Web to conditionally (if the drop completes) create a text annotation when dragging the annotate button from the AnchorMenu when a text/region selection has been made.
// this is pretty clunky and should be rethought out using linkDrag or DocumentDrag
export class AnchorAnnoDragData extends LinkDragData {
- constructor(dragDoc: Doc, linkSourceGetAnchor: () => Doc, dropDocCreator: (annotationOn: Doc | undefined) => Doc) {
- super(dragDoc, linkSourceGetAnchor);
+ constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc, dropDocCreator: (annotationOn: Doc | undefined) => Doc) {
+ super(dragView, linkSourceGetAnchor);
this.dropDocCreator = dropDocCreator;
this.offset = [0, 0];
}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index f8aede717..876fbac54 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -9,6 +9,7 @@ import { DateField } from '../../fields/DateField';
import { ScriptField } from '../../fields/ScriptField';
import { GetEffectiveAcl, SharingPermissions, distributeAcls, denormalizeEmail } from '../../fields/util';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
+import { DocUtils } from '../documents/Documents';
/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -127,16 +128,15 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
public get annotationKey() { return this.fieldKey + "-" + this._annotationKey; }
@action.bound
- removeDocument(doc: Doc | Doc[], annotationKey?: string): boolean {
+ 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 => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin);
if (docs.length) {
- const docs = doc instanceof Doc ? [doc] : doc;
- docs.map(doc => {
+ 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.SetInPlace(doc, "annotationOn", 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));
@@ -144,6 +144,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
if (toRemove.length !== 0) {
const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
toRemove.forEach(doc => {
+ leavePushpin && DocUtils.LeavePushpin(doc);
Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
doc.context = undefined;
recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
@@ -159,7 +160,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
// otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection.
@action.bound
moveDocument(doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean, annotationKey?: string): boolean {
- return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc, annotationKey) ? addDocument(doc) : false;
+ return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc, annotationKey, true) ? addDocument(doc) : false;
}
@action.bound
addDocument(doc: Doc | Doc[], annotationKey?: string): boolean {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 5ebf29603..4a1ec4d6c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -155,7 +155,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
e.preventDefault();
let googleDoc = await Cast(dataDoc.googleDoc, Doc);
if (!googleDoc) {
- const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, useCors: false };
+ const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, useCors: false };
googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
dataDoc.googleDoc = googleDoc;
}
@@ -250,17 +250,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
}
@computed
- get annotateButton() {
- const targetDoc = this.view0?.props.Document;
- const isAnnotating = targetDoc?.isAnnotating;
- return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${isAnnotating ? "Exit" : "Enter"} annotation mode`}</div></>}>
- <div className="documentButtonBar-linker" style={{ backgroundColor: isAnnotating ? "white" : "", color: isAnnotating ? "black" : "white", }}
- onClick={e => targetDoc.isAnnotating = !targetDoc.isAnnotating}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="edit" />
- </div></Tooltip >;
- }
-
- @computed
get menuButton() {
const targetDoc = this.view0?.props.Document;
return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`Open Context Menu`}</div></>}>
@@ -377,9 +366,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
{!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
{this.shareButton}
</div>}
- {![DocumentType.VID, DocumentType.WEB].includes(StrCast(this.view0.props.Document.type) as DocumentType) ? (null) : <div className="documentButtonBar-button">
- {this.annotateButton}
- </div>}
{!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
{this.considerGoogleDocsPush}
</div>}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 093e01a19..f5fac17a9 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -8,22 +8,22 @@ import { Cast, FieldValue, NumCast } from "../../fields/Types";
import MobileInkOverlay from "../../mobile/MobileInkOverlay";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { MobileInkOverlayContent } from "../../server/Message";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, emptyPath } from "../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { DocUtils } from "../documents/Documents";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InteractionUtils } from "../util/InteractionUtils";
-import { LinkManager } from "../util/LinkManager";
import { Scripting } from "../util/Scripting";
import { Transform } from "../util/Transform";
import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
import "./GestureOverlay.scss";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowEnd, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke";
import { DocumentView } from "./nodes/DocumentView";
import { RadialMenu } from "./nodes/RadialMenu";
import HorizontalPalette from "./Palette";
import { Touchable } from "./Touchable";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
+import { SelectionManager } from "../util/SelectionManager";
@observer
export class GestureOverlay extends Touchable {
@@ -827,7 +827,8 @@ export class GestureOverlay extends Touchable {
}
@computed get elements() {
- const width = Number(ActiveInkWidth());
+ const selView = SelectionManager.Views().lastElement();
+ const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc.viewScale, 1) / (selView?.props.ScreenToLocalTransform().Scale || 1);
const rect = this._overlayRef.current?.getBoundingClientRect();
const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
B.left = B.left - width / 2;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 46586bf1b..34cdb50e3 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -28,19 +28,18 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
-
-
private analyzeStrokes = () => {
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
}
- private makeMask = () => {
- this.props.Document.mixBlendMode = "hard-light";
- this.props.Document.color = "#9b9b9bff";
- //this.props.Document._stayInCollection = true;
- this.props.Document.isInkMask = true;
- }
+ public static toggleMask = action((inkDoc: Doc) => {
+ inkDoc.isInkMask = !inkDoc.isInkMask;
+ inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
+ inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined;
+ inkDoc.color = "#9b9b9bff";
+ inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
+ })
public _prevX = 0;
public _prevY = 0;
@@ -207,7 +206,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const cm = ContextMenu.Instance;
if (cm) {
!Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
- cm.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
+ cm.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
cm.addItem({ description: "Edit Points", event: action(() => formatInstance._controlBtn = !formatInstance._controlBtn), icon: "paint-brush" });
//cm.addItem({ description: "Format Shape...", event: this.formatShape, icon: "paint-brush" });
}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 9b14c180e..731d46502 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -30,15 +30,16 @@ export class LightboxView extends React.Component<LightboxViewProps> {
@observable private static _docTarget: Opt<Doc>;
@observable private static _docFilters: string[] = []; // filters
@observable private static _tourMap: Opt<Doc[]> = []; // list of all tours available from the current target
- private static _savedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number> }>;
+ private static _savedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, scrollTop: Opt<number> }>;
private static _history: Opt<{ doc: Doc, target?: Doc }[]> = [];
private static _future: Opt<Doc[]> = [];
private static _docView: Opt<DocumentView>;
- 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> }> }[] = [];
+ 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[]) {
if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) {
this.LightboxDoc._panX = this._savedState.panX;
this.LightboxDoc._panY = this._savedState.panY;
+ this.LightboxDoc._scrollTop = this._savedState.scrollTop;
this.LightboxDoc._viewScale = this._savedState.scale;
this.LightboxDoc._viewTransition = undefined;
}
@@ -53,6 +54,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
panX: Cast(doc._panX, "number", null),
panY: Cast(doc._panY, "number", null),
scale: Cast(doc._viewScale, "number", null),
+ scrollTop: Cast(doc._scrollTop, "number", null),
};
}
}
@@ -123,6 +125,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
LightboxView.LightboxDoc._panX = saved.panX;
LightboxView.LightboxDoc._panY = saved.panY;
LightboxView.LightboxDoc._viewScale = saved.scale;
+ LightboxView.LightboxDoc._scrollTop = saved.scrollTop;
LightboxView.LightboxDoc._viewTransition = undefined;
}
const pop = LightboxView.path.pop();
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 503c284aa..1e97f9b41 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,20 +1,22 @@
-import { action, observable, runInAction } from "mobx";
+import { action, observable, runInAction, ObservableMap } from "mobx";
import { observer } from "mobx-react";
import { Dictionary } from "typescript-collections";
import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
+import { List } from "../../fields/List";
+import { NumCast } from "../../fields/Types";
import { GetEffectiveAcl } from "../../fields/util";
-import { DocUtils, Docs } from "../documents/Documents";
+import { Utils } from "../../Utils";
+import { Docs } from "../documents/Documents";
+import { DocumentType } from "../documents/DocumentTypes";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { DragManager } from "../util/DragManager";
+import { undoBatch } from "../util/UndoManager";
+import "./MarqueeAnnotator.scss";
+import { DocumentView } from "./nodes/DocumentView";
import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
import { AnchorMenu } from "./pdf/AnchorMenu";
-import "./MarqueeAnnotator.scss";
import React = require("react");
-import { undoBatch } from "../util/UndoManager";
-import { NumCast } from "../../fields/Types";
-import { DocumentType } from "../documents/DocumentTypes";
-import { List } from "../../fields/List";
const _global = (window /* browser */ || global /* node */) as any;
export interface MarqueeAnnotatorProps {
@@ -24,11 +26,12 @@ export interface MarqueeAnnotatorProps {
scaling?: () => number;
containerOffset?: () => number[];
mainCont: HTMLDivElement;
- savedAnnotations: Dictionary<number, HTMLDivElement[]>;
+ docView: DocumentView;
+ savedAnnotations: ObservableMap<number, HTMLDivElement[]>;
annotationLayer: HTMLDivElement;
addDocument: (doc: Doc) => boolean;
getPageFromScroll?: (top: number) => number;
- finishMarquee: () => void;
+ finishMarquee: (x?: number, y?: number) => void;
anchorMenuClick?: (anchor: Doc) => void;
}
@observer
@@ -46,7 +49,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
AnchorMenu.Instance.Status = "marquee";
AnchorMenu.Instance.fadeOut(true);
// clear out old marquees and initialize menu for new selection
- this.props.savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ Array.from(this.props.savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
this.props.savedAnnotations.clear();
});
}
@@ -57,8 +60,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
this._startX = this._left = (this.props.down[0] - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width);
this._startY = this._top = (this.props.down[1] - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop;
this._height = this._width = 0;
- document.addEventListener("pointermove", this.onSelectMove);
- document.addEventListener("pointerup", this.onSelectEnd);
+ document.addEventListener("pointermove", this.onSelectMove, true);
+ document.addEventListener("pointerup", this.onSelectEnd, true);
AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.(this.highlight("rgba(173, 216, 230, 0.75)", true));
AnchorMenu.Instance.Highlight = this.highlight;
@@ -79,7 +82,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
FormattedTextBox.SelectOnLoad = target[Id];
return target;
};
- DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
+ DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
dragComplete: e => {
if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
@@ -89,17 +92,17 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
});
}
componentWillUnmount() {
- document.removeEventListener("pointermove", this.onSelectMove);
- document.removeEventListener("pointerup", this.onSelectEnd);
+ document.removeEventListener("pointermove", this.onSelectMove, true);
+ document.removeEventListener("pointerup", this.onSelectEnd, true);
}
@undoBatch
@action
makeAnnotationDocument = (color: string): Opt<Doc> => {
- if (this.props.savedAnnotations.size() === 0) return undefined;
- if ((this.props.savedAnnotations.values()[0][0] as any).marqueeing) {
+ if (this.props.savedAnnotations.size === 0) return undefined;
+ if ((Array.from(this.props.savedAnnotations.values())[0][0] as any).marqueeing) {
const scale = this.props.scaling?.() || 1;
- const anno = this.props.savedAnnotations.values()[0][0];
+ const anno = Array.from(this.props.savedAnnotations.values())[0][0];
const containerOffset = this.props.containerOffset?.() || [0, 0];
const marqueeAnno = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title });
marqueeAnno.x = (parseInt(anno.style.left || "0") - containerOffset[0]) / scale;
@@ -115,7 +118,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
let maxX = -Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
const annoDocs: Doc[] = [];
- this.props.savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => value.map(anno => {
+ this.props.savedAnnotations.forEach((value: HTMLDivElement[], key: number) => value.map(anno => {
const textRegion = new Doc();
textRegion.x = parseInt(anno.style.left ?? "0");
textRegion.y = parseInt(anno.style.top ?? "0");
@@ -147,20 +150,20 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
return annotationDoc as Doc ?? undefined;
}
- public static previewNewAnnotation = action((savedAnnotations: Dictionary<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => {
+ public static previewNewAnnotation = action((savedAnnotations: ObservableMap<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => {
if (div.style.top) {
div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString();
}
annotationLayer.append(div);
div.style.backgroundColor = "#ACCEF7";
div.style.opacity = "0.5";
- const savedPage = savedAnnotations.getValue(page);
+ const savedPage = savedAnnotations.get(page);
if (savedPage) {
savedPage.push(div);
- savedAnnotations.setValue(page, savedPage);
+ savedAnnotations.set(page, savedPage);
}
else {
- savedAnnotations.setValue(page, [div]);
+ savedAnnotations.set(page, [div]);
}
});
@@ -178,15 +181,20 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
}
onSelectEnd = (e: PointerEvent) => {
- if (!e.ctrlKey) {
- AnchorMenu.Instance.Marquee = { left: this._left, top: this._top, width: this._width, height: this._height };
- }
-
if (this._width > 10 || this._height > 10) { // configure and show the annotation/link menu if a the drag region is big enough
const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox");
if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though).
const copy = document.createElement("div");
- ["left", "top", "width", "height", "border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]);
+ ["border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]);
+ const bounds = (marquees[0] as HTMLDivElement).getBoundingClientRect();
+ const uitls = Utils.GetScreenTransform(marquees[0] as HTMLDivElement);
+ const rbounds = { top: uitls.translateY, left: uitls.translateX, width: (bounds.right - bounds.left), height: (bounds.bottom - bounds.top) };
+ const otls = Utils.GetScreenTransform(this.props.annotationLayer);
+ const fbounds = { top: (rbounds.top - otls.translateY) / otls.scale, left: (rbounds.left - otls.translateX) / otls.scale, width: rbounds.width / otls.scale, height: rbounds.height / otls.scale };
+ copy.style.top = fbounds.top.toString() + "px";
+ copy.style.left = fbounds.left.toString() + "px";
+ copy.style.width = fbounds.width.toString() + "px";
+ 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);
@@ -197,10 +205,11 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
if (AnchorMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
this.highlight("rgba(245, 230, 95, 0.75)", false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color)
}
+ this.props.finishMarquee();
} else {
runInAction(() => this._width = this._height = 0);
+ this.props.finishMarquee(e.clientX, e.clientY);
}
- this.props.finishMarquee();
}
render() {
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss
index ba212da7f..29d2bfcb7 100644
--- a/src/client/views/PropertiesButtons.scss
+++ b/src/client/views/PropertiesButtons.scss
@@ -63,6 +63,7 @@ $linkGap : 3px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
+ padding-bottom: 5.5px;
}
.onClickFlyout-editScript {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index bf72dbdba..fbcd55c47 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -1,557 +1,163 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from '@material-ui/core';
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../fields/Doc";
+import { Doc, Opt } from "../../fields/Doc";
import { InkField } from '../../fields/InkField';
import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast, StrCast } from "../../fields/Types";
-import { ImageField } from '../../fields/URLField';
-import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
-import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { GooglePhotos } from '../apis/google_docs/GooglePhotosClientUtils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { BoolCast, StrCast } from "../../fields/Types";
+import { DocUtils } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
import { SelectionManager } from '../util/SelectionManager';
import { undoBatch } from '../util/UndoManager';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
+import { CollectionViewType } from './collections/CollectionView';
+import { InkingStroke } from './InkingStroke';
+import { DocumentView } from './nodes/DocumentView';
import './PropertiesButtons.scss';
import React = require("react");
-import { CollectionViewType } from './collections/CollectionView';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-
-const cloud: IconProp = "cloud-upload-alt";
-const fetch: IconProp = "sync-alt";
-
enum UtilityButtonState {
Default,
OpenRight,
OpenExternally
}
-
@observer
export class PropertiesButtons extends React.Component<{}, {}> {
- private _pullAnimating = false;
- private _pushAnimating = false;
- private _pullColorAnimating = false;
-
- @observable private pushIcon: IconProp = "arrow-alt-circle-up";
- @observable private pullIcon: IconProp = "arrow-alt-circle-down";
- @observable private pullColor: string = "white";
- @observable public isAnimatingFetch = false;
- @observable public isAnimatingPulse = false;
-
- @observable private openHover: UtilityButtonState = UtilityButtonState.Default;
-
@observable public static Instance: PropertiesButtons;
- public static hasPushedHack = false;
- public static hasPulledHack = false;
-
-
- @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; }
- @computed get selectedDocumentView() {
- if (SelectionManager.Views().length) {
- return SelectionManager.Views()[0];
- } else return undefined;
- }
- @computed get onClick() { return this.selectedDoc?.onClickBehavior ? this.selectedDoc?.onClickBehavior : "nothing"; }
+ @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc; }
- public startPullOutcome = action((success: boolean) => {
- if (!this._pullAnimating) {
- this._pullAnimating = true;
- this.pullIcon = success ? "check-circle" : "stop-circle";
- setTimeout(() => runInAction(() => {
- this.pullIcon = "arrow-alt-circle-down";
- this._pullAnimating = false;
- }), 1000);
- }
- });
-
- public startPushOutcome = action((success: boolean) => {
- this.isAnimatingPulse = false;
- if (!this._pushAnimating) {
- this._pushAnimating = true;
- this.pushIcon = success ? "check-circle" : "stop-circle";
- setTimeout(() => runInAction(() => {
- this.pushIcon = "arrow-alt-circle-up";
- this._pushAnimating = false;
- }), 1000);
- }
- });
-
- public setPullState = action((unchanged: boolean) => {
- this.isAnimatingFetch = false;
- if (!this._pullColorAnimating) {
- this._pullColorAnimating = true;
- this.pullColor = unchanged ? "lawngreen" : "red";
- setTimeout(this.clearPullColor, 1000);
- }
- });
-
- private clearPullColor = action(() => {
- this.pullColor = "white";
- this._pullColorAnimating = false;
- });
-
- @computed
- get considerGoogleDocsPush() {
+ propertyToggleBtn = (label: string, property: string, tooltip: (on?: any) => string, icon: (on: boolean) => string, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void) => {
const targetDoc = this.selectedDoc;
- const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
- const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none";
- return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div>} placement="top">
- <div>
- <div
- className="propertiesButtons-linker"
- style={{ animation }}
- onClick={async () => {
- await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
- !published && runInAction(() => this.isAnimatingPulse = true);
- PropertiesButtons.hasPushedHack = false;
- targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "lg" : "sm"} />
+ const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => (dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? undefined : true;
+ return !targetDoc ? (null) :
+ <Tooltip title={<div className={`dash-tooltip`}>{tooltip(targetDoc?.[property])} </div>} placement="top">
+ <div>
+ <div className={`propertiesButtons-linkButton-empty toggle-${StrCast(targetDoc[property]).includes(":hover") ? "hover" : targetDoc[property] ? "on" : "off"}`}
+ onPointerDown={e => e.stopPropagation()}
+ onClick={undoBatch(() => {
+ if (SelectionManager.Views().length) {
+ SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property));
+ } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
+ })} >
+ <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon={icon(BoolCast(targetDoc?.[property])) as any} />
+ </div>
+ <div className="propertiesButtons-title">{label}</div>
</div>
- <div className="propertiesButtons-title">Google</div>
- </div>
- </Tooltip>;
+ </Tooltip>;
}
-
- @computed
- get considerGoogleDocsPull() {
- const targetDoc = this.selectedDoc;
- const dataDoc = targetDoc && Doc.GetProto(targetDoc);
- const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
-
- const title = (() => {
- switch (this.openHover) {
- default:
- case UtilityButtonState.Default: return `${!dataDoc?.googleDocUnchanged ? "Pull from" : "Fetch"} Google Docs`;
- case UtilityButtonState.OpenRight: return "Open in Right Split";
- case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
- }
- })();
-
- return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{title}</div></>} placement="top">
- <div>
- <div className="propertiesButtons-linker"
- style={{ backgroundColor: this.pullColor }}
- onPointerEnter={action(e => {
- e.altKey && (this.openHover = UtilityButtonState.OpenExternally);
- e.shiftKey && (this.openHover = UtilityButtonState.OpenRight);
- })}
- onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
- onClick={async e => {
- const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
- if (e.shiftKey) {
- e.preventDefault();
- let googleDoc = await Cast(dataDoc.googleDoc, Doc);
- if (!googleDoc) {
- const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, useCors: false };
- googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
- dataDoc.googleDoc = googleDoc;
- }
- CollectionDockingView.AddSplit(googleDoc, "right");
- } else if (e.altKey) {
- e.preventDefault();
- window.open(googleDocUrl);
- } else {
- this.clearPullColor();
- PropertiesButtons.hasPulledHack = false;
- targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
- dataDoc.googleDocUnchanged && runInAction(() => this.isAnimatingFetch = true);
- }
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" size="lg" color="black"
- style={{ WebkitAnimation: animation, MozAnimation: animation }}
- icon={(() => {
- switch (this.openHover) {
- default:
- case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch;
- case UtilityButtonState.OpenRight: return "arrow-alt-circle-right";
- case UtilityButtonState.OpenExternally: return "share";
- }
- })()}
- />
- </div>
- <div className="propertiesButtons-title" style={{ backgroundColor: "white", color: "black" }}>Fetch</div>
- </div>
- </Tooltip>;
+ @computed get lockButton() {
+ return this.propertyToggleBtn("No\xA0Drag", "_lockedPosition", on => `${on ? "Unlock" : "Lock"} position to prevent dragging`, on => "thumbtack");
}
-
- @action @undoBatch
- onLock = () => {
- SelectionManager.Views().forEach(dv => dv.docView?.toggleLockPosition());
+ @computed get dictationButton() {
+ return this.propertyToggleBtn("Dictate", "_showAudio", on => `${on ? "Hide" : "Show"} dictation/recording controls`, on => "microphone");
}
-
- @computed
- get lockButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<div className="dash-tooltip">{`${this.selectedDoc?._lockedPosition ? "Unlock" : "Lock"} Position`}</div>} placement="top">
- <div>
- <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._lockedPosition ? "on" : "off"}`} onPointerDown={this.onLock} >
- <FontAwesomeIcon className="documentdecorations-icon" size="lg"
- color={this.selectedDoc?._lockedPosition ? "black" : "white"}
- icon={this.selectedDoc?._lockedPosition ? "unlock" : "lock"} />
- </div>
- <div className="propertiesButtons-title"
- >Position </div>
- </div>
- </Tooltip>;
+ @computed get maskButton() {
+ return this.propertyToggleBtn("Mask", "isInkMask", on => on ? "Make plain ink" : "Make highlight mask", on => "paint-brush", (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc));
}
-
- @computed
- get downloadButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<div className="dash-tooltip">{"Download Document"}</div>} placement="top">
- <div>
- <div className={"propertiesButtons-linkButton-empty"} onPointerDown={() => this.selectedDoc && Doc.Zip(this.selectedDoc)}>
- <FontAwesomeIcon className="propertiesButtons-icon" icon="download" size="lg" />
- </div>
- <div className="propertiesButtons-title"> downld </div>
- </div>
- </Tooltip>;
+ @computed get clustersButton() {
+ return this.propertyToggleBtn("Clusters", "_useClusters", on => `${on ? "Hide" : "Show"} clusters`, on => "braille");
}
-
- @undoBatch
- setDictation = () => SelectionManager.Views().forEach(dv => dv.rootDoc._showAudio = !dv.rootDoc._showAudio)
-
- @computed
- get dictationButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Dictation Controls"}</div>} placement="top">
- <div>
- <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showAudio ? "on" : "off"}`} onPointerDown={this.setDictation}>
- <FontAwesomeIcon className="propertiesButtons-icon" icon="microphone" size="lg" />
- </div>
- <div className="propertiesButtons-title"> Dictate </div>
- </div>
- </Tooltip>;
+ @computed get panButton() {
+ return this.propertyToggleBtn("Lock\xA0View", "_lockedTransform", on => `${on ? "Unlock" : "Lock"} panning of view`, on => "lock");
}
-
-
- @undoBatch
- @action
- setTitle = () => {
- SelectionManager.Views().forEach(dv => dv.rootDoc._showTitle = !dv.rootDoc._showTitle ? "title" : dv.rootDoc._showTitle === "title" ? "title:hover" : undefined);
+ @computed get fitContentButton() {
+ return this.propertyToggleBtn("View All", "_fitToBox", on => `${on ? "Don't" : ""} fit content to container visible area`, on => "eye");
}
-
- @computed
- get titleButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Title Header"}</div>} placement="top">
- <div>
- <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showTitle === "title" ? "on" : StrCast(targetDoc._showTitle).includes(":hover") ? "hover" : "off"}`} onPointerDown={this.setTitle}>
- <FontAwesomeIcon className="propertiesButtons-icon" icon="text-width" size="lg" />
- </div>
- <div className="propertiesButtons-title"> Title </div>
- </div>
- </Tooltip>;
+ @computed get fitWidthButton() {
+ return this.propertyToggleBtn("Fit\xA0Width", "_fitWidth", on => `${on ? "Don't" : ""} fit content to width of container`, on => "arrows-alt-h");
}
-
- @undoBatch
- @action
- setCaption = () => {
- SelectionManager.Views().forEach(dv => {
- dv.rootDoc._showCaption = dv.rootDoc._showCaption === undefined ? "caption" : undefined;
- console.log("caption = " + dv.rootDoc._showCaption);
- });
+ @computed get captionButton() {
+ return this.propertyToggleBtn("Caption", "_showCaption", on => `${on ? "Hide" : "Show"} caption footer`, on => "closed-captioning", (dv, doc) => (dv?.rootDoc || doc)._showCaption = (dv?.rootDoc || doc)._showCaption === undefined ? "caption" : undefined);
}
-
- @computed
- get captionButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Caption Footer"}</div>} placement="top">
- <div>
- <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._showCaption ? "on" : "off"}`} onPointerDown={this.setCaption}>
- <FontAwesomeIcon className="propertiesButtons-icon" icon="closed-captioning" size="lg" />
- </div>
- <div className="propertiesButtons-title"> Caption </div>
- </div>
- </Tooltip>;
+ @computed get chromeButton() {
+ return this.propertyToggleBtn("Controls", "_chromeStatus", on => `${on === "enabled" ? "Hide" : "Show"} editing UI`, on => "edit", (dv, doc) => (dv?.rootDoc || doc)._chromeStatus = (dv?.rootDoc || doc)._chromeStatus === undefined ? "enabled" : undefined);
}
-
- @undoBatch
- @action
- setChrome = () => {
- SelectionManager.Views().forEach(dv => dv.rootDoc._chromeStatus = dv.rootDoc._chromeStatus === "disabled" ? "enabled" : "disabled");
+ @computed get titleButton() {
+ return this.propertyToggleBtn("Title", "_showTitle", on => "Switch between title styles", on => "text-width", (dv, doc) => (dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? "title" : (dv?.rootDoc || doc)._showTitle === "title" ? "title:hover" : undefined);
}
@computed
- get chromeButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Editing UI"}</div>} placement="top">
+ get onClickButton() {
+ return !this.selectedDoc ? (null) : <Tooltip title={<div className="dash-tooltip">Choose onClick behavior</div>} placement="top">
<div>
- <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._chromeStatus === "enabled" ? "on" : "off"}`} onPointerDown={this.setChrome}>
- <FontAwesomeIcon className="propertiesButtons-icon" icon="edit" size="lg" />
+ <div className="propertiesButtons-linkFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.onClickFlyout}>
+ <div className={"propertiesButtons-linkButton-empty"} onPointerDown={e => e.stopPropagation()} >
+ <FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" />
+ </div>
+ </Flyout>
</div>
- <div className="propertiesButtons-title"> Controls </div>
+ <div className="propertiesButtons-title"> onclick </div>
</div>
</Tooltip>;
}
- @computed
- get onClickButton() {
- if (this.selectedDoc) {
- return <Tooltip title={<><div className="dash-tooltip">Choose onClick behavior</div></>} placement="top">
- <div>
- <div className="propertiesButtons-linkFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP}
- content={this.onClickFlyout}>
- <div className={"propertiesButtons-linkButton-empty"} onPointerDown={e => e.stopPropagation()} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" />}
- </div>
- </Flyout>
- </div>
- <div className="propertiesButtons-title"> onclick </div>
- </div>
- </Tooltip>;
- } else {
- return null;
- }
- }
-
@undoBatch
@action
handleOptionChange = (e: any) => {
- const value = e.target.value;
this.selectedDoc && (this.selectedDoc.onClickBehavior = e.target.value);
-
- SelectionManager.Views().forEach(dv => {
- dv.docView?.noOnClick();
- switch (value) {
- case "enterPortal": dv.docView?.makeIntoPortal(); break;
- case "toggleDetail": dv.docView?.toggleDetail(); break;
- case "linkInPlace": dv.docView?.toggleFollowLink("inPlace", true, false); break;
- case "linkOnRight": dv.docView?.toggleFollowLink("add:right", false, false); break;
+ SelectionManager.Views().filter(dv => dv.docView).map(dv => dv.docView!).forEach(docView => {
+ docView.noOnClick();
+ switch (e.target.value) {
+ case "enterPortal": docView.makeIntoPortal(); break;
+ case "toggleDetail": docView.toggleDetail(); break;
+ case "linkInPlace": docView.toggleFollowLink("inPlace", true, false); break;
+ case "linkOnRight": docView.toggleFollowLink("add:right", false, false); break;
}
});
}
- @undoBatch @action
+ @undoBatch
editOnClickScript = () => {
- if (this.selectedDoc) {
- if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, "onClick"));
- else DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, "onClick");
- }
+ if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, "onClick"));
+ else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, "onClick");
}
@computed
get onClickFlyout() {
- return <div><form>
- <div className="radio">
- <label>
- <input type="radio" value="nothing"
- checked={this.onClick === 'nothing'}
- onChange={this.handleOptionChange} />
- Select Document
- </label>
- </div>
- <div className="radio">
- <label>
- <input type="radio" value="enterPortal"
- checked={this.onClick === 'enterPortal'}
- onChange={this.handleOptionChange} />
- Enter Portal
- </label>
- </div>
- <div className="radio">
- <label>
- <input type="radio" value="toggleDetail"
- checked={this.onClick === 'toggleDetail'}
- onChange={this.handleOptionChange} />
- Toggle Detail
- </label>
- </div>
- <div className="radio">
- <label>
- <input type="radio" value="linkInPlace"
- checked={this.onClick === 'linkInPlace'}
- onChange={this.handleOptionChange} />
- Follow Link
- </label>
- </div>
- <div className="radio">
- <label>
- <input type="radio" value="linkOnRight"
- checked={this.onClick === 'linkOnRight'}
- onChange={this.handleOptionChange} />
- Open Link on Right
- </label>
- </div>
- </form>
+ const makeLabel = (value: string, label: string) => <div className="radio">
+ <label>
+ <input type="radio" value={value} checked={(this.selectedDoc?.onClickBehavior ?? "nothing") === value} onChange={this.handleOptionChange} />
+ {label}
+ </label>
+ </div>;
+ return <div>
+ <form>
+ {makeLabel("nothing", "Select Document")}
+ {makeLabel("enterPortal", "Enter Portal")}
+ {makeLabel("toggleDetail", "Toggle Detail")}
+ {makeLabel("linkInPlace", "Follow Link")}
+ {makeLabel("linkOnRight", "Open Link on Right")}
+ </form>
{Doc.UserDoc().noviceMode ? (null) : <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript"> Edit onClick Script</div>}
</div>;
}
- @computed
- get googlePhotosButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{"Export to Google Photos"}</div></>} placement="top">
- <div>
- <div className={"propertiesButtons-linkButton-empty"}
- onPointerDown={() => this.selectedDoc && GooglePhotos.Export.CollectionToAlbum({ collection: this.selectedDoc }).then(console.log)}>
- {<FontAwesomeIcon className="documentdecorations-icon" icon="cloud-upload-alt" size="lg" />}
- </div>
- <div className="propertiesButtons-title"> google </div>
- </div>
- </Tooltip>;
- }
-
- @computed
- get clustersButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{this.selectedDoc?._useClusters ? "Stop Showing Clusters" : "Show Clusters"}</div></>} placement="top">
- <div>
- <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._useClusters ? "on" : "off"}`} onPointerDown={this.changeClusters}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="braille" size="lg" />
- </div>
- <div className="propertiesButtons-title" > clusters </div>
- </div>
- </Tooltip>;
- }
-
- @action @undoBatch
- changeFitToBox = () => {
- if (this.selectedDoc) {
- if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => dv.rootDoc._fitToBox = !dv.rootDoc._fitToBox);
- else this.selectedDoc._fitToBox = !this.selectedDoc._fitToBox;
- }
- }
-
- @action @undoBatch
- changeFitWidth = () => {
- if (this.selectedDoc) {
- if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => dv.rootDoc._fitWidth = !dv.rootDoc._fitWidth);
- else this.selectedDoc._fitWidth = !this.selectedDoc._fitWidth;
- }
- }
-
- @action @undoBatch
- changeClusters = () => {
- if (this.selectedDoc) {
- if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => dv.rootDoc._useClusters = !dv.rootDoc._useClusters);
- else this.selectedDoc._useClusters = !this.selectedDoc._useClusters;
- }
- }
-
- @computed
- get fitContentButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{this.selectedDoc?._fitToBox ? "Stop Fitting Content" : "Fit Content"}</div></>} placement="top">
- <div>
- <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._fitToBox ? "on" : "off"}`} onPointerDown={this.changeFitToBox}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="expand" size="lg" />
- </div>
- <div className="propertiesButtons-title"> {this.selectedDoc?._fitToBox ? "unfit" : "fit"} </div>
- </div>
- </Tooltip>;
- }
-
- @computed
- get fitWidthButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">{this.selectedDoc?._fitWidth ? "Stop Fitting Width" : "Fit Width"}</div></>} placement="top">
- <div>
- <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._fitWidth ? "on" : "off"}`} onPointerDown={this.changeFitWidth}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="arrows-alt-h" size="lg" />
- </div>
- <div className="propertiesButtons-title"> {this.selectedDoc?._fitWidth ? "unfit" : "fit"} </div>
- </div>
- </Tooltip>;
- }
-
- @undoBatch
- @action
- private makeMask = () => {
- if (this.selectedDoc) {
- this.selectedDoc._backgroundColor = "rgba(0,0,0,0.7)";
- this.selectedDoc.mixBlendMode = "hard-light";
- this.selectedDoc.color = "#9b9b9bff";
- this.selectedDoc._stayInCollection = true;
- this.selectedDoc.isInkMask = true;
- }
- }
-
- @computed
- get maskButton() {
- const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip
- title={<><div className="dash-tooltip">Make Mask</div></>} placement="top">
- <div>
- <div className={"propertiesButtons-linkButton-empty"} onPointerDown={this.makeMask}>
- <FontAwesomeIcon className="documentdecorations-icon" color="white" icon="paint-brush" size="lg" />
- </div>
- <div className="propertiesButtons-title"> mask </div>
- </div>
- </Tooltip>;
- }
-
render() {
- if (!this.selectedDoc) return (null);
-
- const layoutField = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)];
+ const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
const isText = layoutField instanceof RichTextField;
- const isImage = layoutField instanceof ImageField;
const isInk = layoutField instanceof InkField;
- const isCollection = this.selectedDoc.type === DocumentType.COL;
- const isFreeForm = this.selectedDoc._viewType === CollectionViewType.Freeform;
- const considerPull = isText && this.considerGoogleDocsPull;
- const considerPush = isText && this.considerGoogleDocsPush;
-
- return <div className="propertiesButtons" style={{ paddingBottom: "5.5px" }}>
- <div className="propertiesButtons-button">
- {this.titleButton}
- </div>
- <div className="propertiesButtons-button">
- {this.captionButton}
- </div>
- <div className="propertiesButtons-button" style={{ display: isCollection ? "" : "none" }}>
- {this.chromeButton}
- </div>
- <div className="propertiesButtons-button">
- {this.lockButton}
- </div>
- <div className="propertiesButtons-button">
- {this.dictationButton}
- </div>
- <div className="propertiesButtons-button">
- {this.onClickButton}
- </div>
- <div className="propertiesButtons-button" style={{ display: !considerPush ? "none" : "" }}>
- {this.considerGoogleDocsPush}
- </div>
- <div className="propertiesButtons-button" style={{ display: !considerPull ? "none" : "" }}>
- {this.considerGoogleDocsPull}
- </div>
- <div className="propertiesButtons-button" style={{ display: !isImage ? "none" : "" }}>
- {this.googlePhotosButton}
- </div>
-
- <div className="propertiesButtons-button" style={{ display: !isFreeForm ? "none" : "" }}>
- {this.clustersButton}
- </div>
- <div className="propertiesButtons-button" style={{ display: !isFreeForm && !isText ? "none" : "" }}>
- {this.fitContentButton}
- </div>
- <div className="propertiesButtons-button">
- {this.fitWidthButton}
- </div>
- <div className="propertiesButtons-button" style={{ display: !isInk ? "none" : "" }}>
- {this.maskButton}
- </div>
- </div>;
- }
-}
+ const isCollection = this.selectedDoc?.type === DocumentType.COL;
+ const isFreeForm = this.selectedDoc?._viewType === CollectionViewType.Freeform;
+ const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => <div className="propertiesButtons-button" style={style}> {ele} </div>;
+
+ return !this.selectedDoc ? (null) :
+ <div className="propertiesButtons">
+ {toggle(this.titleButton)}
+ {toggle(this.captionButton)}
+ {toggle(this.chromeButton, { display: isCollection ? "" : "none" })}
+ {toggle(this.lockButton)}
+ {toggle(this.dictationButton)}
+ {toggle(this.onClickButton)}
+ {toggle(this.clustersButton, { display: !isFreeForm ? "none" : "" })}
+ {toggle(this.panButton, { display: !isFreeForm ? "none" : "" })}
+ {toggle(this.fitContentButton, { display: !isFreeForm && !isText ? "none" : "" })}
+ {toggle(this.fitWidthButton)}
+ {toggle(this.maskButton, { display: !isInk ? "none" : "" })}
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index f39d5ee4c..9f04e8a6e 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -45,7 +45,7 @@ class OtherToggle extends React.Component<{ checked: boolean, name: string, togg
export interface TemplateMenuProps {
docViews: DocumentView[];
- templates?: Map<string, boolean>;
+ templates: Map<string, boolean>;
}
@@ -81,7 +81,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
@action
toggleChrome = (): void => {
this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout =>
- layout._chromeStatus = (layout._chromeStatus !== "disabled" ? "disabled" : StrCast(layout._replacedChrome, "enabled")));
+ layout._chromeStatus = (layout._chromeStatus ? undefined : StrCast(layout._replacedChrome, "enabled")));
}
// todo: add brushes to brushMap to save with a style name
@@ -115,7 +115,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
const addedTypes = Doc.UserDoc().noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
const layout = Doc.Layout(firstDoc);
const templateMenu: Array<JSX.Element> = [];
- this.props.templates?.forEach((checked, template) =>
+ this.props.templates.forEach((checked, template) =>
templateMenu.push(<TemplateToggle key={template} template={template} checked={checked} toggle={this.toggleTemplate} />));
templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
@@ -140,7 +140,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
rootSelected={returnFalse}
onCheckedClick={this.scriptField}
onChildClick={this.scriptField}
- setHeight={returnFalse}
dropAction={undefined}
active={returnTrue}
parentActive={returnFalse}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 3556e74bc..b9757dde3 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -381,7 +381,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
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 as Doc).filter(d => d.type !== DocumentType.KVP);
+ const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP);
other && (Doc.GetProto(other).data = new List<Doc>(vals));
}, 0);
}
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 9b57d4c68..46bfd841e 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -187,7 +187,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@action
headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
if (e.button === 0 && !e.ctrlKey) {
- setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => (this.props.parent.props.Document._chromeStatus === "disabled") && this.collapseSection(e));
+ setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this.props.parent.props.Document._chromeStatus && this.collapseSection(e));
this._createAliasSelected = false;
}
}
@@ -253,7 +253,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap))));
const style = this.props.parent;
const chromeStatus = this.props.parent.props.Document._chromeStatus;
- const showChrome = (chromeStatus !== 'view-mode' && chromeStatus !== 'disabled');
+ const showChrome = (chromeStatus !== 'view-mode' && chromeStatus);
const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `;
return this.collapsed ? (null) :
<div style={{ position: "relative" }}>
@@ -286,7 +286,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
}
@computed get headingView() {
- const noChrome = this.props.parent.props.Document._chromeStatus === "disabled";
+ const noChrome = !this.props.parent.props.Document._chromeStatus;
const key = StrCast(this.props.parent.props.Document._pivotField);
const evContents = this.heading ? this.heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
const editableHeaderView = <EditableView
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index f054e7b7f..22b5c2b2a 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -21,7 +21,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
this.Document._pileLayoutEngine = "pass";
}
this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
- this.layoutDoc._chromeStatus = "disabled";
+ this.layoutDoc._chromeStatus = undefined;
}
componentWillUnmount() {
this.layoutDoc._chromeStatus = this._originalChrome;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index f3c1c4464..e3d7118e9 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -63,7 +63,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
@computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
@computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking; }
@computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
- @computed get showAddAGroup() { return (this.pivotField && (this.chromeStatus !== 'view-mode' && this.chromeStatus !== 'disabled')); }
+ @computed get showAddAGroup() { return (this.pivotField && (this.chromeStatus !== 'view-mode' && this.chromeStatus)); }
@computed get columnWidth() {
return Math.min(this.props.PanelWidth() /* / NumCast(this.layoutDoc._viewScale, 1)*/ - 2 * this.xMargin,
this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
@@ -539,7 +539,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
<EditableView {...editableViewProps} />
</div>}
- {/* {this.chromeStatus === 'disabled' || !this.props.isSelected() ? (null) :
+ {/* {!this.chromeStatus || !this.props.isSelected() ? (null) :
<Switch
onChange={this.onToggle}
onClick={this.onToggle}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 94d74b6f7..389b449b5 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -146,7 +146,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
newDoc.heading = heading;
FormattedTextBox.SelectOnLoad = newDoc[Id];
FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " ";
- return this.props.parent.props.addDocument?.(newDoc) || false;
+ return this.props.parent.addDocument?.(newDoc) || false;
}
@action
@@ -300,7 +300,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
marginTop: NumCast(this.props.parent.yMargin, 5),
width: (style.columnWidth) /
((uniqueHeadings.length +
- ((this.props.parent.chromeStatus !== 'view-mode' && this.props.parent.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
+ ((this.props.parent.chromeStatus !== 'view-mode' && this.props.parent.chromeStatus) ? 1 : 0)) || 1)
}}>
<div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div>
{/* the default bucket (no key value) has a tooltip that describes what it is.
@@ -359,7 +359,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
{this.props.parent.children(this.props.docList, uniqueHeadings.length)}
{singleColumn ? (null) : this.props.parent.columnDragger}
</div>
- {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled' && type !== DocumentType.PRES) ?
+ {(chromeStatus !== 'view-mode' && chromeStatus && type !== DocumentType.PRES) ?
<div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
style={{ width: style.columnWidth / style.numGroupColumns, marginBottom: 10 }}>
<EditableView
@@ -385,7 +385,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
return (
<div className={"collectionStackingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
style={{
- width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
+ width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus) ? 1 : 0)) || 1)}%`,
height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
background: this._background
}}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index f226cf79b..05e9ac265 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -373,16 +373,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
if (uriList) {
console.log("Web URI = ", uriList);
- const existingWebDoc = await Hypothesis.findWebDoc(uriList);
- if (existingWebDoc) {
- const alias = Doc.MakeAlias(existingWebDoc);
- alias.x = options.x;
- alias.y = options.y;
- alias._nativeWidth = 850;
- alias._height = 512;
- alias._width = 400;
- addDocument(alias);
- } else {
+ // const existingWebDoc = await Hypothesis.findWebDoc(uriList);
+ // if (existingWebDoc) {
+ // const alias = Doc.MakeAlias(existingWebDoc);
+ // alias.x = options.x;
+ // alias.y = options.y;
+ // alias._nativeWidth = 850;
+ // alias._height = 512;
+ // alias._width = 400;
+ // addDocument(alias);
+ // } else
+ {
console.log("Adding ...");
const newDoc = Docs.Create.WebDocument(uriList.split("#annotations:")[0], {// clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
...options,
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index be5a4b852..34fd20f1a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -154,19 +154,7 @@ export class CollectionView extends Touchable<CollectionViewProps> {
}
else {
added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document
- const context = Cast(doc.context, Doc, null);
- const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context));
- if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
- const pushpin = Docs.Create.FontIconDocument({
- title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true,
- icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), backgroundColor: "#0000003d", color: "#ACCEF7",
- _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null)
- });
- Doc.SetInPlace(doc, "annotationOn", undefined, true);
- Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
- const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
- doc._timecodeToShow = undefined;
- }
+ DocUtils.LeavePushpin(doc);
doc._stayInCollection = undefined;
doc.context = this.props.Document;
});
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 53801eef1..d26f53e28 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -559,7 +559,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
onPointerDown={this.props.onPointerDown} onClick={this.props.onClick} onWheel={e => this.props.active(true) && e.stopPropagation()}
onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
{this.reactTable}
- {StrCast(this.props.Document._chromeStatus) !== "disabled" ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
+ {this.props.Document._chromeStatus ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
: undefined}
{!this._showDoc ? (null) :
<div className="collectionSchemaView-documentPreview"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5c5164d98..0ee2fad2e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -17,7 +17,6 @@ import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUp
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager, dropActionType } from "../../../util/DragManager";
@@ -236,7 +235,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
- if (!this.ChildDrag && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
+ if (!de.embedKey && !this.ChildDrag && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
if (!super.onInternalDrop(e, de)) return false;
const refDoc = docDragData.droppedDocuments[0];
const [xpo, ypo] = this.getTransformOverlay().transformPoint(de.x, de.y);
@@ -1286,10 +1285,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@undoBatch
toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight)
- @undoBatch
- @action
- toggleLockTransform = () => this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true
-
onContextMenu = (e: React.MouseEvent) => {
if (this.props.isAnnotationOverlay || this.props.Document.annotationOn || !ContextMenu.Instance) return;
@@ -1314,7 +1309,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const optionItems = options && "subitems" in options ? options.subitems : [];
!this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode &&
optionItems.push({ description: (this._showAnimTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this._showAnimTimeline = !this._showAnimTimeline), icon: "eye" });
- optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
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" });
@@ -1493,7 +1487,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onDragOver={e => e.preventDefault()}
onContextMenu={this.onContextMenu}
style={{
- pointerEvents: this.backgroundEvents ? "all" : undefined,
+ pointerEvents: this.backgroundEvents ? "all" : this.props.pointerEvents as any,
transform: `scale(${this.contentScaling || 1})`,
width: `${100 / (this.contentScaling || 1)}%`,
height: this.isAnnotationOverlay && this.Document.scrollHeight ? this.Document.scrollHeight : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 9206d3767..af391a078 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -89,7 +89,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
if (e.key === "?") {
cm.setDefaultItem("?", (str: string) => this.props.addDocTab(
- Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, isAnnotating: false, title: "bing", useCors: true }), "add:right"));
+ Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: "bing", useCors: true }), "add:right"));
cm.displayMenu(this._downX, this._downY);
e.stopPropagation();
@@ -165,7 +165,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.props.addDocument?.(slide);
e.stopPropagation();
} else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) {
- FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout && !this.props.childLayoutString ? e.key : "";
+ FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : "";
FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch");
this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xMargin === 0, this.props.isAnnotationOverlay ? this.props.Document : undefined));
e.stopPropagation();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c7b2f2df6..358446a57 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -481,7 +481,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
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 {
- this.props.select(e.ctrlKey || e.shiftKey);
+ const ctrlPressed = e.ctrlKey || e.shiftKey;
+ if (this.props.Document.type === DocumentType.WEB) {
+ this._timeout = setTimeout(() => { this._timeout = undefined; this.props.select(ctrlPressed); }, 350);
+ } else this.props.select(ctrlPressed);
}
preventDefault = false;
}
@@ -593,7 +596,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
@undoBatch toggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`);
- @undoBatch toggleLockPosition = () => this.Document._lockedPosition = this.Document._lockedPosition ? undefined : true;
@undoBatch @action
drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -688,7 +690,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!this.Document.annotationOn) {
const options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && optionItems.push({ description: this.Document._lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document._lockedPosition) ? "unlock" : "lock" });
!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" });
@@ -720,7 +721,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
(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._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+ moreItems.push({ description: `${this.Document._chromeStatus ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus ? undefined : "enabled"), icon: "project-diagram" });
if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
@@ -1114,8 +1115,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
position: this.props.Document.isInkMask ? "absolute" : undefined,
transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
width: xshift ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
- height: yshift ?? this.props.Document._fitWidth ? `${this.panelHeight}px` :
- `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`,
+ height: yshift ?? (this.props.Document._fitWidth ? `${this.panelHeight}px` :
+ `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
}}>
<DocumentViewInternal {...this.props}
DocumentView={this.selfView}
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 56c79cde9..6ae4b9726 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -49,14 +49,12 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
const presTrailsIcon = <img src={`/assets/${"presTrails.png"}`}
style={{ width: presSize, height: presSize, filter: `invert(${color === "white" ? "100%" : "0%"})`, marginBottom: "5px" }} />;
const button = <button className={`menuButton-${shape}`} onContextMenu={this.specificContextMenu}
- style={{
- backgroundColor: this.layoutDoc.iconShape === "square" ? backgroundColor : "",
- }}>
+ style={{ backgroundColor: backgroundColor, }}>
<div className="menuButton-wrap">
{icon === 'pres-trail' ? presTrailsIcon : <FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={icon} color={color}
size={this.layoutDoc.iconShape === "square" ? "sm" : "sm"} />}
{!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>}
- {this.props.Document.watchedDocuments ? <FontIconBadge collection={Cast(this.props.Document.watchedDocuments, Doc, null)} /> : (null)}
+ <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} />
</div>
</button>;
return !this.layoutDoc.toolTip ? button :
@@ -67,7 +65,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
}
interface FontIconBadgeProps {
- collection: Doc;
+ collection: Doc | undefined;
}
@observer
@@ -77,7 +75,7 @@ export class FontIconBadge extends React.Component<FontIconBadgeProps> {
onPointerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e,
(e: PointerEvent) => {
- const dragData = new DragManager.DocumentDragData([this.props.collection]);
+ const dragData = new DragManager.DocumentDragData([this.props.collection!]);
DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y);
return true;
},
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index bc01acdfd..9426f6afc 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, ObservableMap, untracked } from 'mobx';
import { observer } from "mobx-react";
import { Dictionary } from 'typescript-collections';
import { DataSym, Doc, DocListCast, WidthSym } from '../../../fields/Doc';
@@ -50,10 +50,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
- private _curSuffix = "_m";
private _dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
- @observable uploadIcon = uploadIcons.idle;
+ @observable _curSuffix = "";
+ @observable _uploadIcon = uploadIcons.idle;
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer?.();
@@ -61,17 +61,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
}
componentDidMount() {
+ 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],
+ selected: this.props.isSelected()
+ }),
+ ({ forceFull, scrSize, selected }) => this._curSuffix = forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o",
+ { fireImmediately: true, delay: 1000 });
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => !selected && setTimeout(() => {
- this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
this._savedAnnotations.clear();
}));
this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
- action(({ nativeSize, width }) => {
+ ({ nativeSize, width }) => {
if (!this.layoutDoc._height) {
this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth;
}
- }),
+ },
{ fireImmediately: true });
}
@@ -176,32 +184,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return url.href; //Why is this here
const ext = path.extname(url.href);
- const scrSize = this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight);
- this._curSuffix = this.props.renderDepth < 1 || this.layoutDoc._showFullRes ? "_o" : scrSize[0] < 100 ? "_s" : scrSize[0] < 400 ? "_m" : scrSize[0] < 800 || !this.props.isSelected() ? "_l" : "_o";
return url.href.replace(ext, this._curSuffix + ext);
}
- @observable _smallRetryCount = 1;
- @observable _mediumRetryCount = 1;
- @observable _largeRetryCount = 1;
- @action retryPath = () => {
- if (this._curSuffix === "_s") this._smallRetryCount++;
- if (this._curSuffix === "_m") this._mediumRetryCount++;
- if (this._curSuffix === "_l") this._largeRetryCount++;
- }
-
- @action onError = (error: any) => {
- const timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
- if (timeout < 5) {
- setTimeout(this.retryPath, 500);
- } else {
- const original = StrCast(this.dataDoc[this.fieldKey + "-originalUrl"]);
- if (error.type === "error" && original) {
- this.dataDoc[this.fieldKey] = new ImageField(original);
- }
- }
- }
-
considerGooglePhotosLink = () => {
const remoteUrl = this.dataDoc.googlePhotosUrl;
return !remoteUrl ? (null) : (<img draggable={false}
@@ -231,11 +216,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
<img
id={"upload-icon"} draggable={false}
style={{ transformOrigin: "bottom right" }}
- src={`/assets/${this.uploadIcon}`}
+ src={`/assets/${this._uploadIcon}`}
onClick={async () => {
const { dataDoc } = this;
const { success, failure, idle, loading } = uploadIcons;
- runInAction(() => this.uploadIcon = loading);
+ runInAction(() => this._uploadIcon = loading);
const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] });
dataDoc[this.props.fieldKey + "-originalUrl"] = primary;
let succeeded = true;
@@ -245,9 +230,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
} catch {
succeeded = false;
}
- runInAction(() => this.uploadIcon = succeeded ? success : failure);
+ runInAction(() => this._uploadIcon = succeeded ? success : failure);
setTimeout(action(() => {
- this.uploadIcon = idle;
+ this._uploadIcon = idle;
if (data) {
dataDoc[this.fieldKey] = data;
}
@@ -265,11 +250,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
return { nativeWidth, nativeHeight, nativeOrientation };
}
- // this._curSuffix = "";
- // if (w > 20) {
- // if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s";
- // else if (w < 600 && this._mediumRetryCount < 10) this._curSuffix = "_m";
- // else if (this._largeRetryCount < 10) this._curSuffix = "_l";
@computed get paths() {
const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
const alts = DocListCast(this.dataDoc[this.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
@@ -300,20 +280,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
return <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
<div className="imageBox-fader" >
- <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
+ <img key="paths" ref={this._imgRef}
src={srcpath}
style={{ transform, transformOrigin }} draggable={false}
- width={nativeWidth}
- ref={this._imgRef}
- onError={this.onError} />
+ width={nativeWidth} />
{fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker">
- <img className="imageBox-fadeaway"
- key={"fadeaway" + this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
+ <img className="imageBox-fadeaway" key={"fadeaway"} ref={this._imgRef}
src={fadepath}
style={{ transform, transformOrigin }} draggable={false}
- width={nativeWidth}
- ref={this._imgRef}
- onError={this.onError} /></div>}
+ width={nativeWidth} />
+ </div>}
</div>
{this.considerDownloadIcon}
{this.considerGooglePhotosLink()}
@@ -337,7 +313,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
@observable _marqueeing: number[] | undefined;
- @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+ @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@computed get annotationLayer() {
return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
}
@@ -388,7 +364,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
</CollectionFreeFormView>
{this.annotationLayer}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc} scrollTop={0} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ <MarqueeAnnotator rootDoc={this.rootDoc}
+ scrollTop={0} down={this._marqueeing}
+ scaling={this.props.scaling}
+ docView={this.props.docViewPath().lastElement()}
+ addDocument={this.addDocument}
+ finishMarquee={this.finishMarquee}
+ savedAnnotations={this._savedAnnotations}
+ annotationLayer={this._annotationLayer.current}
+ mainCont={this._mainCont.current}
+ />}
</div >);
}
+
}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index e4aa639ff..0dbe0c917 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -231,7 +231,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
} else {
this.layoutDoc.nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
}
- this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) / NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]))
+ this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) / NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]));
}
settingsPanel() {
const pageBtns = <>
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 844afe1e2..682ec5356 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -2262,7 +2262,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
const isMini: boolean = this.toolbarWidth <= 100;
return (
- <div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}>
+ <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeStatus ? "none" : undefined }}>
{isMini ? (null) : <select className="presBox-viewPicker"
style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }}
onPointerDown={e => e.stopPropagation()}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 3e1edb927..575fbcf2e 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,6 +1,6 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, ObservableMap } from "mobx";
import { observer } from "mobx-react";
import * as rp from 'request-promise';
import { Dictionary } from "typescript-collections";
@@ -51,7 +51,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
private _playRegionDuration = 0;
@observable static _showControls: boolean;
@observable _marqueeing: number[] | undefined;
- @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+ @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@observable _screenCapture = false;
@observable _clicking = false;
@observable _forceCreateYouTubeIFrame = false;
@@ -206,7 +206,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => !selected && setTimeout(() => {
- this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
this._savedAnnotations.clear();
}));
this._disposers.triggerVideo = reaction(
@@ -345,7 +345,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this._disposers.youtubeReactionDisposer?.();
this._disposers.reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek((this.layoutDoc._currentTimecode || 0)));
this._disposers.youtubeReactionDisposer = reaction(
- () => !this.props.Document.isAnnotating && CurrentUserUtils.SelectedTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
+ () => CurrentUserUtils.SelectedTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting,
(interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true });
};
if (typeof (YT) === undefined) setTimeout(() => this.loadYouTube(iframe), 100);
@@ -575,6 +575,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
scrollTop={0}
rootDoc={this.rootDoc}
down={this._marqueeing}
+ docView={this.props.docViewPath().lastElement()}
scaling={this.marqueeFitScaling}
containerOffset={this.marqueeOffset}
addDocument={this.addDocWithTimecode}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 6127f82e3..f15a249da 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,10 +1,8 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Dictionary } from "typescript-collections";
import * as WebRequest from 'web-request';
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, StrListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { HtmlField } from "../../../fields/HtmlField";
@@ -14,29 +12,30 @@ import { listSpec, makeInterface } from "../../../fields/Schema";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { WebField } from "../../../fields/URLField";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, OmitKeys, returnOne, smoothScroll, Utils, returnZero, returnTrue } from "../../../Utils";
+import { emptyFunction, getWordAtPoint, OmitKeys, returnOne, returnTrue, returnZero, smoothScroll, Utils } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
-import { DragManager } from "../../util/DragManager";
-import { ImageUtils } from "../../util/Import & Export/ImageUtils";
+import { DocumentType } from '../../documents/DocumentTypes';
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { SnappingManager } from "../../util/SnappingManager";
import { undoBatch } from "../../util/UndoManager";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CollectionStackingView } from "../collections/CollectionStackingView";
+import { CollectionViewType } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
+import { LightboxView } from "../LightboxView";
import { MarqueeAnnotator } from "../MarqueeAnnotator";
+import { AnchorMenu } from "../pdf/AnchorMenu";
import { Annotation } from "../pdf/Annotation";
+import { SearchBox } from "../search/SearchBox";
+import { StyleProp } from "../StyleProvider";
import { FieldView, FieldViewProps } from './FieldView';
+import { FormattedTextBox } from "./formattedText/FormattedTextBox";
import { LinkDocPreview } from "./LinkDocPreview";
import "./WebBox.scss";
-import { DocumentType } from '../../documents/DocumentTypes';
import React = require("react");
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { SearchBox } from "../search/SearchBox";
-import { CollectionStackingView } from "../collections/CollectionStackingView";
-import { StyleProp } from "../StyleProvider";
-import { FormattedTextBox } from "./formattedText/FormattedTextBox";
-import { CollectionViewType } from "../collections/CollectionView";
const htmlToText = require("html-to-text");
type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -46,24 +45,22 @@ const WebDocument = makeInterface(documentSchema);
export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
- private _longPressSecondsHack?: NodeJS.Timeout;
- private _outerRef = React.createRef<HTMLDivElement>();
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
- private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
- private _iframeDragRef = React.createRef<HTMLDivElement>();
private _keyInput = React.createRef<HTMLInputElement>();
- private _ignoreScroll = "";
- private _scrollTimer: any;
+ @observable _scrollTimer: any;
+ @observable private _overlayAnnoInfo: Opt<Doc>;
private _initialScroll: Opt<number>;
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
@observable private _marqueeing: number[] | undefined;
@observable private _url: string = "hello";
- @observable private _pressX: number = 0;
- @observable private _pressY: number = 0;
+ @observable private _isAnnotating = false;
@observable private _iframe: HTMLIFrameElement | null = null;
- @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
- get scrollHeight() { return this.webpage?.scrollHeight || 1000; }
- get webpage() { return this._iframe?.contentDocument?.children[0]; }
+ @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+ @observable private _scrollHeight = 1500;
+ @computed get scrollHeight() { return this._scrollHeight; }
+ @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
constructor(props: any) {
super(props);
@@ -71,14 +68,80 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
}
- this._annotationKey = this._annotationKey + "-" + this.urlHash(this._url);
+ if (this.layoutDoc[this.fieldKey + "-contentWidth"] === undefined) {
+ this.layoutDoc[this.fieldKey + "-contentWidth"] = Doc.NativeWidth(this.layoutDoc);
+ }
+ this._annotationKey = "annotations-" + this.urlHash(this._url);
+ }
+
+ @action
+ createTextAnnotation = (sel: Selection, selRange: Range) => {
+ if (this._mainCont.current) {
+ const clientRects = selRange.getClientRects();
+ for (let i = 0; i < clientRects.length; i++) {
+ const rect = clientRects.item(i);
+ if (rect && rect.width !== this._mainCont.current.clientWidth) {
+ const annoBox = document.createElement("div");
+ annoBox.className = "marqueeAnnotator-annotationBox";
+ // transforms the positions from screen onto the pdf div
+ annoBox.style.top = (rect.top + this._mainCont.current.scrollTop).toString();
+ annoBox.style.left = (rect.left).toString();
+ annoBox.style.width = (rect.width).toString();
+ annoBox.style.height = (rect.height).toString();
+ this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, 1);
+ }
+ }
+ }
+ //this._selectionText = selRange.cloneContents().textContent || "";
+
+ // clear selection
+ if (sel.empty) { // Chrome
+ sel.empty();
+ } else if (sel.removeAllRanges) { // Firefox
+ sel.removeAllRanges();
+ }
}
+ @action
+ iframeUp = (e: PointerEvent) => {
+ if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
+ this._iframe.contentDocument.addEventListener("pointerup", this.iframeUp);
+ 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));
+ AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX,
+ e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale);
+ }
+ } else AnchorMenu.Instance.fadeOut(true);
+ }
+ @action
+ iframeDown = (e: PointerEvent) => {
+ const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
+ const scale = (this.props.scaling?.() || 1) * mainContBounds.scale;
+ const word = getWordAtPoint(e.target, e.clientX, e.clientY);
+ this._marqueeing = [e.clientX * scale + mainContBounds.translateX,
+ e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale];
+ if (word) {
+ this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp);
+ setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
+ } else {
+ this._isAnnotating = true;
+ this.props.select(false);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ @action
iframeLoaded = (e: any) => {
const iframe = this._iframe;
if (iframe?.contentDocument) {
- if (this._initialScroll !== undefined && this._outerRef.current && this.webpage) {
- this.webpage.scrollTop = this._initialScroll;
+ 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);
+ if (this._initialScroll !== undefined && this._outerRef.current) {
this._outerRef.current.scrollTop = this._initialScroll;
this._initialScroll = undefined;
}
@@ -90,76 +153,57 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
if (href) {
this.submitURL(href.replace(Utils.prepend(""), Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.origin));
- if (this.webpage) {
- this.webpage.scrollTop = NumCast(this.layoutDoc._scrollTop);
- this.webpage.scrollLeft = 0;
+ if (this._outerRef.current) {
+ this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop);
+ this._outerRef.current.scrollLeft = 0;
}
}
})));
iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false);
- iframe.contentDocument.addEventListener('scroll', this.iframeScroll, false);
+ //iframe.contentDocument.addEventListener('scroll', () => !this.active() && this._iframe && (this._iframe.scrollTop = NumCast(this.layoutDoc._scrollTop), false));
+ iframe.contentDocument.addEventListener('scroll', () => {
+ console.log("Scroll = " + this._iframe?.scrollTop)
+ }
+ , true);
}
}
- resetIgnoreScroll = () => {
+ @action
+ setDashScrollTop = (scrollTop: number, timeout: number = 250) => {
+ const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
+ timeout = scrollTop > iframeHeight ? 0 : timeout;
this._scrollTimer && clearTimeout(this._scrollTimer);
- this._scrollTimer = setTimeout(() => {
+ this._scrollTimer = setTimeout(action(() => {
this._scrollTimer = undefined;
- this._ignoreScroll = "";
- }, 250);
- this._outerRef.current && (this._outerRef.current.scrollLeft = 0);
+ if (!LinkDocPreview.LinkInfo && this._outerRef.current &&
+ (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
+ this.layoutDoc._scrollTop = this._outerRef.current.scrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
+ }
+ }), timeout);
}
+ @action
iframeWheel = (e: any) => {
- this._ignoreScroll = "iframe";
- this.resetIgnoreScroll();
- e.stopPropagation();
- }
- onWebWheel = (e: React.WheelEvent) => {
- this._ignoreScroll = "iframe";
- this.goTo(Math.max(0, (this.webpage?.scrollTop || 0) + (this._accumulatedGoTo + 1) * e.deltaY), 100);
- this.resetIgnoreScroll();
- e.stopPropagation();
- }
- onWheel = (e: React.WheelEvent) => {
- this._ignoreScroll = "outer";
- this.resetIgnoreScroll();
- e.stopPropagation();
- }
- iframeScroll = (e: any) => {
- if (!this._ignoreScroll.includes("outer") && this._outerRef.current) {
- this._outerRef.current.scrollTop = this.webpage?.scrollTop || 0;
- this.layoutDoc._scrollTop = this.webpage?.scrollTop;
+ if (!this._scrollTimer) {
+ this._scrollTimer = setTimeout(action(() => this._scrollTimer = undefined), 250); // this turns events off on the iframe which allows scrolling to change direction smoothly
}
}
- onScroll = (e: any) => {
- if (!this._ignoreScroll.includes("iframe") && this.webpage) {
- this.webpage.scrollTop = this._outerRef.current?.scrollTop || 0;
- this.layoutDoc._scrollTop = this._outerRef.current?.scrollTop;
- }
+ onWheel = (e: any) => {
+ e.stopPropagation();
+ e.preventDefault();
}
+ onScroll = (e: any) => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0);
scrollFocus = (doc: Doc, smooth: boolean) => {
- let focusSpeed: Opt<number>;
- if (doc !== this.rootDoc && this.webpage && this._outerRef.current) {
+ if (doc !== this.rootDoc && this._outerRef.current) {
const scrollTo = doc.type === DocumentType.TEXTANCHOR ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
if (scrollTo !== undefined) {
+ const focusSpeed = smooth ? 500 : 0;
this._initialScroll !== undefined && (this._initialScroll = scrollTo);
- if (!LinkDocPreview.LinkInfo) {
- this._ignoreScroll = "iframe|outer";
- this.layoutDoc._scrollTop = scrollTo;
- this._ignoreScroll = "";
- }
- this._ignoreScroll = "iframe|outer";
- this.goTo(scrollTo, focusSpeed = smooth ? 500 : 0);
- setTimeout(() => {
- this._scrollTimer = undefined;
- this._ignoreScroll = "";
- }, focusSpeed);
+ this.goTo(scrollTo, focusSpeed);
+ return focusSpeed;
}
- } else {
- this._initialScroll = NumCast(doc.y);
}
-
- return focusSpeed;
+ this._initialScroll = NumCast(doc.y);
+ return 0;
}
getAnchor = () => {
@@ -182,12 +226,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => !selected && setTimeout(() => {
- this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
this._savedAnnotations.clear();
}));
- document.addEventListener("pointerup", this.onLongPressUp);
- document.addEventListener("pointermove", this.onLongPressMove);
const field = Cast(this.rootDoc[this.props.fieldKey], WebField);
if (field?.url.href.indexOf("youtube") !== -1) {
const youtubeaspect = 400 / 315;
@@ -226,29 +268,22 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
quickScroll = false;
}
- _accumulatedGoTo = 0;
- _resetGoTo: { resetGoTo: { to: number, duration: number } | undefined } = { resetGoTo: undefined };
goTo = (scrollTop: number, duration: number) => {
- if (this._outerRef.current && this.webpage) {
+ if (this._outerRef.current) {
+ const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
+ scrollTop = scrollTop > iframeHeight + 50 ? iframeHeight : scrollTop;
if (duration) {
- if (this._accumulatedGoTo++) {
- this._resetGoTo.resetGoTo = { to: scrollTop, duration };
- } else {
- smoothScroll(duration, [this.webpage as any as HTMLElement, this._outerRef.current], scrollTop, () => this._accumulatedGoTo = 0, this._resetGoTo);
- }
+ smoothScroll(duration, [this._outerRef.current], scrollTop);
+ this.setDashScrollTop(scrollTop, duration);
} else {
- this.webpage.scrollTop = scrollTop;
- this._outerRef.current.scrollTop = scrollTop;
+ this.setDashScrollTop(scrollTop);
}
}
}
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
- document.removeEventListener("pointerup", this.onLongPressUp);
- document.removeEventListener("pointermove", this.onLongPressMove);
- this._iframe?.removeEventListener('wheel', this.iframeWheel);
- this._iframe?.removeEventListener('scroll', this.iframeScroll);
+ this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
}
@action
@@ -258,7 +293,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (future.length) {
history.push(this._url);
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!));
- this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ this._annotationKey = "annotations-" + this.urlHash(this._url);
return true;
}
return false;
@@ -272,7 +307,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
else future.push(this._url);
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!));
- this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ this._annotationKey = "annotations-" + this.urlHash(this._url);
return true;
}
return false;
@@ -299,7 +334,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
future && (future.length = 0);
}
this._url = newUrl;
- this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ this._annotationKey = "annotations-" + this.urlHash(this._url);
this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
} catch (e) {
console.log("WebBox URL error:" + this._url);
@@ -353,114 +388,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
);
}
- editToggleBtn() {
- return <Tooltip title={<div className="dash-tooltip" >{`${this.props.Document.isAnnotating ? "Exit" : "Enter"} annotation mode`}</div>}>
- <div className="webBox-annotationToggle"
- style={{ color: this.props.Document.isAnnotating ? "black" : "white", backgroundColor: this.props.Document.isAnnotating ? "white" : "black" }}
- onClick={action(() => this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating)}>
- <FontAwesomeIcon icon="edit" size="sm" />
- </div>
- </Tooltip>;
- }
-
- _ignore = 0;
- onPreWheel = (e: React.WheelEvent) => this._ignore = e.timeStamp;
- onPrePointer = (e: React.PointerEvent) => this._ignore = e.timeStamp;
- onPostPointer = (e: React.PointerEvent) => this._ignore !== e.timeStamp && e.stopPropagation();
- onPostWheel = (e: React.WheelEvent) => this._ignore !== e.timeStamp && e.stopPropagation();
-
- onLongPressDown = (e: React.PointerEvent) => {
- this._pressX = e.clientX;
- this._pressY = e.clientY;
-
- // find the pressed element in the iframe (currently only works if its an img)
- let pressedElement: HTMLElement | undefined;
- let pressedBound: ClientRect | undefined;
- let selectedText: string = "";
- let pressedImg: boolean = false;
- if (this._iframe) {
- const B = this._iframe.getBoundingClientRect();
- const iframeDoc = this._iframe.contentDocument;
- if (B && iframeDoc) {
- // TODO: this only works when scale = 1 as it is currently only inteded for mobile upload
- const element = iframeDoc.elementFromPoint(this._pressX - B.left, this._pressY - B.top);
- if (element && element.nodeName === "IMG") {
- pressedBound = element.getBoundingClientRect();
- pressedElement = element.cloneNode(true) as HTMLElement;
- pressedImg = true;
- } else {
- // check if there is selected text
- const text = iframeDoc.getSelection();
- if (text && text.toString().length > 0) {
- selectedText = text.toString();
-
- // get html of the selected text
- const range = text.getRangeAt(0);
- const contents = range.cloneContents();
- const div = document.createElement("div");
- div.appendChild(contents);
- pressedElement = div;
-
- pressedBound = range.getBoundingClientRect();
- }
- }
- }
- }
-
- // mark the pressed element
- if (pressedElement && pressedBound) {
- if (this._iframeIndicatorRef.current) {
- this._iframeIndicatorRef.current.style.top = pressedBound.top + "px";
- this._iframeIndicatorRef.current.style.left = pressedBound.left + "px";
- this._iframeIndicatorRef.current.style.width = pressedBound.width + "px";
- this._iframeIndicatorRef.current.style.height = pressedBound.height + "px";
- this._iframeIndicatorRef.current.classList.add("active");
- }
- }
-
- // start dragging the pressed element if long pressed
- this._longPressSecondsHack = setTimeout(() => {
- if (pressedImg && pressedElement && pressedBound) {
- e.stopPropagation();
- e.preventDefault();
- if (pressedElement.nodeName === "IMG") {
- const src = pressedElement.getAttribute("src"); // TODO: may not always work
- if (src) {
- const doc = Docs.Create.ImageDocument(src);
- ImageUtils.ExtractExif(doc);
-
- // add clone to div so that dragging ghost is placed properly
- if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement);
-
- const dragData = new DragManager.DocumentDragData([doc]);
- DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX, this._pressY, { hideSource: true });
- }
- }
- } else if (selectedText && pressedBound && pressedElement) {
- e.stopPropagation();
- e.preventDefault();
- // create doc with the selected text's html
- const doc = Docs.Create.HtmlDocument(pressedElement.innerHTML);
-
- // create dragging ghost with the selected text
- if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement);
-
- // start the drag
- const dragData = new DragManager.DocumentDragData([doc]);
- DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX - pressedBound.top, this._pressY - pressedBound.top, { hideSource: true });
- }
- }, 1500);
- }
- onLongPressMove = (e: PointerEvent) => {
- // this._pressX = e.clientX;
- // this._pressY = e.clientY;
- }
- onLongPressUp = (e: PointerEvent) => {
- this._longPressSecondsHack && clearTimeout(this._longPressSecondsHack);
- this._iframeIndicatorRef.current?.classList.remove("active");
- while (this._iframeDragRef.current?.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild);
- }
-
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
@@ -479,12 +406,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
} else if (field instanceof WebField) {
const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href;
// view = <iframe className="webBox-iframe" src={url} onLoad={e => { e.currentTarget.before((e.currentTarget.contentDocument?.body || e.currentTarget.contentDocument)?.children[0]!); e.currentTarget.remove(); }}
- view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
+ view = <iframe className="webBox-iframe" enable-annotation={"true"}
+ style={{ pointerEvents: this._scrollTimer ? "none" : undefined }}
+ ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
// the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
// sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin"} />;
} else {
- view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={"https://crossorigin.me/https://cs.brown.edu"} />;
+ view = <iframe className="webBox-iframe" enable-annotation={"true"}
+ 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"} />;
}
return view;
}
@@ -509,7 +440,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
} else {
this.layoutDoc.nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]);
}
- this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) / NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]))
+ this.layoutDoc._width = NumCast(this.layoutDoc._nativeWidth) * (NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) / NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"]));
}
sidebarKey = () => this.fieldKey + "-sidebar";
sidebarFiltersHeight = () => 50;
@@ -576,36 +507,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
@computed
get content() {
- const frozen = !this.props.isSelected() || DocumentDecorations.Instance?.Interacting;
- const scale = this.props.scaling?.() || 1;
- return (<>
- <div className={"webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
- style={{
- width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / scale}%`,
- height: `${100 / scale}%`,
- transform: `scale(${scale})`
- }}
- onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
- {this.urlContent}
- </div>
- {!frozen ? (null) :
- <div className="webBox-overlay" style={{ pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? undefined : "all" }}
- onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
- <div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} >
- <div className="indicator" ref={this._iframeIndicatorRef}></div>
- <div className="dragger" ref={this._iframeDragRef}></div>
- </div>
- </div>}
- </>);
+ return <div className={"webBox-cont" + (this.active() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
+ style={{ width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / (this.props.scaling?.() || 1)}%`, }}>
+ {this.urlContent}
+ </div>;
}
- @computed get allAnnotations() { return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]); }
- @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); }
+ showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
+ @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); }
@computed get annotationLayer() {
TraceMobx();
return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
- {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} showInfo={emptyFunction} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+ {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
+ <Annotation {...this.props} fieldKey={this.annotationKey} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)
}
</div>;
}
@@ -617,9 +531,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this.props.select(false);
}
}
-
- @action
- finishMarquee = () => this._marqueeing = undefined;
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
+ @action finishMarquee = (x?: number, y?: number) => {
+ this._marqueeing = undefined;
+ this._isAnnotating = false;
+ x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false);
+ }
panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (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);
@@ -628,50 +545,54 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false;
const scale = this.props.scaling?.() || 1;
return (
- <div className="webBox" ref={this._mainCont} >
+ <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.active() || SnappingManager.GetIsDragging() ? undefined : "none" }} >
<div className={`webBox-container`}
style={{ pointerEvents: inactiveLayer ? "none" : undefined }}
- onWheel={this.onWebWheel}
onContextMenu={this.specificContextMenu}>
<base target="_blank" />
- {this.content}
<div className={"webBox-outerContent"} ref={this._outerRef}
style={{
- width: `calc(${100 / scale}% - ${this.sidebarWidth()}px)`, height: `${100 / scale}%`, transform: `scale(${scale})`,
- pointerEvents: !this.layoutDoc.isAnnotating || inactiveLayer ? "none" : "all"
+ width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale}px)`,
+ height: `${100 / scale}%`,
+ transform: `scale(${scale})`,
+ pointerEvents: inactiveLayer ? "none" : undefined
}}
onWheel={this.onWheel}
- onPointerDown={this.onMarqueeDown}
onScroll={this.onScroll}
+ onPointerDown={this.onMarqueeDown}
>
<div className={"webBox-innerContent"} style={{
height: NumCast(this.scrollHeight, 50),
pointerEvents: inactiveLayer ? "none" : undefined
}}>
+ {this.content}
<CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
renderDepth={this.props.renderDepth + 1}
CollectionView={undefined}
fieldKey={this.annotationKey}
isAnnotationOverlay={true}
scaling={returnOne}
+ pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
ScreenToLocalTransform={this.scrollXf}
+ setPreviewCursor={this.setPreviewCursor}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
select={emptyFunction}
active={this.active}
whenActiveChanged={this.whenActiveChanged} />
+ {this.annotationLayer}
</div>
</div>
- {this.annotationLayer}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
<MarqueeAnnotator rootDoc={this.rootDoc}
anchorMenuClick={this.anchorMenuClick}
- scrollTop={NumCast(this.rootDoc._scrollTop)}
- down={this._marqueeing} scaling={this.props.scaling}
+ scrollTop={0}
+ down={this._marqueeing} scaling={returnOne}
addDocument={this.addDocument}
+ docView={this.props.docViewPath().lastElement()}
finishMarquee={this.finishMarquee}
savedAnnotations={this._savedAnnotations}
annotationLayer={this._annotationLayer.current}
@@ -682,7 +603,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
<FontAwesomeIcon style={{ color: "white" }} icon={"chevron-left"} size="sm" />
</button>
{this.sidebarOverlay}
- {this.props.isSelected() ? this.editToggleBtn() : null}
</div>);
}
} \ 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 9259e6c25..e4c481014 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -84,9 +84,6 @@ type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, da
export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
- public static get DefaultLayout() {
- return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
- }
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"];
@@ -229,7 +226,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return target;
};
- DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, this.getAnchor, targetCreator), e.pageX, e.pageY);
+ DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), this.getAnchor, targetCreator), e.pageX, e.pageY);
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
@@ -1203,19 +1200,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
prosediv && (prosediv.keeplocation = undefined);
const pos = this._editorView?.state.selection.$from.pos || 1;
keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
- const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos);
-
- // jump rich text menu to this textbox
- const bounds = this._ref.current?.getBoundingClientRect();
- if (bounds && this.layoutDoc._chromeStatus !== "disabled" && RichTextMenu.Instance) {
- const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
- let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
- if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
- y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
- }
- this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
- setTimeout(() => window.document.activeElement === this.ProseRef?.children[0] && RichTextMenu.Instance.jumpTo(x, y), 250);
- }
+
+ this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
}
onPointerWheel = (e: React.WheelEvent): void => {
// if a text note is selected and scrollable, stop event to prevent, say, outer collection from zooming.
@@ -1421,11 +1407,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
</div>;
}
@computed get sidebarHandle() {
+ TraceMobx();
const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
return (!annotated && !this.active()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
style={{
left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`,
- background: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""))
+ background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""))
}} />;
}
@computed get sidebarCollection() {
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 0a1e0ba8f..1e2d72254 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -51,7 +51,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public PinToPres: () => void = unimplementedFunction;
public MakePushpin: () => void = unimplementedFunction;
public IsPushpin: () => boolean = returnFalse;
- public Marquee: { left: number; top: number; width: number; height: number; } | undefined;
public get Active() { return this._left > 0; }
constructor(props: Readonly<{}>) {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c9bee5101..91bb321b2 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, ObservableMap } from "mobx";
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
@@ -65,7 +65,7 @@ interface IViewerProps extends FieldViewProps {
export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) {
static _annotationStyle: any = addStyleSheet();
@observable private _pageSizes: { width: number, height: number }[] = [];
- @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+ @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;
@@ -140,8 +140,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
this._disposers.selected = reaction(() => this.props.isSelected(),
selected => {
if (!selected) {
- this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
- this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
+ Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
+ Array.from(this._savedAnnotations.keys()).forEach(k => this._savedAnnotations.set(k, []));
}
(SelectionManager.Views().length === 1) && this.setupPdfJsViewer();
},
@@ -390,7 +390,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
// clear out old marquees and initialize menu for new selection
AnchorMenu.Instance.Status = "marquee";
- this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
this._savedAnnotations.clear();
this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" });
document.addEventListener("pointerup", this.onSelectEnd);
@@ -400,7 +400,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
@action
- finishMarquee = () => {
+ finishMarquee = (x?: number, y?: number) => {
this._marqueeing = undefined;
this._textSelecting = true;
document.removeEventListener("pointermove", this.onSelectMove);
@@ -574,6 +574,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
anchorMenuClick={this.props.anchorMenuClick}
addDocument={this.addDocument}
finishMarquee={this.finishMarquee}
+ docView={this.props.docViewPath().lastElement()}
getPageFromScroll={this.getPageFromScroll}
savedAnnotations={this._savedAnnotations}
annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}