diff options
Diffstat (limited to 'src/client/views/nodes/WebBox.tsx')
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 605 |
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); |