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.tsx605
1 files changed, 200 insertions, 405 deletions
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 045af7ecd..1e158f484 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -4,7 +4,6 @@ import { htmlToText } from 'html-to-text';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import axios from 'axios';
import * as WebRequest from 'web-request';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivHeight, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
@@ -70,20 +69,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
private _scrollTimer: NodeJS.Timeout | undefined;
private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
- @observable private _webUrl = ''; // url of the page we want to display
- @observable private _hackHide = false;
+ @observable private _webUrl = ''; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't want the src parameter to also change as that would cause an unnecessary re-render.
+ @observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe. so thisforces the ifrmae to be rebuilt when allowScripts is toggled
@observable private _searching: boolean = false;
@observable private _showSidebar = false;
@observable private _webPageHasBeenRendered = false;
@observable private _marqueeing: number[] | undefined = undefined;
- @observable private _screenshotUrl: string | null = null; // URL to the screenshot image
- @observable private _fullHeight: number = 0; // Full height of the webpage screenshot
- @observable private _isLoadingScreenshot: boolean = false; // Loading state for the screenshot
+ get marqueeing() {
+ return this._marqueeing;
+ }
+ set marqueeing(val) {
+ val && this._marqueeref.current?.onInitiateSelection(val);
+ !val && this._marqueeref.current?.onTerminateSelection();
+ this._marqueeing = val;
+ }
@observable private _iframe: HTMLIFrameElement | null = null;
@observable private _savedAnnotations = new ObservableMap<number, (HTMLDivElement & { marqueeing?: boolean })[]>();
@observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight);
- @observable private _screenshotError: string | null = null; // Error message if screenshot fails
- @observable private _loadingFromCache: boolean = false;
@computed get _url() {
return this.webField?.toString() || '';
}
@@ -143,38 +145,31 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
updateIcon = async () => {
- if (!this._screenshotUrl) {
- // If we don't have a screenshot yet, capture one first
- await this.captureWebScreenshot();
- }
-
+ if (!this._iframe) return new Promise<void>(res => res());
const scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth();
-
+ let htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
+ if (!htmlString) {
+ htmlString = await fetch(ClientUtils.CorsProxy(this.webField!.href)).then(response => response.text());
+ }
this.layoutDoc.thumb = undefined;
this.Document.thumbLockout = true; // lock to prevent multiple thumb updates.
-
- try {
- // If we have a screenshot, use it directly for the thumbnail
- if (this._screenshotUrl) {
- return ClientUtils.convertDataUri(this._screenshotUrl, this.layoutDoc[Id] + '_icon_' + new Date().getTime(), true, this.layoutDoc[Id] + '_icon_').then(returnedfilename => {
+ return (CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop) as Promise<string>)
+ .then((dataUrl: string) => {
+ if (dataUrl.includes('<!DOCTYPE')) {
+ console.log('BAD DATA IN THUMB CREATION');
+ return;
+ }
+ return ClientUtils.convertDataUri(dataUrl, this.layoutDoc[Id] + '_icon_' + new Date().getTime(), true, this.layoutDoc[Id] + '_icon_').then(returnedfilename => {
this.Document.thumbLockout = false;
this.layoutDoc.thumb = new ImageField(returnedfilename);
this.layoutDoc.thumbScrollTop = scrollTop;
this.layoutDoc.thumbNativeWidth = nativeWidth;
this.layoutDoc.thumbNativeHeight = nativeHeight;
});
- } else {
- console.log('No screenshot available for thumbnail generation');
- this.Document.thumbLockout = false;
- return Promise.resolve();
- }
- } catch (error) {
- console.error('Error creating thumbnail:', error);
- this.Document.thumbLockout = false;
- return Promise.reject(error);
- }
+ })
+ .catch((error: object) => console.error('oops, something went wrong!', error));
};
componentDidMount() {
@@ -243,64 +238,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
},
{ fireImmediately: true }
);
-
- // Check if we have a cached screenshot URL in metadata
- if (this._url) {
- this._webUrl = this._url;
- const cachedScreenshotUrl = StrCast(this.dataDoc[this.fieldKey + '_screenshotUrl']);
- const cachedHeight = NumCast(this.dataDoc[this.fieldKey + '_screenshotHeight']);
-
- if (cachedScreenshotUrl && cachedHeight) {
- // Use cached screenshot
- this._loadingFromCache = true;
- this._isLoadingScreenshot = true;
-
- // Verify the cached screenshot exists by loading the image
- const img = new Image();
- img.onload = action(() => {
- this._screenshotUrl = cachedScreenshotUrl;
- this._fullHeight = cachedHeight;
- this._scrollHeight = cachedHeight;
- this._webPageHasBeenRendered = true;
- this._isLoadingScreenshot = false;
- this._loadingFromCache = false;
-
- // Apply dimensions and initial scroll
- if (this.layoutDoc._layout_autoHeight) {
- this.layoutDoc._nativeHeight = this._fullHeight;
- this._props.setHeight?.(this._fullHeight * (this._props.NativeDimScaling?.() || 1));
- }
-
- if (this._initialScroll !== undefined) {
- this.setScrollPos(this._initialScroll);
- }
-
- console.log(`Loaded cached screenshot: ${this._screenshotUrl}`);
- });
-
- img.onerror = action(() => {
- // If image fails to load, capture a new screenshot
- console.log('Cached screenshot not found, capturing new one');
- this._loadingFromCache = false;
- this.captureWebScreenshot();
- });
-
- img.src = cachedScreenshotUrl;
- } else {
- // No cached screenshot, capture a new one
- this.captureWebScreenshot();
- }
- }
}
componentWillUnmount() {
- // Clean up timers
- if (this._scrollTimer) {
- clearTimeout(this._scrollTimer);
- this._scrollTimer = undefined;
- }
-
- // Clean up reaction disposers
+ this._iframetimeout && clearTimeout(this._iframetimeout);
+ this._iframetimeout = undefined;
Object.values(this._disposers).forEach(disposer => disposer?.());
+ // this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
+ // this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
}
private _selectionText: string = '';
@@ -415,6 +359,59 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
savedAnnotationsCreator: () => ObservableMap<number, (HTMLDivElement & { marqueeing?: boolean })[]> = () => this._textAnnotationCreator?.() || this._savedAnnotations;
@action
+ iframeMove = (e: PointerEvent) => {
+ const theclick = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+ this._marqueeref.current?.onMove(theclick);
+ };
+ @action
+ iframeUp = (e: PointerEvent) => {
+ this._iframe?.contentDocument?.removeEventListener('pointermove', this.iframeMove);
+ this.marqueeing = undefined;
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
+ this._textAnnotationCreator = undefined;
+ this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
+ if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
+ const mainContBounds = ClientUtils.GetScreenTransform(this._mainCont.current!);
+ const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
+ const sel = this._iframe.contentWindow.getSelection();
+ if (sel) {
+ this._selectionText = sel.toString();
+ AnchorMenu.Instance.setSelectedText(sel.toString());
+ this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
+ AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale);
+ // Changing which document to add the annotation to (the currently selected WebBox)
+ GPTPopup.Instance.setSidebarFieldKey(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
+ GPTPopup.Instance.addDoc = this.sidebarAddDocument;
+ }
+ } else {
+ const theclick = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+ if (!this._marqueeref.current?.isEmpty) this._marqueeref.current?.onEnd(theclick[0], theclick[1]);
+ else {
+ if (!(e.target as HTMLElement)?.tagName?.includes('INPUT')) this.finishMarquee(theclick[0], theclick[1]);
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+ this.marqueeing = undefined;
+ }
+
+ ContextMenu.Instance.closeMenu();
+ ContextMenu.Instance.setIgnoreEvents(false);
+ if (e?.button === 2 || e?.altKey) {
+ e?.preventDefault();
+ e?.stopPropagation();
+ setTimeout(() => {
+ // if menu comes up right away, the down event can still be active causing a menu item to be selected
+ this.specificContextMenu();
+ this.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
+ });
+ }
+ }
+ };
+ @action
webClipDown = (e: React.PointerEvent) => {
e.stopPropagation();
const sel = window.getSelection();
@@ -508,6 +505,98 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._scrollHeight = this._iframe?.contentDocument?.body?.scrollHeight ?? 0;
this.addWebStyleSheetRule(this.addWebStyleSheet(this._iframe?.contentDocument), '::selection', { color: 'white', background: 'orange' }, '');
+ // Add error handler to suppress font CORS errors
+ if (this._iframe?.contentWindow) {
+ try {
+ // Track if any resource errors occurred
+ let hasResourceErrors = false;
+
+ // Override the console.error to filter out font CORS errors
+ const win = this._iframe.contentWindow as Window & { console: Console };
+ const originalConsoleError = win.console.error;
+ win.console.error = (...args: unknown[]) => {
+ const errorMsg = args.map(arg => String(arg)).join(' ');
+ if (errorMsg.includes('Access to font') && errorMsg.includes('has been blocked by CORS policy')) {
+ // Mark that we have font errors
+ hasResourceErrors = true;
+ // Ignore font CORS errors
+ return;
+ }
+ // Also catch other resource loading errors
+ if (errorMsg.includes('ERR_FAILED') || errorMsg.includes('ERR_BLOCKED_BY_CLIENT')) {
+ hasResourceErrors = true;
+ }
+ originalConsoleError.apply(win.console, args);
+ };
+
+ // Listen for resource loading errors
+ this._iframe.contentWindow.addEventListener(
+ 'error',
+ (e: Event) => {
+ const target = e.target as HTMLElement;
+ if (target instanceof HTMLElement) {
+ // If it's a resource that failed to load
+ if (target.tagName === 'LINK' || target.tagName === 'IMG' || target.tagName === 'SCRIPT') {
+ hasResourceErrors = true;
+ // Apply error class after a short delay to allow initial content to load
+ setTimeout(() => {
+ if (this._iframe && hasResourceErrors) {
+ this._iframe.classList.add('loading-error');
+ }
+ }, 1000);
+ }
+ }
+ },
+ true
+ );
+
+ // Add fallback CSS for fonts that fail to load
+ const style = this._iframe.contentDocument?.createElement('style');
+ if (style) {
+ style.textContent = `
+ @font-face {
+ font-family: 'CORS-fallback-serif';
+ src: local('Times New Roman'), local('Georgia'), serif;
+ }
+ @font-face {
+ font-family: 'CORS-fallback-sans';
+ src: local('Arial'), local('Helvetica'), sans-serif;
+ }
+ /* Fallback for all fonts that fail to load */
+ @font-face {
+ font-display: swap !important;
+ }
+
+ /* Add a script to find and fix elements with failed fonts */
+ @font-face {
+ font-family: '__failed_font__';
+ src: local('Arial');
+ unicode-range: U+0000;
+ }
+ `;
+ this._iframe.contentDocument?.head.appendChild(style);
+
+ // Add a script to detect and fix font loading issues
+ const script = this._iframe.contentDocument?.createElement('script');
+ if (script) {
+ script.textContent = `
+ // Fix font loading issues with fallbacks
+ setTimeout(function() {
+ document.querySelectorAll('*').forEach(function(el) {
+ if (window.getComputedStyle(el).fontFamily.includes('__failed_font__')) {
+ el.classList.add('font-error-hidden');
+ }
+ });
+ }, 1000);
+ `;
+ this._iframe.contentDocument?.head.appendChild(script);
+ }
+ }
+ } catch (e) {
+ console.log('Error setting up font error handling:', e);
+ }
+ }
+
let href: Opt<string>;
try {
href = iframe?.contentWindow?.location.href;
@@ -658,23 +747,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.dataDoc[this.fieldKey + '_history'] = new List<string>([...history, this._url]);
this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!));
this._scrollHeight = 0;
-
- // Reset screenshot state for new URL
- this._screenshotUrl = null;
- this._fullHeight = 0;
- this._isLoadingScreenshot = false;
-
if (this._webUrl === this._url) {
this._webUrl = curUrl;
setTimeout(
action(() => {
this._webUrl = this._url;
- this.captureWebScreenshot(); // Capture screenshot for new URL
})
);
} else {
this._webUrl = this._url;
- this.captureWebScreenshot(); // Capture screenshot for new URL
}
return true;
}
@@ -694,18 +775,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
else this.dataDoc[this.fieldKey + '_future'] = new List<string>([...future, this._url]);
this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!));
this._scrollHeight = 0;
-
- // Reset screenshot state for new URL
- this._screenshotUrl = null;
- this._fullHeight = 0;
- this._isLoadingScreenshot = false;
-
if (this._webUrl === this._url) {
this._webUrl = curUrl;
setTimeout(action(() => (this._webUrl = this._url)));
} else {
this._webUrl = this._url;
- this.captureWebScreenshot(); // Capture screenshot for new URL
}
return true;
}
@@ -724,11 +798,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.layoutDoc.thumbNativeWidth = undefined;
this.layoutDoc.thumbNativeHeight = undefined;
}
-
+ }
+ if (!preview) {
if (!dontUpdateIframe) {
this._webUrl = this._url;
- // Capture screenshot when URL changes
- this.captureWebScreenshot();
}
}
} catch {
@@ -737,85 +810,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return true;
};
- @action
- captureWebScreenshot = async () => {
- if (!this._url || this._loadingFromCache) return;
-
- try {
- this._isLoadingScreenshot = true;
- this._screenshotError = null;
-
- console.log(`Capturing screenshot for URL: ${this._url}`);
-
- try {
- const response = await axios.post('/captureWebScreenshot', {
- url: this._url,
- width: NumCast(this.Document.nativeWidth, 1200),
- height: NumCast(this.Document.nativeHeight, 800),
- fullPage: true, // Request a full page screenshot
- });
-
- runInAction(() => {
- this._screenshotUrl = response.data.screenshotUrl;
- this._fullHeight = response.data.fullHeight;
- this._scrollHeight = response.data.fullHeight;
- this._webPageHasBeenRendered = true;
- this._isLoadingScreenshot = false;
-
- // Store screenshot URL and height in document metadata
- this.dataDoc[this.fieldKey + '_screenshotUrl'] = response.data.screenshotUrl;
- this.dataDoc[this.fieldKey + '_screenshotHeight'] = response.data.fullHeight;
-
- // Update native dimensions to match the screenshot
- if (!this.dataDoc[this.fieldKey + '_nativeWidth']) {
- this.dataDoc[this.fieldKey + '_nativeWidth'] = 1200; // Default width
- }
-
- if (!this.dataDoc[this.fieldKey + '_nativeHeight']) {
- this.dataDoc[this.fieldKey + '_nativeHeight'] = this._fullHeight;
- }
-
- // Set document height if needed
- if (this.layoutDoc._layout_autoHeight) {
- this.layoutDoc._nativeHeight = this._fullHeight;
- this._props.setHeight?.(this._fullHeight * (this._props.NativeDimScaling?.() || 1));
- }
-
- // Apply initial scroll if needed
- if (this._initialScroll !== undefined) {
- this.setScrollPos(this._initialScroll);
- }
-
- console.log(`Screenshot captured successfully: ${this._screenshotUrl} with height: ${this._fullHeight}px`);
- });
- } catch (error: any) {
- // Handle error from the API
- console.error('Error capturing screenshot:', error);
- let errorMessage = 'Failed to capture webpage screenshot';
-
- // Try to extract detailed error message from response
- if (error.response && error.response.data && error.response.data.error) {
- errorMessage = error.response.data.error;
- } else if (error.message) {
- errorMessage = error.message;
- }
-
- runInAction(() => {
- this._screenshotError = errorMessage;
- this._isLoadingScreenshot = false;
- });
- }
- } catch (error: any) {
- // Handle unexpected errors
- runInAction(() => {
- console.error('Unexpected error in captureWebScreenshot:', error);
- this._screenshotError = 'An unexpected error occurred';
- this._isLoadingScreenshot = false;
- });
- }
- };
-
- @action
onWebUrlDrop = (e: React.DragEvent) => {
const { dataTransfer } = e;
const html = dataTransfer.getData('text/html');
@@ -830,28 +824,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
setData = (data: FieldType | Promise<RefField | undefined>) => {
if (!(typeof data === 'string') && !(data instanceof WebField)) return false;
if (Field.toString(data) === this._url) return false;
-
- // Reset state for new URL
this._scrollHeight = 0;
- this._screenshotUrl = null;
- this._fullHeight = 0;
- this._isLoadingScreenshot = false;
-
- // Clear stored screenshot metadata for the previous URL
- this.dataDoc[this.fieldKey + '_screenshotUrl'] = undefined;
- this.dataDoc[this.fieldKey + '_screenshotHeight'] = undefined;
-
const oldUrl = this._url;
const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
const weburl = new WebField(Field.toString(data));
this.dataDoc[this.fieldKey + '_future'] = new List<string>([]);
this.dataDoc[this.fieldKey + '_history'] = new List<string>([...(history || []), oldUrl]);
this.dataDoc[this.fieldKey] = weburl;
-
- // Capture screenshot for the new URL
- this._webUrl = weburl.toString();
- this.captureWebScreenshot();
-
return true;
};
onWebUrlValueKeyDown = (e: React.KeyboardEvent) => {
@@ -868,14 +847,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors',
event: () => {
this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors'];
- // Re-capture screenshot with the new setting
- this.captureWebScreenshot();
},
icon: 'snowflake',
});
-
- // Remove the "Allow Scripts" option since it's not relevant for screenshots
-
+ funcs.push({
+ description: (this.dataDoc[this.fieldKey + '_allowScripts'] ? 'Prevent' : 'Allow') + ' Scripts',
+ event: () => {
+ this.dataDoc[this.fieldKey + '_allowScripts'] = !this.dataDoc[this.fieldKey + '_allowScripts'];
+ if (this._iframe) {
+ runInAction(() => {
+ this._hackHide = true;
+ });
+ setTimeout(
+ action(() => {
+ this._hackHide = false;
+ })
+ );
+ }
+ },
+ icon: 'snowflake',
+ });
funcs.push({
description: (!this.layoutDoc.layout_reflowHorizontal ? 'Force' : 'Prevent') + ' Reflow',
event: () => {
@@ -887,21 +878,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
},
icon: 'snowflake',
});
-
- // Add a refresh option to re-capture the screenshot
- funcs.push({
- description: 'Refresh Screenshot',
- event: () => this.captureWebScreenshot(),
- icon: 'sync-alt',
- });
-
- !Doc.noviceMode &&
- funcs.push({
- description: 'Update Icon',
- event: () => this.updateIcon(),
- icon: 'portrait',
- });
-
+ !Doc.noviceMode && funcs.push({ description: 'Update Icon', event: () => this.updateIcon(), icon: 'portrait' });
cm.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
}
};
@@ -913,7 +890,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
*/
@action
onMarqueeDown = (e: React.PointerEvent) => {
- const sel = window.document.getSelection();
+ const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
this._textAnnotationCreator = undefined;
if (sel?.empty)
sel.empty(); // Chrome
@@ -948,7 +925,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@computed get urlContent() {
if (this.ScreenToLocalBoxXf().Scale > 25) return <div />;
-
setTimeout(
action(() => {
if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -957,10 +933,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._webPageHasBeenRendered = true;
})
);
-
const field = this.dataDoc[this._props.fieldKey];
-
- // Handle HTML field (text content)
if (field instanceof HtmlField) {
return (
<span
@@ -977,8 +950,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
/>
);
}
-
- // Handle WebField (screenshot of webpage)
if (field instanceof WebField) {
const url = this.layoutDoc[this.fieldKey + '_useCors'] ? '/corsproxy/' + this._webUrl : this._webUrl;
const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing');
@@ -1198,7 +1169,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
TraceMobx();
// const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as Property.PointerEvents | undefined);
- const scale = this._props.NativeDimScaling?.() || 1;
+ // const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
return (
<div
className="webBox-outerContent"
@@ -1207,16 +1178,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
height: '100%', //`${100 / scale}%`,
pointerEvents,
}}
+ // when active, block wheel events from propagating since they're handled by the iframe
onWheel={this.onZoomWheel}
onScroll={() => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
onPointerDown={this.onMarqueeDown}>
- <div
- className="webBox-innerContent"
- style={{
- width: '100%',
- pointerEvents,
- backgroundColor: '#f5f5f5',
- }}>
+ <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this._props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
{this.content}
<div style={{ display: SnappingManager.CanEmbed ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div>
{this.renderOpaqueAnnotations}
@@ -1258,13 +1224,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<FontAwesomeIcon icon={this._searching ? 'times' : 'search'} size="lg" />
</div>
</button>
-
- {/* Refresh button */}
- <button type="button" className="webBox-overlayButton webBox-refreshButton" title="Refresh webpage" onClick={() => this.captureWebScreenshot()}>
- <div className="webBox-overlayButton-iconCont" onPointerDown={e => e.stopPropagation()}>
- <FontAwesomeIcon icon="sync" size="lg" />
- </div>
- </button>
</div>
);
}
@@ -1293,25 +1252,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
render() {
TraceMobx();
- const containerWidth = NumCast(this.layoutDoc._width) || this._props.PanelWidth();
+ const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as Property.PointerEvents);
- const scale = this._props.NativeDimScaling?.() || 1;
-
- // Force the component to be square
- this.layoutDoc._height = containerWidth;
- this.layoutDoc._width = containerWidth;
- this.layoutDoc._forceActive = true;
-
+ const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
return (
<div
className="webBox"
ref={this._mainCont}
style={{
- pointerEvents: this.pointerEvents(),
+ pointerEvents: this.pointerEvents(), //
position: SnappingManager.IsDragging ? 'absolute' : undefined,
- width: `${containerWidth}px`,
- height: `${containerWidth}px`,
- aspectRatio: '1 / 1', // Explicitly enforce square aspect ratio
}}>
<div className="webBox-background" style={{ backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string }} />
<div
@@ -1376,15 +1326,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
</div>
);
}
-
- get marqueeing() {
- return this._marqueeing;
- }
- set marqueeing(val) {
- val && this._marqueeref.current?.onInitiateSelection(val);
- !val && this._marqueeref.current?.onTerminateSelection();
- this._marqueeing = val;
- }
}
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function urlHash(url: string) {
@@ -1395,149 +1336,3 @@ Docs.Prototypes.TemplateMap.set(DocumentType.WEB, {
layout: { view: WebBox, dataField: 'data' },
options: { acl: '', _height: 300, _layout_fitWidth: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
});
-
-// Add CSS styles for screenshot mode
-const webBoxStyles = `
-.webBox-screenshot-container {
- width: 100%;
- position: relative;
- overflow: visible;
- display: flex;
- align-items: flex-start;
- justify-content: center;
- background-color: #f5f5f5;
-}
-
-.webBox-screenshot {
- width: 100%;
- pointer-events: none;
- display: block;
- user-select: none;
- object-fit: contain;
- transition: opacity 0.3s ease;
-}
-
-.webBox-loading {
- padding: 20px;
- text-align: center;
- color: #666;
- background-color: #f5f5f5;
- border-radius: 4px;
- min-height: 200px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
-}
-
-.webBox-loading-message {
- font-size: 16px;
- margin-bottom: 15px;
- color: #555;
-}
-
-.webBox-loading-spinner {
- margin-top: 10px;
- color: #1976d2;
-}
-
-.webBox-error {
- padding: 20px;
- color: #d32f2f;
- text-align: center;
- background-color: #ffebee;
- border-radius: 4px;
- min-height: 200px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 15px;
-}
-
-.webBox-error-icon {
- color: #d32f2f;
- margin-bottom: 10px;
-}
-
-.webBox-error-message {
- color: #d32f2f;
- font-size: 14px;
- max-width: 80%;
- line-height: 1.5;
-}
-
-.webBox-error-actions {
- margin-top: 10px;
-}
-
-.webBox-retry-button {
- background-color: #f44336;
- color: white;
- border: none;
- padding: 8px 16px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- transition: background-color 0.3s;
-}
-
-.webBox-retry-button:hover {
- background-color: #d32f2f;
-}
-
-.webBox-placeholder {
- padding: 20px;
- text-align: center;
- color: #757575;
- background-color: #fafafa;
- border-radius: 4px;
- min-height: 200px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.webBox-refreshButton {
- margin-right: 5px;
-}
-
-.webBox-innerContent {
- position: relative;
- width: 100%;
- background-color: #f5f5f5;
- overflow: visible;
-}
-
-.webBox-outerContent {
- overflow: auto;
- width: 100%;
- background-color: #f5f5f5;
- position: relative;
-}
-
-.webBox-container {
- position: relative;
- display: flex;
- flex-direction: column;
- height: 100%;
- background-color: white;
- border-radius: 4px;
- overflow: hidden;
-}
-
-.webBox {
- position: relative;
- height: 100%;
- width: 100%;
- overflow: hidden;
- background-color: white;
- border-radius: 4px;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
-}
-`;
-
-// Add the styles to the document
-const styleEl = document.createElement('style');
-styleEl.textContent = webBoxStyles;
-document.head.appendChild(styleEl);