diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index f038ba03f6..e489a150ec 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -246,6 +246,28 @@ export default class BrowserCell extends Component { menuItems.length && setContextMenu(pageX, pageY, menuItems); } + onMouseMove(event) { + this.props.setDataForPreview(); + + const { pageX, pageY } = event; + clearTimeout(this.hoverTimer); + + this.hoverTimer = setTimeout(() => { + this.props.setDataForPreview({ + value: this.copyableValue, + type: this.props.type, + pageX, + pageY, + }); + }, 400); + } + + onMouseOut() { + clearTimeout(this.hoverTimer); + delete this.hoverTimer; + this.props.setDataForPreview(); + } + getContextMenuOptions(constraints) { let { onEditSelectedRow, readonly } = this.props; const contextMenuOptions = []; @@ -459,6 +481,8 @@ export default class BrowserCell extends Component { } }}} onContextMenu={this.onContextMenu.bind(this)} + onMouseMove={this.onMouseMove.bind(this)} + onMouseOut={this.onMouseOut.bind(this)} > {this.state.content} diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index 5ae4e78ac3..729c4c14f5 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -19,7 +19,7 @@ export default class BrowserRow extends Component { } render() { - const { className, columns, currentCol, isUnique, obj, onPointerClick, onPointerCmdClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation, onEditSelectedRow, setContextMenu, onFilterChange, markRequiredFieldRow } = this.props; + const { className, columns, currentCol, isUnique, obj, onPointerClick, onPointerCmdClick, order, readOnlyFields, row, rowWidth, selection, selectRow, setCopyableValue, setCurrent, setEditing, setRelation, onEditSelectedRow, setContextMenu, setDataForPreview, onFilterChange, markRequiredFieldRow } = this.props; let attributes = obj.attributes; let requiredCols = []; Object.entries(columns).reduce((acc, cur) => { @@ -100,6 +100,7 @@ export default class BrowserRow extends Component { markRequiredFieldRow={markRequiredFieldRow} setCopyableValue={setCopyableValue} setContextMenu={setContextMenu} + setDataForPreview={setDataForPreview} onEditSelectedRow={onEditSelectedRow} /> ); })} diff --git a/src/components/DataPreview/DataPreview.react.js b/src/components/DataPreview/DataPreview.react.js new file mode 100644 index 0000000000..21f1ad18ad --- /dev/null +++ b/src/components/DataPreview/DataPreview.react.js @@ -0,0 +1,64 @@ +import React, { useEffect, useState } from "react"; +import PropTypes from "lib/PropTypes"; +import Popover from "components/Popover/Popover.react"; +import StringDataHandler from "./StringDataHandler"; +import PointerDataHandler from "./PointerDataHandler"; +import ObjectDataHandler from "./ObjectDataHandler"; + +const DATA_HANDLERS = { + String: StringDataHandler, + Pointer: PointerDataHandler, + Object: ObjectDataHandler, +}; + +function DataPreview({ data }) { + const [dataComponent, setDataComponent] = useState(); + + async function handleData(type, value) { + const dataHandler = DATA_HANDLERS[type]; + if (!dataHandler) { + return; + } + const dataComponent = await dataHandler(value); + setDataComponent(dataComponent); + } + + useEffect(() => { + dataComponent && setDataComponent(); + if (!data) { + return; + } + + handleData(data.type, data.value); + }, [JSON.stringify(data)]); + + if (!dataComponent) { + return null; + } + + return ( + + {dataComponent} + + ); +} + +DataPreview.propTypes = { + data: PropTypes.shape({ + value: PropTypes.any.isRequired, + type: PropTypes.string.isRequired, + pageX: PropTypes.number, + pageY: PropTypes.number, + }), +}; + +export default DataPreview; diff --git a/src/components/DataPreview/ObjectDataHandler.js b/src/components/DataPreview/ObjectDataHandler.js new file mode 100644 index 0000000000..4bfc971e14 --- /dev/null +++ b/src/components/DataPreview/ObjectDataHandler.js @@ -0,0 +1,4 @@ +export default async function (value) { + // TODO: show whole object in a readable way + // component might be useful here +} diff --git a/src/components/DataPreview/PointerDataHandler.js b/src/components/DataPreview/PointerDataHandler.js new file mode 100644 index 0000000000..ec75e1cd5f --- /dev/null +++ b/src/components/DataPreview/PointerDataHandler.js @@ -0,0 +1,3 @@ +export default async function (value) { + // TODO: fetch data from Pointer and present it in a nice way +} diff --git a/src/components/DataPreview/StringDataHandler.js b/src/components/DataPreview/StringDataHandler.js new file mode 100644 index 0000000000..0c09187908 --- /dev/null +++ b/src/components/DataPreview/StringDataHandler.js @@ -0,0 +1,40 @@ +import React from "react"; + +function isValidHttpUrl(string) { + let url; + try { + url = new URL(string); + } catch (_) { + return false; + } + + return url.protocol === "http:" || url.protocol === "https:"; +} + +async function loadImage(url) { + return new Promise((resolve) => { + const testImage = new Image(); + testImage.onerror = () => resolve(); + testImage.onload = function (event) { + const { width, height } = event?.currentTarget; + const style = {}; + if (width >= height) { + style.width = window.innerWidth / 3; + } else { + style.height = window.innerHeight / 3; + } + const imageComponent = ; + resolve(imageComponent); + }; + testImage.src = url; + }); +} + +export default async function (value) { + if (isValidHttpUrl(value)) { + return loadImage(value); + } + + // TODO: return paragraoh with value inside, set styles for element to fit the view + // return

{value}

+} diff --git a/src/components/Popover/Popover.react.js b/src/components/Popover/Popover.react.js index 10dfda9d36..1bb543af1c 100644 --- a/src/components/Popover/Popover.react.js +++ b/src/components/Popover/Popover.react.js @@ -18,6 +18,9 @@ export default class Popover extends React.Component { this._checkExternalClick = this._checkExternalClick.bind(this); this._popoverLayer = document.createElement('div'); + if (props.style) { + this._popoverLayer.style = props.style; + } } componentDidUpdate(prevState) { @@ -41,8 +44,17 @@ export default class Popover extends React.Component { this._popoverWrapper.appendChild(this._popoverLayer); if (this.props.position) { - this._popoverLayer.style.left = this.props.position.x + 'px'; - this._popoverLayer.style.top = this.props.position.y + 'px'; + // Fix position if Popover goes off the screen + this._popoverLayer.style.left = + Math.min( + this.props.position.x, + window.innerWidth - this._popoverLayer.clientWidth + ) + 'px'; + this._popoverLayer.style.top = + Math.min( + this.props.position.y, + window.innerHeight - this._popoverLayer.clientHeight + ) + 'px'; } if (this.props.modal) { this._popoverLayer.style.right = 0; diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index eb48eaef0f..ccad483ffc 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -154,6 +154,7 @@ export default class BrowserTable extends React.Component { setRelation={this.props.setRelation} setCopyableValue={this.props.setCopyableValue} setContextMenu={this.props.setContextMenu} + setDataForPreview={this.props.setDataForPreview} onEditSelectedRow={this.props.onEditSelectedRow} markRequiredFieldRow={this.props.markRequiredFieldRow} /> @@ -206,6 +207,7 @@ export default class BrowserTable extends React.Component { setRelation={this.props.setRelation} setCopyableValue={this.props.setCopyableValue} setContextMenu={this.props.setContextMenu} + setDataForPreview={this.props.setDataForPreview} onEditSelectedRow={this.props.onEditSelectedRow} markRequiredFieldRow={this.props.markRequiredFieldRow} /> @@ -264,6 +266,7 @@ export default class BrowserTable extends React.Component { setRelation={this.props.setRelation} setCopyableValue={this.props.setCopyableValue} setContextMenu={this.props.setContextMenu} + setDataForPreview={this.props.setDataForPreview} onEditSelectedRow={this.props.onEditSelectedRow} /> } diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 962ba7e46c..7a5db6ddd0 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -9,6 +9,7 @@ import copy from 'copy-to-clipboard'; import BrowserTable from 'dashboard/Data/Browser/BrowserTable.react'; import BrowserToolbar from 'dashboard/Data/Browser/BrowserToolbar.react'; import ContextMenu from 'components/ContextMenu/ContextMenu.react'; +import DataPreview from 'components/DataPreview/DataPreview.react'; import * as ColumnPreferences from 'lib/ColumnPreferences'; import React from 'react'; @@ -44,6 +45,7 @@ export default class DataBrowser extends React.Component { this.handleColumnsOrder = this.handleColumnsOrder.bind(this); this.setCopyableValue = this.setCopyableValue.bind(this); this.setContextMenu = this.setContextMenu.bind(this); + this.setDataForPreview = this.setDataForPreview.bind(this); this.saveOrderTimeout = null; } @@ -290,6 +292,10 @@ export default class DataBrowser extends React.Component { this.setState({ contextMenuX, contextMenuY, contextMenuItems }); } + setDataForPreview(dataForPreview) { + this.setState({ dataForPreview }); + } + handleColumnsOrder(order, shouldReload) { this.setState({ order: [...order] }, () => { this.updatePreferences(order, shouldReload); @@ -315,6 +321,7 @@ export default class DataBrowser extends React.Component { setCurrent={this.setCurrent} setCopyableValue={this.setCopyableValue} setContextMenu={this.setContextMenu} + setDataForPreview={this.setDataForPreview} onFilterChange={this.props.onFilterChange} {...other} /> } + ); }