mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-26 07:22:34 +00:00
table location editor (#8130)
Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
@@ -13,6 +13,7 @@ const POPUP_EDITOR_COLUMN_TYPES = [
|
||||
CellType.LONG_TEXT,
|
||||
CellType.LINK,
|
||||
CellType.TAGS,
|
||||
CellType.GEOLOCATION,
|
||||
];
|
||||
|
||||
const PREVIEW_EDITOR_COLUMN_TYPES = [
|
||||
|
@@ -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 (
|
||||
<ClickOutside onClickOutside={this.onClickOutside}>
|
||||
<div
|
||||
@@ -237,7 +265,7 @@ class PopupEditorContainer extends React.Component {
|
||||
onContextMenu={this.handleRightClick}
|
||||
ref={this.props.innerRef}
|
||||
>
|
||||
{this.createEditor()}
|
||||
{editor}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
);
|
||||
|
@@ -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 (<TagsEditor ref={ref} { ...props } />);
|
||||
}
|
||||
case CellType.GEOLOCATION: {
|
||||
return (<TableGeolocationEditor ref={ref} { ...props } />);
|
||||
}
|
||||
case CellType.LINK: {
|
||||
return null;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
@@ -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 (
|
||||
<div className="sf-table-geolocation-editor" ref={editorRef} style={editorStyle}>
|
||||
{!isFullScreen ? (
|
||||
<GeolocationEditor
|
||||
position={currentValue}
|
||||
onSubmit={onSubmit}
|
||||
onFullScreen={onFullScreen}
|
||||
onReadyToEraseLocation={onReadyToEraseLocation}
|
||||
/>
|
||||
) : (
|
||||
<Modal
|
||||
size='lg'
|
||||
isOpen={true}
|
||||
toggle={onFullScreen}
|
||||
zIndex={1052}
|
||||
>
|
||||
<GeolocationEditor
|
||||
position={currentValue}
|
||||
isFullScreen={isFullScreen}
|
||||
onSubmit={onSubmit}
|
||||
onFullScreen={onFullScreen}
|
||||
onReadyToEraseLocation={onReadyToEraseLocation}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default TableGeolocationEditor;
|
Reference in New Issue
Block a user