1
0
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:
Aries
2025-08-16 11:39:43 +08:00
committed by GitHub
parent b7e0f8d042
commit b5bcc978a5
5 changed files with 217 additions and 3 deletions

View File

@@ -13,6 +13,7 @@ const POPUP_EDITOR_COLUMN_TYPES = [
CellType.LONG_TEXT,
CellType.LINK,
CellType.TAGS,
CellType.GEOLOCATION,
];
const PREVIEW_EDITOR_COLUMN_TYPES = [

View File

@@ -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>
);

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;