aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/WebBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/WebBox.tsx')
-rw-r--r--src/client/views/nodes/WebBox.tsx132
1 files changed, 103 insertions, 29 deletions
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 19135b6dd..8e6586735 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -16,9 +16,11 @@ import { TraceMobx } from "../../../fields/util";
import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { KeyCodes } from "../../util/KeyCodes";
import { Scripting } from "../../util/Scripting";
import { SnappingManager } from "../../util/SnappingManager";
import { undoBatch } from "../../util/UndoManager";
+import { MarqueeOptionsMenu } from "../collections/collectionFreeForm";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
@@ -30,6 +32,8 @@ import { MarqueeAnnotator } from "../MarqueeAnnotator";
import { AnchorMenu } from "../pdf/AnchorMenu";
import { Annotation } from "../pdf/Annotation";
import { SidebarAnnos } from "../SidebarAnnos";
+import { StyleProp } from "../StyleProvider";
+import { DocumentViewProps } from "./DocumentView";
import { FieldView, FieldViewProps } from './FieldView';
import { LinkDocPreview } from "./LinkDocPreview";
import "./WebBox.scss";
@@ -52,6 +56,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _keyInput = React.createRef<HTMLInputElement>();
private _initialScroll: Opt<number>;
private _sidebarRef = React.createRef<SidebarAnnos>();
+ private _searchRef = React.createRef<HTMLInputElement>();
+ private _searchString = "";
+ @observable private _searching: boolean = false;
+ @observable _showSidebar = false;
@observable private _scrollTimer: any;
@observable private _overlayAnnoInfo: Opt<Doc>;
@observable private _marqueeing: number[] | undefined;
@@ -69,12 +77,28 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
constructor(props: any) {
super(props);
- // if (true) {// his.webField) {
- // Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
- // Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
- // }
+ Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
+ Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
}
+ @action
+ search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
+ if (!this._searching && !clear) {
+ this._searching = true;
+ setTimeout(() => {
+ this._searchRef.current?.focus();
+ this._searchRef.current?.select();
+ this._searchRef.current?.setRangeText(searchString);
+ });
+ }
+ if (clear) {
+ this._iframe?.contentWindow?.getSelection()?.empty();
+ }
+ if (searchString) {
+ (this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true);
+ }
+ return true;
+ }
async componentDidMount() {
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.
@@ -130,6 +154,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
+ this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
}
@action
@@ -193,8 +218,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
iframeUp = (e: PointerEvent) => {
+ this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
- 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();
@@ -203,7 +228,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
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) => {
@@ -215,7 +240,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
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._iframeClick = this._iframe ?? undefined;
@@ -224,6 +248,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
e.stopPropagation();
e.preventDefault();
}
+
+ // bcz: hack - iframe grabs all events which messes up how we handle contextMenus. So this super naively simulates the event stack to get the specific menu items and the doc view menu items.
+ if (e.button === 2 || (e.button === 0 && e.altKey)) {
+ e.preventDefault();
+ e.stopPropagation();
+ ContextMenu.Instance.closeMenu();
+ ContextMenu.Instance.setIgnoreEvents(true);
+ }
}
getScrollHeight = () => this._scrollHeight;
@@ -237,6 +269,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
iframeLoaded = (e: any) => {
const iframe = this._iframe;
+ this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp);
if (iframe?.contentDocument) {
iframe?.contentDocument.addEventListener("pointerdown", this.iframeDown);
this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
@@ -391,20 +424,22 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
}
- specificContextMenu = (e: React.MouseEvent): void => {
+ specificContextMenu = (e: React.MouseEvent | PointerEvent): void => {
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
- funcs.push({
- description: (!this.layoutDoc.allowReflow ? "Allow" : "Prevent") + " Reflow", event: () => {
- const nw = !this.layoutDoc.allowReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
- this.layoutDoc.allowReflow = !nw;
- if (nw) {
- Doc.SetInPlace(this.layoutDoc, this.fieldKey + "-nativeWidth", nw, true);
- }
- }, icon: "snowflake"
- });
- cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+ if (!cm.findByDescription("Options...")) {
+ !Doc.UserDoc().noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
+ funcs.push({
+ description: (!this.layoutDoc.allowReflow ? "Allow" : "Prevent") + " Reflow", event: () => {
+ const nw = !this.layoutDoc.allowReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
+ this.layoutDoc.allowReflow = !nw;
+ if (nw) {
+ Doc.SetInPlace(this.layoutDoc, this.fieldKey + "-nativeWidth", nw, true);
+ }
+ }, icon: "snowflake"
+ });
+ cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+ }
}
@action
@@ -417,11 +452,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false);
}
}
- @action finishMarquee = (x?: number, y?: number) => {
+ @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
this._marqueeing = undefined;
this._isAnnotating = false;
this._iframeClick = undefined;
- x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false);
+ if (x !== undefined && y !== undefined) {
+ this._setPreviewCursor?.(x, y, false, false);
+ ContextMenu.Instance.closeMenu();
+ ContextMenu.Instance.setIgnoreEvents(false);
+ if (e?.button === 2 || e?.altKey) {
+ this.specificContextMenu(undefined as any);
+ this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+ }
+ }
}
@computed get urlContent() {
@@ -491,7 +534,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
NumCast(this.layoutDoc.nativeWidth)
@computed get content() {
- return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
+ const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
+ return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
style={{ width: !this.layoutDoc.allowReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
{this.urlContent}
</div>;
@@ -499,26 +543,54 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get annotationLayer() {
TraceMobx();
+ const pe = this.pointerEvents();
return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
{this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} fieldKey={this.annotationKey} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)
- }
+ <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={pe} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)}
</div>;
}
- @observable _showSidebar = false;
@computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+ @computed get searchUI() {
+ return <div className="webBox-ui"
+ onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}>
+ <div className="webBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
+ <button className="webBox-overlayButton" title={"search"} />
+ <input className="webBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged}
+ onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey)} />
+ <button className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
+ <FontAwesomeIcon icon="search" size="sm" />
+ </button>
+ </div>
+ <button className="webBox-overlayButton" title={"search"}
+ onClick={action(() => { this._searching = !this._searching; this.search("", false, true); })} >
+ <div className="webBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()} />
+ <div className="webBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
+ <FontAwesomeIcon icon={this._searching ? "times" : "search"} size="lg" />
+ </div>
+ </button>
+ </div>;
+ }
+ searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func;
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);
scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
+ basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+ childStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+ if (doc instanceof Doc && property === StyleProp.PointerEvents) {
+ if (doc.textInlineAnnotations) return "none";
+ }
+ return this.props.styleProvider?.(doc, props, property);
+ }
+ pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none";
render() {
- const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined;
+ const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents ? this.props.pointerEvents as any : undefined;
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const scale = previewScale * (this.props.scaling?.() || 1);
const renderAnnotations = (docFilters?: () => string[]) =>
@@ -533,7 +605,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
ScreenToLocalTransform={this.scrollXf}
scaling={returnOne}
dropAction={"alias"}
- docFilters={docFilters || this.props.docFilters}
+ docFilters={docFilters || this.basicFilter}
dontRenderDocuments={docFilters ? false : true}
select={emptyFunction}
ContentScaling={returnOne}
@@ -542,10 +614,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.sidebarAddDocument}
- childPointerEvents={true}
+ styleProvider={this.childStyleProvider}
pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
return (
- <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.props.isContentActive() ? "all" : this.props.isContentActive() || SnappingManager.GetIsDragging() ? undefined : "none" }} >
+ <div className="webBox" ref={this._mainCont}
+ style={{ pointerEvents: this.pointerEvents() }} >
<div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}>
<base target="_blank" />
<div className={"webBox-outerContent"} ref={this._outerRef}
@@ -605,6 +678,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onPointerDown={this.sidebarBtnDown} >
<FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" />
</div>
+ {!this.props.isContentActive() ? (null) : this.searchUI}
</div>);
}
}