diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 0beb5c17b42..b53a19d1e2a 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -17,7 +17,7 @@
"@seafile/sdoc-editor": "1.0.8",
"@seafile/seafile-calendar": "0.0.12",
"@seafile/seafile-editor": "1.0.99",
- "@seafile/sf-metadata-ui-component": "0.0.9",
+ "@seafile/sf-metadata-ui-component": "0.0.10",
"@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/react-codemirror": "^4.19.4",
"chart.js": "2.9.4",
@@ -4953,9 +4953,9 @@
}
},
"node_modules/@seafile/sf-metadata-ui-component": {
- "version": "0.0.9",
- "resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.9.tgz",
- "integrity": "sha512-J0D3DK1TI16QPlhAeBp64ilcKO7pCX9w03Q94D1Ni7phLquqZwCD3PFFyRgv6oUkWtGojTL++SiLVTTXubI68g==",
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.10.tgz",
+ "integrity": "sha512-WP1SH6NbP4tH3ZQ1dgzFKPpYfFzhDD/uzPktxJFpBX66xnTWlBirqYl5IHkyJ5nx6vuXG9CcRc9LhNyl+r3jeg==",
"dependencies": {
"@seafile/seafile-calendar": "0.0.24",
"classnames": "2.3.2",
@@ -32160,9 +32160,9 @@
}
},
"@seafile/sf-metadata-ui-component": {
- "version": "0.0.9",
- "resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.9.tgz",
- "integrity": "sha512-J0D3DK1TI16QPlhAeBp64ilcKO7pCX9w03Q94D1Ni7phLquqZwCD3PFFyRgv6oUkWtGojTL++SiLVTTXubI68g==",
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.10.tgz",
+ "integrity": "sha512-WP1SH6NbP4tH3ZQ1dgzFKPpYfFzhDD/uzPktxJFpBX66xnTWlBirqYl5IHkyJ5nx6vuXG9CcRc9LhNyl+r3jeg==",
"requires": {
"@seafile/seafile-calendar": "0.0.24",
"classnames": "2.3.2",
diff --git a/frontend/package.json b/frontend/package.json
index e60c4aa086f..59674dea924 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,7 +12,7 @@
"@seafile/sdoc-editor": "1.0.8",
"@seafile/seafile-calendar": "0.0.12",
"@seafile/seafile-editor": "1.0.99",
- "@seafile/sf-metadata-ui-component": "0.0.9",
+ "@seafile/sf-metadata-ui-component": "0.0.10",
"@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/react-codemirror": "^4.19.4",
"chart.js": "2.9.4",
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/editor-container.js b/frontend/src/metadata/metadata-view/components/cell-editor/editor-container.js
new file mode 100644
index 00000000000..9434ab4a3ac
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/editor-container.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Editor from './editor';
+
+const EditorContainer = (props) => {
+ if (!props.column) return null;
+ return ();
+};
+
+
+EditorContainer.propTypes = {
+ table: PropTypes.object,
+ columns: PropTypes.array,
+ isGroupView: PropTypes.bool,
+ scrollTop: PropTypes.number,
+ scrollLeft: PropTypes.number,
+ firstEditorKeyDown: PropTypes.object,
+ openEditorMode: PropTypes.string,
+ portalTarget: PropTypes.any,
+ editorPosition: PropTypes.object,
+ record: PropTypes.object,
+ column: PropTypes.object,
+ width: PropTypes.number.isRequired,
+ height: PropTypes.number.isRequired,
+ left: PropTypes.number.isRequired,
+ top: PropTypes.number.isRequired,
+ onCommit: PropTypes.func,
+ onCommitCancel: PropTypes.func,
+};
+
+export default EditorContainer;
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/editor-portal.jsx b/frontend/src/metadata/metadata-view/components/cell-editor/editor-portal.jsx
new file mode 100644
index 00000000000..5d4cb260b2c
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/editor-portal.jsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
+
+class EditorPortal extends React.Component {
+ static propTypes = {
+ children: PropTypes.node.isRequired,
+ target: PropTypes.instanceOf(Element).isRequired
+ };
+
+ // Keep track of when the modal element is added to the DOM
+ state = {
+ isMounted: false
+ };
+
+ el = document.createElement('div');
+
+ componentDidMount() {
+ this.props.target.appendChild(this.el);
+ // eslint-disable-next-line react/no-did-mount-set-state
+ this.setState({ isMounted: true });
+ }
+
+ componentWillUnmount() {
+ this.props.target.removeChild(this.el);
+ }
+
+ render() {
+ // Don't render the portal until the component has mounted,
+ // So the portal can safely access the DOM.
+ if (!this.state.isMounted) {
+ return null;
+ }
+
+ return ReactDOM.createPortal(
+ this.props.children,
+ this.el,
+ );
+ }
+}
+
+export default EditorPortal;
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/editor.js b/frontend/src/metadata/metadata-view/components/cell-editor/editor.js
new file mode 100644
index 00000000000..c2f81ca71a7
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/editor.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { CellType } from '../../_basic';
+import FileNameEditor from './file-name-editor';
+
+const Editor = (props) => {
+
+ switch (props.column.type) {
+ case CellType.FILE_NAME: {
+ return ();
+ }
+ default: {
+ return null;
+ }
+ }
+};
+
+Editor.propTypes = {
+ column: PropTypes.object.isRequired,
+};
+
+export default Editor;
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/file-name-editor.js b/frontend/src/metadata/metadata-view/components/cell-editor/file-name-editor.js
new file mode 100644
index 00000000000..f0fe53185e5
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/file-name-editor.js
@@ -0,0 +1,97 @@
+import React, { useEffect, useMemo } from 'react';
+import PropTypes from 'prop-types';
+import { ModalPortal } from '@seafile/sf-metadata-ui-component';
+import { PRIVATE_COLUMN_KEY } from '../../_basic';
+import { Utils } from '../../../../utils/utils';
+import ImageDialog from '../../../../components/dialog/image-dialog';
+import { serviceURL, siteRoot, thumbnailSizeForOriginal } from '../../../../utils/constants';
+
+const FileNameEditor = ({ column, record, onCommitCancel }) => {
+ const _isDir = useMemo(() => {
+ const isDirValue = record[PRIVATE_COLUMN_KEY.IS_DIR];
+ if (typeof isDirValue === 'string') return isDirValue.toUpperCase() === 'TRUE';
+ return isDirValue;
+ }, [record]);
+
+ const fileName = useMemo(() => {
+ const { key } = column;
+ return record[key];
+ }, [column, record]);
+
+ const fileType = useMemo(() => {
+ if (_isDir) return 'folder';
+ if (!fileName) return '';
+ const index = fileName.lastIndexOf('.');
+ if (index === -1) return 'file';
+ const suffix = fileName.slice(index).toLowerCase();
+ if (Utils.imageCheck(fileName)) return 'image';
+ if (suffix === '.sdoc') return 'sdoc';
+ return 'file';
+ }, [_isDir, fileName]);
+
+ const parentDir = useMemo(() => {
+ const value = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
+ if (value === '/') return '';
+ return value;
+ }, [record]);
+
+ const repoID = useMemo(() => {
+ return window.sfMetadataContext.getSetting('repoID');
+ }, []);
+
+ const path = useMemo(() => {
+ return Utils.encodePath(Utils.joinPath(parentDir, fileName));
+ }, [parentDir, fileName]);
+
+ const url = useMemo(() => {
+ return `${siteRoot}lib/${repoID}/file${path}`;
+ }, [path, repoID]);
+
+ useEffect(() => {
+ if (fileType === 'image') return;
+ onCommitCancel && onCommitCancel();
+ }, [fileType, onCommitCancel]);
+
+ if (!fileName) return null;
+
+ if (fileType === 'image') {
+ const fileExt = fileName.substr(fileName.lastIndexOf('.') + 1).toLowerCase();
+ const isGIF = fileExt === 'gif';
+ const useThumbnail = window.sfMetadataContext.getSetting('currentRepoInfo')?.encrypted;
+ let src = '';
+ if (useThumbnail && !isGIF) {
+ src = `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForOriginal}${path}`;
+ } else {
+ src = `${siteRoot}repo/${repoID}/raw${path}`;
+ }
+ const images = [
+ { 'name': fileName, 'url': url, 'src': src },
+ ];
+ return (
+
+ {}}
+ moveToNextImage={() => {}}
+ />
+
+ );
+ }
+
+ if (fileType === 'sdoc') {
+ window.open(serviceURL + url);
+ } else {
+ window.open(window.location.href + Utils.encodePath(Utils.joinPath(parentDir, fileName)));
+ }
+ return null;
+};
+
+FileNameEditor.propTypes = {
+ column: PropTypes.object,
+ record: PropTypes.object,
+ onCommitCancel: PropTypes.func,
+};
+
+export default FileNameEditor;
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/index.js b/frontend/src/metadata/metadata-view/components/cell-editor/index.js
new file mode 100644
index 00000000000..8d3dbed8940
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/index.js
@@ -0,0 +1,7 @@
+import EditorPortal from './editor-portal';
+import EditorContainer from './editor-container';
+
+export {
+ EditorPortal,
+ EditorContainer,
+};
diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/record-cell.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/record-cell.js
index ed731ef64b4..91f4e513dd9 100644
--- a/frontend/src/metadata/metadata-view/components/table/table-main/records/record-cell.js
+++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/record-cell.js
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import toaster from '../../../../../../components/toast';
-import { isFunction } from '../../../../_basic';
+import { PRIVATE_COLUMN_KEY, isFunction } from '../../../../_basic';
import { isNameColumn } from '../../../../utils/column-utils';
import { TABLE_SUPPORT_EDIT_TYPE_MAP } from '../../../../constants';
import { isCellValueChanged } from '../../../../utils/cell-comparer';
@@ -75,9 +75,7 @@ class RecordCell extends React.Component {
};
onCellMouseDown = (e) => {
- if (e.button === 2) {
- return;
- }
+ if (e.button === 2) return;
const { column, groupRecordIndex, recordIndex, cellMetaData } = this.props;
const cell = { idx: column.idx, groupRecordIndex, rowIdx: recordIndex };
@@ -133,9 +131,16 @@ class RecordCell extends React.Component {
toaster.warning(message);
};
+ isDir = () => {
+ const { record } = this.props;
+ const isDirValue = record[PRIVATE_COLUMN_KEY.IS_DIR];
+ if (typeof isDirValue === 'string') return isDirValue.toUpperCase() === 'TRUE';
+ return isDirValue;
+ };
+
render = () => {
const { frozen, record, column, needBindEvents, height, bgColor } = this.props;
- const { key, name, left, width } = column;
+ const { key, left, width } = column;
const readonly = true;
const commentCount = isNameColumn(column) && this.getCommentCount();
const hasComment = !!commentCount;
@@ -151,7 +156,7 @@ class RecordCell extends React.Component {
cellStyle['backgroundColor'] = bgColor;
}
- let cellValue = record[name] || record[key];
+ let cellValue = record[key];
const cellEvents = needBindEvents && this.getEvents();
const props = {
className,
@@ -159,7 +164,7 @@ class RecordCell extends React.Component {
...cellEvents,
};
const cellContent = (
-
+
);
return (
diff --git a/frontend/src/metadata/metadata-view/components/table/table-masks/interaction-masks/index.js b/frontend/src/metadata/metadata-view/components/table/table-masks/interaction-masks/index.js
index 194e6c58ef8..39f277e8cd3 100644
--- a/frontend/src/metadata/metadata-view/components/table/table-masks/interaction-masks/index.js
+++ b/frontend/src/metadata/metadata-view/components/table/table-masks/interaction-masks/index.js
@@ -25,6 +25,7 @@ import { getGroupRecordByIndex } from '../../../../utils/group-metrics';
import DragMask from '../drag-mask';
import DragHandler from '../drag-handler';
import { gettext } from '../../../../../../utils/constants';
+import { EditorPortal, EditorContainer } from '../../../cell-editor';
import './index.css';
@@ -32,53 +33,6 @@ const READONLY_PREVIEW_COLUMNS = [
];
-const propTypes = {
- table: PropTypes.object,
- columns: PropTypes.array,
- canAddRow: PropTypes.bool,
- isGroupView: PropTypes.bool,
- recordsCount: PropTypes.number,
- recordMetrics: PropTypes.object,
- groups: PropTypes.array,
- groupMetrics: PropTypes.object,
- rowHeight: PropTypes.number,
- groupOffsetLeft: PropTypes.number,
- frozenColumnsWidth: PropTypes.number,
- enableCellSelect: PropTypes.bool,
- getRowTop: PropTypes.func,
- scrollTop: PropTypes.number,
- getScrollLeft: PropTypes.func,
- getTableContentRect: PropTypes.func,
- getMobileFloatIconStyle: PropTypes.func,
- onToggleMobileMoreOperations: PropTypes.func,
- onToggleInsertRecordDialog: PropTypes.func,
- onCellRangeSelectionStarted: PropTypes.func,
- onCellRangeSelectionUpdated: PropTypes.func,
- onCellRangeSelectionCompleted: PropTypes.func,
- selectNone: PropTypes.func,
- onCheckCellIsEditable: PropTypes.func,
- editorPortalTarget: PropTypes.instanceOf(Element).isRequired,
- modifyRecord: PropTypes.func.isRequired,
- recordGetterByIndex: PropTypes.func,
- recordGetterById: PropTypes.func,
- updateRecords: PropTypes.func,
- deleteRecordsLinks: PropTypes.func,
- paste: PropTypes.func,
- editMobileCell: PropTypes.func,
- getVisibleIndex: PropTypes.func,
- onHitBottomBoundary: PropTypes.func,
- onHitTopBoundary: PropTypes.func,
- onCellClick: PropTypes.func,
- scrollToColumn: PropTypes.func,
- setRecordsScrollLeft: PropTypes.func,
- getGroupCanvasScrollTop: PropTypes.func,
- setGroupCanvasScrollTop: PropTypes.func,
- appPage: PropTypes.object,
- onFillingDragRows: PropTypes.func,
- onCellsDragged: PropTypes.func,
- gridUtils: PropTypes.object,
- getCopiedRecordsAndColumnsFromRange: PropTypes.func,
-};
class InteractionMasks extends React.Component {
@@ -478,7 +432,7 @@ class InteractionMasks extends React.Component {
}
};
- modifyRecord = (updated, closeEditor = true) => {
+ onCommit = (updated, closeEditor = true) => {
this.props.modifyRecord(updated);
if (closeEditor) {
this.closeEditor();
@@ -1057,7 +1011,6 @@ class InteractionMasks extends React.Component {
}
};
-
renderSingleCellSelectView = () => {
const { columns } = this.props;
const {
@@ -1157,7 +1110,8 @@ class InteractionMasks extends React.Component {
};
render() {
- const { selectedRange, draggedRange } = this.state;
+ const { selectedRange, isEditorEnabled, draggedRange, selectedPosition, firstEditorKeyDown, openEditorMode, editorPosition } = this.state;
+ const { table, columns, isGroupView, recordGetterByIndex, scrollTop, getScrollLeft, editorPortalTarget } = this.props;
const isSelectedSingleCell = selectedRangeIsSingleCell(selectedRange);
return (
+
+
+ )}
);
}
}
-InteractionMasks.propTypes = propTypes;
+InteractionMasks.propTypes = {
+ table: PropTypes.object,
+ columns: PropTypes.array,
+ canAddRow: PropTypes.bool,
+ isGroupView: PropTypes.bool,
+ recordsCount: PropTypes.number,
+ recordMetrics: PropTypes.object,
+ groups: PropTypes.array,
+ groupMetrics: PropTypes.object,
+ rowHeight: PropTypes.number,
+ groupOffsetLeft: PropTypes.number,
+ frozenColumnsWidth: PropTypes.number,
+ enableCellSelect: PropTypes.bool,
+ getRowTop: PropTypes.func,
+ scrollTop: PropTypes.number,
+ getScrollLeft: PropTypes.func,
+ getTableContentRect: PropTypes.func,
+ getMobileFloatIconStyle: PropTypes.func,
+ onToggleMobileMoreOperations: PropTypes.func,
+ onToggleInsertRecordDialog: PropTypes.func,
+ onCellRangeSelectionStarted: PropTypes.func,
+ onCellRangeSelectionUpdated: PropTypes.func,
+ onCellRangeSelectionCompleted: PropTypes.func,
+ selectNone: PropTypes.func,
+ onCheckCellIsEditable: PropTypes.func,
+ editorPortalTarget: PropTypes.instanceOf(Element).isRequired,
+ modifyRecord: PropTypes.func.isRequired,
+ recordGetterByIndex: PropTypes.func,
+ recordGetterById: PropTypes.func,
+ updateRecords: PropTypes.func,
+ deleteRecordsLinks: PropTypes.func,
+ paste: PropTypes.func,
+ editMobileCell: PropTypes.func,
+ getVisibleIndex: PropTypes.func,
+ onHitBottomBoundary: PropTypes.func,
+ onHitTopBoundary: PropTypes.func,
+ onCellClick: PropTypes.func,
+ scrollToColumn: PropTypes.func,
+ setRecordsScrollLeft: PropTypes.func,
+ getGroupCanvasScrollTop: PropTypes.func,
+ setGroupCanvasScrollTop: PropTypes.func,
+ appPage: PropTypes.object,
+ onFillingDragRows: PropTypes.func,
+ onCellsDragged: PropTypes.func,
+ gridUtils: PropTypes.object,
+ getCopiedRecordsAndColumnsFromRange: PropTypes.func,
+ onCommit: PropTypes.func,
+};
export default InteractionMasks;
diff --git a/frontend/src/metadata/metadata-view/context.js b/frontend/src/metadata/metadata-view/context.js
index 13f289fd991..d6a2d72ebb9 100644
--- a/frontend/src/metadata/metadata-view/context.js
+++ b/frontend/src/metadata/metadata-view/context.js
@@ -76,7 +76,7 @@ class Context {
};
canModifyRow = (row) => {
- return false;
+ return true;
};
getPermission = () => {
diff --git a/frontend/src/metadata/metadata-view/utils/column-utils.js b/frontend/src/metadata/metadata-view/utils/column-utils.js
index 8835dd8f8fd..01e33c8819b 100644
--- a/frontend/src/metadata/metadata-view/utils/column-utils.js
+++ b/frontend/src/metadata/metadata-view/utils/column-utils.js
@@ -120,15 +120,13 @@ export const setColumnOffsets = (columns) => {
export function isColumnSupportEdit(cell, columns) {
const column = columns[cell.idx];
- if (column?.type === CellType.LINK_FORMULA && [CellType.IMAGE, CellType.FILE].includes(column?.data?.array_type)) {
- return true;
- }
+ if (column.type === CellType.FILE_NAME) return true;
return false;
}
export function isColumnSupportDirectEdit(cell, columns) {
const column = columns[cell.idx];
- return [].includes(column?.type);
+ return [CellType.CHECKBOX].includes(column?.type);
}
const _getCustomColumnsWidth = () => {
@@ -172,21 +170,17 @@ export const recalculate = (columns, allColumns) => {
export const getColumnName = (key, name) => {
switch (key) {
case PRIVATE_COLUMN_KEY.CTIME:
+ case PRIVATE_COLUMN_KEY.FILE_CTIME:
return gettext('Created time');
case PRIVATE_COLUMN_KEY.MTIME:
+ case PRIVATE_COLUMN_KEY.FILE_MTIME:
return gettext('Last modified time');
case PRIVATE_COLUMN_KEY.CREATOR:
+ case PRIVATE_COLUMN_KEY.FILE_CREATOR:
return gettext('Creator');
case PRIVATE_COLUMN_KEY.LAST_MODIFIER:
- return gettext('Last modifier');
- case PRIVATE_COLUMN_KEY.FILE_CREATOR:
- return gettext('File creator');
case PRIVATE_COLUMN_KEY.FILE_MODIFIER:
- return gettext('File modifier');
- case PRIVATE_COLUMN_KEY.FILE_CTIME:
- return gettext('File created time');
- case PRIVATE_COLUMN_KEY.FILE_MTIME:
- return gettext('File last modified time');
+ return gettext('Last modifier');
case PRIVATE_COLUMN_KEY.IS_DIR:
return gettext('Is folder');
case PRIVATE_COLUMN_KEY.PARENT_DIR: