aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2022-05-13 11:58:10 -0400
committerbobzel <zzzman@gmail.com>2022-05-13 11:58:10 -0400
commite40a1e248da90af698d5ff64bd5d63d11211e6dc (patch)
treebe471df9e7e52d5fd915daa54b801ace16347d7c /src
parent5b71f2ffeabbc9f4fbb3fbceabdb7d542c80233c (diff)
fixed crashing bug when quickly re-updating webbox (or other) icon. fixed infinite loop in FilterBox. changed TimeView to scale WebBox's. fixed issues with timeView filters. fixed bugs with webBox scrolling on startup, following links, etc.
Diffstat (limited to 'src')
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx18
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx4
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx23
-rw-r--r--src/client/views/nodes/FilterBox.tsx10
-rw-r--r--src/client/views/nodes/WebBox.tsx136
-rw-r--r--src/client/views/pdf/Annotation.tsx37
-rw-r--r--src/client/views/pdf/PDFViewer.tsx2
-rw-r--r--src/client/views/search/SearchBox.tsx4
-rw-r--r--src/fields/Doc.ts32
-rw-r--r--src/server/ApiManagers/UploadManager.ts14
12 files changed, 166 insertions, 118 deletions
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index e63ea7151..4f6f45d2f 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -10,6 +10,7 @@ import { ComputedField, ScriptField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { ScriptingGlobals } from "../../util/ScriptingGlobals";
import { ContextMenu } from "../ContextMenu";
@@ -128,6 +129,7 @@ export class CollectionTimeView extends CollectionSubView() {
}
}
+ dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF;
@computed get contents() {
return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : "none" }}
onClick={this.contentsDown}>
@@ -137,6 +139,7 @@ export class CollectionTimeView extends CollectionSubView() {
childClickScript={this._childClickedScript}
viewDefDivClick={this._viewDefDivClick}
childFreezeDimensions={true}
+ dontScaleFilter={this.dontScaleFilter}
layoutEngine={this.layoutEngine} />
</div>;
}
@@ -174,7 +177,7 @@ export class CollectionTimeView extends CollectionSubView() {
typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] !== "#" || fieldKey === "#") && (fieldKey === "tags" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey)));
Array.from(keySet).map(fieldKey =>
docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" }));
- docItems.push({ description: ":(null)", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
+ docItems.push({ description: ":default", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
ContextMenu.Instance.displayMenu(x, y, ":");
@@ -234,16 +237,17 @@ export class CollectionTimeView extends CollectionSubView() {
}
ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
+ const pivotField = StrCast(pivotDoc._pivotField) || "author";
let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
- pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotDoc._pivotField;
+ pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotField;
pivotDoc._prevFilterIndex = ++prevFilterIndex;
- runInAction(() => {
- pivotDoc._docFilters = new List();
+ pivotDoc._docFilters = new List();
+ setTimeout(action(() => {
const filterVals = (bounds.payload as string[]);
- filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+ filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, "check"));
const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
@@ -252,11 +256,11 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
}
const newFilters = StrListCast(pivotDoc._docFilters);
if (newFilters.length && originalFilter.length &&
- newFilters[newFilters.length - 1] === originalFilter[originalFilter.length - 1]) {
+ newFilters.lastElement() === originalFilter.lastElement()) {
pivotDoc._prevFilterIndex = --prevFilterIndex;
pivotDoc["_prevDocFilter" + prevFilterIndex] = undefined;
pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = undefined;
pivotDoc["_prevPivotFields" + prevFilterIndex] = undefined;
}
- });
+ }));
}); \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 16c7df311..9de2cfcf9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -147,7 +147,7 @@ export function computePivotLayout(
const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
let nonNumbers = 0;
- const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField);
+ const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || "author";
childPairs.map(pair => {
const lval = pivotFieldKey === "#" || pivotFieldKey === "tags" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 19b6612b0..4f79d19ad 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -75,6 +75,7 @@ export type collectionFreeformViewProps = {
scaleField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
+ dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
// However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
};
@@ -1197,10 +1198,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
layerProvider={this.props.layerProvider}
dataProvider={this.childDataProvider}
sizeProvider={this.childSizeProvider}
- freezeDimensions={this.props.childFreezeDimensions}
+ freezeDimensions={BoolCast(this.props.Document.childFreezeDimensions, this.props.childFreezeDimensions)}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
bringToFront={this.bringToFront}
showTitle={this.props.childShowTitle}
+ dontScaleFilter={this.props.dontScaleFilter}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.pointerEvents}
jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index abb01c466..49cd39f43 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -87,7 +87,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
return whichDoc ? <>
<DocumentView
ref={(r) => {
- whichDoc !== targetDoc && r?.focus(targetDoc);
+ whichDoc !== targetDoc && r?.focus(whichDoc);
}}
{...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
isContentActive={returnFalse}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e117be0c2..f30d093a1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -168,6 +168,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
radialMenu?: String[];
LayoutTemplateString?: string;
dontCenter?: "x" | "y" | "xy";
+ dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling
ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
NativeWidth?: () => number;
NativeHeight?: () => number;
@@ -249,6 +250,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
}
}
+ @action
cleanupHandlers(unbrush: boolean) {
this._dropDisposer?.();
this._multiTouchDisposer?.();
@@ -856,9 +858,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this.props.isContentActive()) ? true : undefined;
}
@observable _retryThumb = 1;
- thumbShown = () => !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
- !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
- !this._componentView?.isAnyChildContentActive?.() ? true : false
+ thumbShown = () => {
+ return !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
+ !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
+ !Doc.isBrushedHighlightedDegree(this.props.Document) &&
+ !this._componentView?.isAnyChildContentActive?.() ? true : false;
+ }
@computed get contents() {
TraceMobx();
const audioView = !this.layoutDoc._showAudio ? (null) :
@@ -874,7 +879,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
{!this._retryThumb || !this.thumbShown() ? (null) :
- <img style={{ background: "white", top: 0, position: "absolute" }} src={this.thumb} // + '?d=' + (new Date()).getTime()}
+ <img style={{ background: "white", top: 0, position: "relative" }} src={this.thumb} // + '?d=' + (new Date()).getTime()}
width={this.props.PanelWidth()} height={this.props.PanelHeight()}
onError={(e: any) => {
setTimeout(action(() => this._retryThumb = 0), 0);
@@ -1109,8 +1114,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
render() {
TraceMobx();
const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
- const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange"][highlightIndex];
- const highlightStyle = ["solid", "dashed", "solid", "solid"][highlightIndex];
+ const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange", "lightBlue"][highlightIndex];
+ const highlightStyle = ["solid", "dashed", "solid", "solid", "solid"][highlightIndex];
const excludeTypes = !this.props.treeViewDoc && highlightIndex < 3 ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
@@ -1126,8 +1131,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onKeyDown={this.onKeyDown}
onPointerDown={this.onPointerDown}
onClick={this.onClick}
- onPointerEnter={e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)}
- onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)}
+ onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))}
+ onPointerLeave={action(e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document))}
style={{
display: this.hidden ? "inline" : undefined,
borderRadius: this.borderRounding,
@@ -1207,7 +1212,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
@computed get shouldNotScale() {
return (this.fitWidth && !this.nativeWidth) ||
- this.props.ContainingCollectionView?.collectionViewType === CollectionViewType.Time ||
+ this.props.dontScaleFilter?.(this.Document) ||
this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
}
@computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index dc0ab5ec2..78adf63cb 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -96,17 +96,15 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
runInAction(() => this._loaded = true);
}, { fireImmediately: true });
}
- @observable _allDocs = [] as Doc[];
+
@computed get allDocs() {
// trace();
+ const allDocs = new Set<Doc>();
const targetDoc = FilterBox.targetDoc;
if (this._loaded && targetDoc) {
- const allDocs = new Set<Doc>();
- const activeTabs = FilterBox.targetDocChildren;
- SearchBox.foreachRecursiveDoc(activeTabs, (depth, doc) => allDocs.add(doc));
- setTimeout(action(() => this._allDocs = Array.from(allDocs)));
+ SearchBox.foreachRecursiveDoc(FilterBox.targetDocChildren, (depth, doc) => allDocs.add(doc));
}
- return this._allDocs;
+ return Array.from(allDocs);
}
@computed get _allFacets() {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 64655ba0b..af7a2b941 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -40,7 +40,6 @@ import React = require("react");
const { CreateImage } = require("./WebBoxRenderer");
const _global = (window /* browser */ || global /* node */) as any;
const htmlToText = require("html-to-text");
-
@observer
export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
@@ -52,7 +51,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _disposers: { [name: string]: IReactionDisposer } = {};
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _keyInput = React.createRef<HTMLInputElement>();
- private _initialScroll: Opt<number>;
+ private _initialScroll: Opt<number> = NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop));
private _sidebarRef = React.createRef<SidebarAnnos>();
private _searchRef = React.createRef<HTMLInputElement>();
private _searchString = "";
@@ -68,14 +67,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@observable private _iframeClick: HTMLIFrameElement | undefined = undefined;
@observable private _iframe: HTMLIFrameElement | null = null;
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
- @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight, 1500);
+ @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight);
@computed get _url() { return this.webField?.toString() || ""; }
@computed get _urlHash() { return this._url ? WebBox.urlHash(this._url) + "" : ""; }
- @computed get scrollHeight() { return this._scrollHeight; }
+ @computed get scrollHeight() { return Math.max(this.layoutDoc[HeightSym](), this._scrollHeight); }
@computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); }
@computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); }
@computed get webField() { return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; }
- @computed get webThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; }
+ @computed get webThumb() { return this.props.thumbShown?.() && ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumbScrollTop === this.layoutDoc._scrollTop ? this.layoutDoc.thumb : undefined))?.url }
constructor(props: any) {
super(props);
@@ -100,14 +99,49 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
return true;
}
+ @action
setScrollPos = (pos: number) => {
if (!this._outerRef.current || this._outerRef.current.scrollHeight < pos) {
- setTimeout(() => this.setScrollPos(pos), 250);
+ if (this._webPageHasBeenRendered) setTimeout(() => this.setScrollPos(pos), 250);
} else {
this._outerRef.current.scrollTop = pos;
this._initialScroll = undefined;
}
}
+
+ lockout = false;
+ updateThumb = async () => {
+ const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href;
+ const scrollTop = NumCast(this.layoutDoc._scrollTop);
+ if (!this.lockout && this._iframe && !imageBitmap && scrollTop !== this.layoutDoc.thumbScrollTop) {
+ var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
+ if (!htmlString) {
+ htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
+ }
+ this.layoutDoc.thumb = undefined;
+ this.lockout = true; // lock to prevent multiple thumb updates.
+ const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
+ CreateImage(
+ this._webUrl.endsWith("/") ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl,
+ this._iframe.contentDocument?.styleSheets ?? [],
+ htmlString,
+ nativeWidth,
+ nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
+ scrollTop
+ ).then
+ ((data_url: any) => {
+ VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
+ returnedfilename => setTimeout(action(() => {
+ this.lockout = false;
+ this.layoutDoc.thumb = new ImageField(returnedfilename);
+ this.layoutDoc.thumbScrollTop = scrollTop
+ }), 500));
+ })
+ .catch(function (error: any) {
+ console.error('oops, something went wrong!', error);
+ });
+ }
+ }
async componentDidMount() {
this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
@@ -117,45 +151,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`);
this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`);
});
- reaction(() => this.props.isSelected(),
+ reaction(() => this.props.isSelected() || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document),
async (selected) => {
if (selected) {
this._webPageHasBeenRendered = true;
- setTimeout(action(() => {
- this._scrollHeight = Math.max(this.scrollHeight, this._iframe?.contentDocument?.body.scrollHeight || 0);
- if (this._initialScroll !== undefined) {
- this.setScrollPos(this._initialScroll);
- }
- }));
} else if ((!this.props.isContentActive() || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail)
!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty
LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty.
- const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href;
- if (this._iframe && !imageBitmap) {
- var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
- if (!htmlString) {
- htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
- }
- this.layoutDoc.thumb = undefined;
- const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
- CreateImage(
- this._webUrl.endsWith("/") ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl,
- this._iframe.contentDocument?.styleSheets ?? [],
- htmlString,
- nativeWidth,
- nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
- NumCast(this.layoutDoc._scrollTop)
- ).then
- ((data_url: any) => {
- VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
- returnedfilename => setTimeout(action(() => this.layoutDoc.thumb = new ImageField(returnedfilename)), 500));
- })
- .catch(function (error: any) {
- console.error('oops, something went wrong!', error);
- });
- }
+ this.updateThumb();
}
- }, { fireImmediately: this.props.isSelected() });
+ }, { fireImmediately: this.props.isSelected() || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegree(this.props.Document) ? true : false) });
this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight,
autoHeight => {
@@ -183,21 +188,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
}
- var quickScroll = true;
this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop),
(scrollTop) => {
- if (quickScroll) this._initialScroll = scrollTop;
- else {
- const viewTrans = StrCast(this.Document._viewTransition);
- const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
- const durationSecStr = viewTrans.match(/([0-9.]*)s/);
- const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
- this.goTo(scrollTop, duration);
- }
+ const viewTrans = StrCast(this.Document._viewTransition);
+ const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+ const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+ const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+ this.goTo(scrollTop, duration);
},
{ fireImmediately: true }
);
- quickScroll = false;
}
@action componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
@@ -234,7 +234,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected
scrollFocus = (doc: Doc, smooth: boolean) => {
- this.props.select(false); // hack to preven the webBox from being replaced with a thumbnail that currently doesn't show the the anchors
if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth);
if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) {
this.toggleSidebar(!smooth);
@@ -242,15 +241,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
if (doc !== this.rootDoc && this._outerRef.current) {
const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
- const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1, this.getScrollHeight());
- if (scrollTo !== undefined) {
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1,
+ Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight()));
+ if (scrollTo !== undefined && this._initialScroll === undefined) {
const focusSpeed = smooth ? 500 : 0;
- this._initialScroll !== undefined && (this._initialScroll = scrollTo);
this.goTo(scrollTo, focusSpeed);
return focusSpeed;
+ } else if (!this._webPageHasBeenRendered || !this.getScrollHeight() || this._initialScroll !== undefined) {
+ this._initialScroll = scrollTo;
}
}
- this._initialScroll = NumCast(this.layoutDoc._scrollTop);
return undefined;
}
@@ -324,6 +324,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
iframeLoaded = (e: any) => {
const iframe = this._iframe;
+ if (this._initialScroll !== undefined) {
+ this.setScrollPos(this._initialScroll);
+ }
let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend("") + "/corsProxy/", "") ?? this._url.toString());
if (requrlraw !== this._url.toString()) {
if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
@@ -342,8 +345,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (iframe?.contentDocument) {
iframe.contentDocument.addEventListener("pointerup", this.iframeUp);
iframe.contentDocument.addEventListener("pointerdown", this.iframeDown);
- this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
- setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
+ this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument.body.scrollHeight);
+ setTimeout(action(() => this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
iframe.setAttribute("enable-annotation", "true");
iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => {
let href = "";
@@ -374,29 +377,29 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
setDashScrollTop = (scrollTop: number, timeout: number = 250) => {
- const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
- timeout = scrollTop > iframeHeight ? 0 : timeout;
+ const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
this._scrollTimer && clearTimeout(this._scrollTimer);
this._scrollTimer = setTimeout(action(() => {
this._scrollTimer = undefined;
if (!LinkDocPreview.LinkInfo && this._outerRef.current &&
(!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
- this.layoutDoc._scrollTop = this._outerRef.current.scrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
+ this.layoutDoc.thumb = undefined;
+ this.layoutDoc.thumbScrollTop = undefined;
+ this.layoutDoc.scrollTop = this._outerRef.current.scrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
}
}), timeout);
}
goTo = (scrollTop: number, duration: number) => {
if (this._outerRef.current) {
- const iframeHeight = Math.max(1000, this._scrollHeight - this.panelHeight());
- scrollTop = scrollTop > iframeHeight + 50 ? iframeHeight : scrollTop;
+ const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
if (duration) {
smoothScroll(duration, [this._outerRef.current], scrollTop);
this.setDashScrollTop(scrollTop, duration);
} else {
this.setDashScrollTop(scrollTop);
}
- }
+ } else this._initialScroll = scrollTop;
}
forward = (checkAvailable?: boolean) => {
@@ -456,6 +459,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (url && !preview) {
this.dataDoc[this.fieldKey + "-history"] = new List<string>([...(history || []), url]);
this.layoutDoc._scrollTop = 0;
+ if (this._webPageHasBeenRendered) {
+ this.layoutDoc.thumb = undefined;
+ this.layoutDoc.thumbScrollTop = undefined;
+ }
future && (future.length = 0);
}
if (!preview) {
@@ -566,6 +573,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get urlContent() {
if (this._hackHide || (this.webThumb && (!this._webPageHasBeenRendered && LightboxView.LightboxDoc !== this.rootDoc))) return (null);
+ this.props.thumbShown?.();
const field = this.dataDoc[this.props.fieldKey];
let view;
if (field instanceof HtmlField) {
@@ -583,7 +591,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
style={{ pointerEvents: this._scrollTimer ? "none" : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly
ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={"https://crossorigin.me/https://cs.brown.edu"} />;
}
- setTimeout(action(() => this._webPageHasBeenRendered = true));
+ setTimeout(action(() => {
+ this._scrollHeight = Math.max(this._scrollHeight, this._iframe && this._iframe.contentDocument && this._iframe.contentDocument.body ? this._iframe.contentDocument.body.scrollHeight : 0);
+ if (this._initialScroll === undefined && !this._webPageHasBeenRendered) this.setScrollPos(NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop)));
+ this._webPageHasBeenRendered = true;
+ }));
return view;
}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 218f37f3d..fb2e33e2a 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -22,7 +22,11 @@ interface IAnnotationProps extends FieldViewProps {
export
class Annotation extends React.Component<IAnnotationProps> {
render() {
- return DocListCast(this.props.anno.textInlineAnnotations).map(a => <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />);
+ return <div>
+ {DocListCast(this.props.anno.textInlineAnnotations).map(a =>
+ <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
+ )}
+ </div>
}
}
@@ -32,23 +36,10 @@ interface IRegionAnnotationProps extends IAnnotationProps {
}
@observer
class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
- private _disposers: { [name: string]: IReactionDisposer } = {};
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- @observable _brushed: boolean = false;
@computed get annoTextRegion() { return Cast(this.props.document.annoTextRegion, Doc, null) || this.props.document; }
- componentDidMount() {
- this._disposers.brush = reaction(
- () => this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion),
- brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0)
- );
- }
-
- componentWillUnmount() {
- Object.values(this._disposers).forEach(disposer => disposer?.());
- }
-
@undoBatch
deleteAnnotation = () => {
const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]);
@@ -91,15 +82,27 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
}
render() {
- return (<div className="htmlAnnotation" onPointerEnter={() => this.props.showInfo(this.props.anno)} onPointerLeave={() => this.props.showInfo(undefined)} onPointerDown={this.onPointerDown} ref={this._mainCont}
+ const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion);
+ return (<div className="htmlAnnotation" ref={this._mainCont}
+ onPointerEnter={action(() => {
+ Doc.BrushDoc(this.props.anno);
+ this.props.showInfo(this.props.anno);
+ })}
+ onPointerLeave={action(() => {
+ Doc.UnBrushDoc(this.props.anno);
+ this.props.showInfo(undefined);
+ })}
+ onPointerDown={this.onPointerDown}
style={{
left: NumCast(this.props.document.x),
top: NumCast(this.props.document.y),
width: NumCast(this.props.document._width),
height: NumCast(this.props.document._height),
- opacity: this._brushed ? 0.5 : undefined,
+ opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
pointerEvents: this.props.pointerEvents?.() as any,
- backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor),
+ outline: brushed === Doc.DocBrushStatus.linkHighlighted ? "solid 1px lightBlue" : undefined,
+ backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? "orange" :
+ StrCast(this.props.document.backgroundColor),
}} >
</div>);
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 608ba07ff..b0a5fc93b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -484,7 +484,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
CollectionView={undefined}
ScreenToLocalTransform={this.overlayTransform}
renderDepth={this.props.renderDepth + 1}
- />;
+ />
@computed get overlayTransparentAnnotations() { return this.renderAnnotations(this.transparentFilter, false); }
@computed get overlayOpaqueAnnotations() { return this.renderAnnotations(this.opaqueFilter, false); }
@computed get overlayClickableAnnotations() {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 3d36efbc4..2905e96d9 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -127,9 +127,11 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
static foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) {
let newarray: Doc[] = [];
var depth = 0;
+ let visited: Doc[] = [];
while (docs.length > 0) {
newarray = [];
- docs.filter(d => d).forEach(d => {
+ docs.filter(d => d && !visited.includes(d)).forEach(d => {
+ visited.push(d);
const fieldKey = Doc.LayoutFieldKey(d);
const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
const data = d[annos ? fieldKey + "-annotations" : fieldKey];
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 50e5fcbc4..9de8d0831 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1016,12 +1016,28 @@ export namespace Doc {
unbrushed = 0,
protoBrushed = 1,
selfBrushed = 2,
- highlighted = 3
+ highlighted = 3,
+ linkHighlighted = 4,
}
// don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
export function IsBrushedDegreeUnmemoized(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return DocBrushStatus.unbrushed;
- return brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+ const status = brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+ if (status === DocBrushStatus.unbrushed) {
+ const lastBrushed = Array.from(brushManager.BrushedDoc.keys()).lastElement();
+ if (lastBrushed) {
+ for (var link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) {
+ const a1 = Cast(link.anchor1, Doc, null);
+ const a2 = Cast(link.anchor2, Doc, null);
+ if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) ||
+ (Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc)) ||
+ (Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc))) {
+ return DocBrushStatus.linkHighlighted;
+ }
+ }
+ }
+ }
+ return status;
}
export function IsBrushedDegree(doc: Doc) {
return computedFn(function IsBrushDegree(doc: Doc) {
@@ -1030,14 +1046,18 @@ export namespace Doc {
}
export function BrushDoc(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
- brushManager.BrushedDoc.set(doc, true);
- brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
+ runInAction(() => {
+ brushManager.BrushedDoc.set(doc, true);
+ brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
+ });
return doc;
}
export function UnBrushDoc(doc: Doc) {
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
- brushManager.BrushedDoc.delete(doc);
- brushManager.BrushedDoc.delete(Doc.GetProto(doc));
+ runInAction(() => {
+ brushManager.BrushedDoc.delete(doc);
+ brushManager.BrushedDoc.delete(Doc.GetProto(doc));
+ });
return doc;
}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 83a2bc79b..cd85a7a65 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -271,10 +271,10 @@ export default class UploadManager extends ApiManager {
return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
const ext = extname(savedName).toLowerCase();
const { pngs, jpgs } = AcceptableMedia;
- const resizers = !origSuffix ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" }] : [
- { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: "_s" },
- { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: "_m" },
- { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: "_l" },
+ const resizers = !origSuffix ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium }] : [
+ { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Small },
+ { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium },
+ { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Large },
];
let isImage = false;
if (pngs.includes(ext)) {
@@ -290,8 +290,10 @@ export default class UploadManager extends ApiManager {
}
if (isImage) {
resizers.forEach(resizer => {
- const path = serverPathToFile(Directory.images, filename + resizer.suffix + ext);
- createReadStream(savedName).pipe(resizer.resizer).pipe(createWriteStream(path));
+ const path = serverPathToFile(Directory.images, InjectSize(filename, resizer.suffix) + ext);
+ createReadStream(savedName).on("error", e => console.log("Resizing Read error:" + e))
+ .pipe(resizer.resizer)
+ .pipe(createWriteStream(path).on("error", e => console.log("Resizing write error: " + e)));
});
}