diff --git a/frontend/src/metadata/components/cell-editors/editor-container/index.js b/frontend/src/metadata/components/cell-editors/editor-container/index.js
index 6f06f6307b..2a99d703bf 100644
--- a/frontend/src/metadata/components/cell-editors/editor-container/index.js
+++ b/frontend/src/metadata/components/cell-editors/editor-container/index.js
@@ -13,6 +13,7 @@ const POPUP_EDITOR_COLUMN_TYPES = [
CellType.LONG_TEXT,
CellType.LINK,
CellType.TAGS,
+ CellType.GEOLOCATION,
];
const PREVIEW_EDITOR_COLUMN_TYPES = [
diff --git a/frontend/src/metadata/components/cell-editors/editor-container/popup-editor-container.js b/frontend/src/metadata/components/cell-editors/editor-container/popup-editor-container.js
index 7d9a6ced24..f7e097f0e0 100644
--- a/frontend/src/metadata/components/cell-editors/editor-container/popup-editor-container.js
+++ b/frontend/src/metadata/components/cell-editors/editor-container/popup-editor-container.js
@@ -4,7 +4,7 @@ import classnames from 'classnames';
import ClickOutside from '../../../../components/click-outside';
import Editor from '../editor';
import { Utils } from '../../../../utils/utils';
-import { isCellValueChanged, getCellValueByColumn, getColumnOptionNameById, getColumnOptionNamesByIds } from '../../../utils/cell';
+import { isCellValueChanged, getCellValueByColumn, getColumnOptionNameById, getColumnOptionNamesByIds, getFileNameFromRecord } from '../../../utils/cell';
import { canEditCell, getColumnOriginName } from '../../../utils/column';
import { CellType, metadataZIndexes, EVENT_BUS_TYPE, PRIVATE_COLUMN_KEYS } from '../../../constants';
@@ -65,6 +65,14 @@ class PopupEditorContainer extends React.Component {
createEditor = () => {
const { column, record, height, onPressTab, editorPosition, columns, modifyColumnData, onSelectTag, onDeselectTag } = this.props;
const readOnly = !canEditCell(column, record, true) || NOT_SUPPORT_EDITOR_COLUMN_TYPES.includes(column.type);
+
+ if (column.type === CellType.GEOLOCATION) {
+ const fileName = getFileNameFromRecord(record);
+ if (!Utils.imageCheck(fileName)) {
+ return null;
+ }
+ }
+
const value = this.getInitialValue(readOnly);
let editorProps = {
@@ -146,10 +154,12 @@ class PopupEditorContainer extends React.Component {
const columnName = getColumnOriginName(column);
const { key: columnKey } = column;
let oldValue = originalOldCellValue;
- if (this.getEditor().getOldValue) {
+
+ if (this.getEditor() && this.getEditor().getOldValue) {
const original = this.getEditor().getOldValue();
oldValue = original[Object.keys(original)[0]];
}
+
const oldRowData = { [columnName]: oldValue };
const originalOldRowData = { [columnKey]: originalOldCellValue }; // { [column.key]: cellValue }
return { oldRowData, originalOldRowData };
@@ -161,6 +171,16 @@ class PopupEditorContainer extends React.Component {
if (!record._id) return;
const { key: columnKey, type: columnType } = column;
if (columnType === CellType.TAGS) return;
+ if (columnType === CellType.GEOLOCATION) {
+ // For non-image files, there might be no editor, so check before calling onClose
+ if (this.getEditor() && this.getEditor().onClose) {
+ this.getEditor().onClose();
+ }
+ return;
+ }
+
+ if (!this.getEditor()) return;
+
const newValue = this.getEditor().getValue();
let updated = columnType === CellType.DATE ? { [columnKey]: newValue } : newValue;
if (columnType === CellType.SINGLE_SELECT) {
@@ -203,6 +223,8 @@ class PopupEditorContainer extends React.Component {
};
isNewValueValid = (value) => {
+ if (!this.getEditor()) return true;
+
if (Utils.isFunction(this.getEditor().validate)) {
const isValid = this.getEditor().validate(value);
this.setState({ isInvalid: !isValid });
@@ -229,6 +251,12 @@ class PopupEditorContainer extends React.Component {
};
render() {
+ const editor = this.createEditor();
+
+ if (!editor) {
+ return null;
+ }
+
return (
- {this.createEditor()}
+ {editor}
);
diff --git a/frontend/src/metadata/components/cell-editors/editor.js b/frontend/src/metadata/components/cell-editors/editor.js
index 2732a89d03..1dab6a52f0 100644
--- a/frontend/src/metadata/components/cell-editors/editor.js
+++ b/frontend/src/metadata/components/cell-editors/editor.js
@@ -9,6 +9,7 @@ import MultipleSelectEditor from './multiple-select-editor';
import CollaboratorEditor from './collaborator-editor';
import LongTextEditor from './long-text-editor';
import TagsEditor from './tags-editor';
+import TableGeolocationEditor from './geolocation-editor/table-geolocation-editor';
import { lang } from '../../../utils/constants';
import { CellType } from '../../constants';
@@ -43,6 +44,9 @@ const Editor = React.forwardRef((props, ref) => {
case CellType.TAGS: {
return ();
}
+ case CellType.GEOLOCATION: {
+ return ();
+ }
case CellType.LINK: {
return null;
}
diff --git a/frontend/src/metadata/components/cell-editors/geolocation-editor/table-geolocation-editor.css b/frontend/src/metadata/components/cell-editors/geolocation-editor/table-geolocation-editor.css
new file mode 100644
index 0000000000..716171da29
--- /dev/null
+++ b/frontend/src/metadata/components/cell-editors/geolocation-editor/table-geolocation-editor.css
@@ -0,0 +1,10 @@
+.sf-table-geolocation-editor {
+ background: white;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.sf-table-geolocation-editor .text-muted {
+ color: var(--bs-body-secondary-color) !important;
+}
diff --git a/frontend/src/metadata/components/cell-editors/geolocation-editor/table-geolocation-editor.js b/frontend/src/metadata/components/cell-editors/geolocation-editor/table-geolocation-editor.js
new file mode 100644
index 0000000000..702df2d3d3
--- /dev/null
+++ b/frontend/src/metadata/components/cell-editors/geolocation-editor/table-geolocation-editor.js
@@ -0,0 +1,171 @@
+import React, { useState, useCallback, forwardRef, useImperativeHandle, useEffect, useRef } from 'react';
+import { Modal } from 'reactstrap';
+import GeolocationEditor from './index';
+import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../../../constants';
+import { getRecordIdFromRecord } from '../../../utils/cell';
+import toaster from '../../../../components/toast';
+import { gettext } from '../../../../utils/constants';
+
+import './table-geolocation-editor.css';
+
+const TableGeolocationEditor = forwardRef(({ value, onCommit, onClose, record, column, columns, ...props }, ref) => {
+ const [isFullScreen, setFullScreen] = useState(false);
+ const [currentValue, setCurrentValue] = useState(value);
+ const [isReadyToEraseLocation, setReadyToEraseLocation] = useState(false);
+ const [editorStyle, setEditorStyle] = useState({});
+ const editorRef = useRef(null);
+
+ useImperativeHandle(ref, () => ({
+ onClose: () => closeEditor()
+ }));
+
+ // Calculate viewport-aware positioning
+ useEffect(() => {
+ if (!isFullScreen && editorRef.current) {
+ const editorElement = editorRef.current;
+ const parent = editorElement.parentElement;
+
+ if (parent) {
+ const parentRect = parent.getBoundingClientRect();
+ const viewportHeight = window.innerHeight;
+ const viewportWidth = window.innerWidth;
+
+ const editorWidth = 500;
+ const editorHeight = 434;
+
+ const cellWidth = parentRect.width;
+ const cellHeight = parentRect.height;
+
+ let adjustedStyle = {
+ position: 'absolute',
+ zIndex: '1050',
+ width: `${editorWidth}px`,
+ height: `${editorHeight}px`
+ };
+
+ let left = (cellWidth - editorWidth) / 2;
+ let top = cellHeight + 5;
+
+ const rightEdge = parentRect.left + left + editorWidth;
+ if (rightEdge > viewportWidth) {
+ left = viewportWidth - parentRect.left - editorWidth - 10;
+ }
+ if (parentRect.left + left < 0) {
+ left = -parentRect.left + 10;
+ }
+
+ const bottomEdge = parentRect.top + top + editorHeight;
+ if (bottomEdge > viewportHeight) {
+ top = -editorHeight - 5;
+
+ if (parentRect.top + top < 0) {
+ top = -parentRect.top + 10;
+ }
+ }
+
+ adjustedStyle.left = `${left}px`;
+ adjustedStyle.top = `${top}px`;
+
+ setEditorStyle(adjustedStyle);
+ }
+ }
+ }, [isFullScreen]);
+
+ const closeEditor = useCallback(() => {
+ if (isReadyToEraseLocation) {
+ const repoID = window.sfMetadataContext.getSetting('repoID');
+ const recordId = getRecordIdFromRecord(record);
+
+ window.sfMetadataContext.modifyRecord(repoID, recordId, {
+ [PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED]: null
+ }).then(() => {
+ return window.sfMetadataContext.modifyRecord(repoID, recordId, {
+ [PRIVATE_COLUMN_KEY.LOCATION]: null
+ });
+ }).then(() => {
+ setCurrentValue(null);
+ setReadyToEraseLocation(false);
+ if (window.sfMetadataContext?.eventBus) {
+ const updates = {
+ [PRIVATE_COLUMN_KEY.LOCATION]: null,
+ [PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED]: null
+ };
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, { recordId }, updates);
+ }
+ }).catch((error) => {
+ toaster.danger(gettext('Failed to modify records'));
+ });
+ }
+ onClose && onClose();
+ }, [isReadyToEraseLocation, onClose, record]);
+
+ const onFullScreen = useCallback(() => {
+ setFullScreen(!isFullScreen);
+ }, [isFullScreen]);
+
+ const onSubmit = useCallback((locationData) => {
+ const { position, location_translated } = locationData;
+
+ // Update location data using direct API calls
+ const repoID = window.sfMetadataContext.getSetting('repoID');
+ const recordId = getRecordIdFromRecord(record);
+
+ // First update location_translated, then location
+ window.sfMetadataContext.modifyRecord(repoID, recordId, {
+ [PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED]: location_translated
+ }).then(() => {
+ return window.sfMetadataContext.modifyRecord(repoID, recordId, {
+ [PRIVATE_COLUMN_KEY.LOCATION]: position
+ });
+ }).then(() => {
+ setCurrentValue(position);
+ setFullScreen(false);
+ onClose && onClose();
+
+ // Dispatch local update event for real-time UI updates
+ if (window.sfMetadataContext?.eventBus) {
+ const updates = {
+ [PRIVATE_COLUMN_KEY.LOCATION]: position,
+ [PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED]: location_translated
+ };
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, { recordId }, updates);
+ }
+ }).catch((error) => {
+ toaster.danger(gettext('Failed to modify records'));
+ });
+ }, [record, onClose]);
+
+ const onReadyToEraseLocation = useCallback(() => {
+ setReadyToEraseLocation(true);
+ }, []);
+
+ return (
+
+ {!isFullScreen ? (
+
+ ) : (
+
+
+
+ )}
+
+ );
+});
+
+export default TableGeolocationEditor;