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} />
}
+
);
}