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;