1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-06 09:21:54 +00:00

optimize kanban (#7054)

* optimize kanban

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
Aries
2024-11-19 16:08:42 +08:00
committed by GitHub
parent 1429051186
commit d06667462e
12 changed files with 133 additions and 53 deletions

View File

@@ -7,10 +7,10 @@ import DetailItem from '../../../components/dirent-detail/detail-item';
import { Utils } from '../../../utils/utils'; import { Utils } from '../../../utils/utils';
import metadataAPI from '../../api'; import metadataAPI from '../../api';
import Column from '../../model/metadata/column'; import Column from '../../model/metadata/column';
import { getCellValueByColumn, getOptionName, getColumnOptionNamesByIds, getColumnOptionNameById, getFileNameFromRecord } from '../../utils/cell'; import { getCellValueByColumn, getOptionName, getColumnOptionNamesByIds, getColumnOptionNameById, getFileNameFromRecord, geRecordIdFromRecord, getFileObjIdFromRecord } from '../../utils/cell';
import { normalizeFields } from './utils'; import { normalizeFields } from './utils';
import { gettext } from '../../../utils/constants'; import { gettext } from '../../../utils/constants';
import { CellType, PREDEFINED_COLUMN_KEYS, PRIVATE_COLUMN_KEY } from '../../constants'; import { CellType, EVENT_BUS_TYPE, PREDEFINED_COLUMN_KEYS, PRIVATE_COLUMN_KEY } from '../../constants';
import { getColumnOptions, getColumnOriginName } from '../../utils/column'; import { getColumnOptions, getColumnOriginName } from '../../utils/column';
import { SYSTEM_FOLDERS } from './constants'; import { SYSTEM_FOLDERS } from './constants';
import Location from './location'; import Location from './location';
@@ -22,46 +22,24 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, updateRecord
const [metadata, setMetadata] = useState({ record: {}, fields: [] }); const [metadata, setMetadata] = useState({ record: {}, fields: [] });
const permission = useMemo(() => repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? 'r' : 'rw', [repoInfo]); const permission = useMemo(() => repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? 'r' : 'rw', [repoInfo]);
useEffect(() => {
setLoading(true);
if (SYSTEM_FOLDERS.find(folderPath => filePath.startsWith(folderPath))) {
setLoading(false);
return;
}
const dirName = Utils.getDirName(filePath);
const fileName = Utils.getFileName(filePath);
let parentDir = direntType === 'file' ? dirName : dirName.slice(0, dirName.length - fileName.length - 1);
if (!parentDir.startsWith('/')) {
parentDir = '/' + parentDir;
}
metadataAPI.getMetadataRecordInfo(repoID, parentDir, fileName).then(res => {
const { results, metadata } = res.data;
const record = Array.isArray(results) && results.length > 0 ? results[0] : {};
const fields = normalizeFields(metadata).map(field => new Column(field));
updateRecord && updateRecord(record);
setMetadata({ record, fields });
setLoading(false);
}).catch(error => {
const errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
setLoading(false);
});
}, [repoID, filePath, direntType, updateRecord]);
const onChange = useCallback((fieldKey, newValue) => { const onChange = useCallback((fieldKey, newValue) => {
const { record, fields } = metadata; const { record, fields } = metadata;
const field = fields.find(f => f.key === fieldKey); const field = fields.find(f => f.key === fieldKey);
const fileName = getColumnOriginName(field); const fileName = getColumnOriginName(field);
const recordId = geRecordIdFromRecord(record);
const fileObjId = getFileObjIdFromRecord(record);
let update = { [fileName]: newValue }; let update = { [fileName]: newValue };
if (field.type === CellType.SINGLE_SELECT) { if (field.type === CellType.SINGLE_SELECT) {
update = { [fileName]: getColumnOptionNameById(field, newValue) }; update = { [fileName]: getColumnOptionNameById(field, newValue) };
} else if (field.type === CellType.MULTIPLE_SELECT) { } else if (field.type === CellType.MULTIPLE_SELECT) {
update = { [fileName]: newValue ? getColumnOptionNamesByIds(field, newValue) : [] }; update = { [fileName]: newValue ? getColumnOptionNamesByIds(field, newValue) : [] };
} }
metadataAPI.modifyRecord(repoID, record._id, update, record._obj_id).then(res => { metadataAPI.modifyRecord(repoID, recordId, update, fileObjId).then(res => {
const newMetadata = { ...metadata, record: { ...record, ...update } }; const newMetadata = { ...metadata, record: { ...record, ...update } };
setMetadata(newMetadata); setMetadata(newMetadata);
if (window?.sfMetadataContext?.eventBus) {
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, recordId, update);
}
}).catch(error => { }).catch(error => {
const errorMsg = Utils.getErrorMsg(error); const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg); toaster.danger(errorMsg);
@@ -98,6 +76,48 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, updateRecord
}); });
}, [repoID, metadata]); }, [repoID, metadata]);
const localRecordChanged = useCallback((recordId, updates) => {
if (geRecordIdFromRecord(metadata?.record) !== recordId) return;
const newMetadata = { ...metadata, record: { ...metadata.record, ...updates } };
setMetadata(newMetadata);
}, [metadata]);
useEffect(() => {
setLoading(true);
if (SYSTEM_FOLDERS.find(folderPath => filePath.startsWith(folderPath))) {
setLoading(false);
return;
}
const dirName = Utils.getDirName(filePath);
const fileName = Utils.getFileName(filePath);
let parentDir = direntType === 'file' ? dirName : dirName.slice(0, dirName.length - fileName.length - 1);
if (!parentDir.startsWith('/')) {
parentDir = '/' + parentDir;
}
metadataAPI.getMetadataRecordInfo(repoID, parentDir, fileName).then(res => {
const { results, metadata } = res.data;
const record = Array.isArray(results) && results.length > 0 ? results[0] : {};
const fields = normalizeFields(metadata).map(field => new Column(field));
updateRecord && updateRecord(record);
setMetadata({ record, fields });
setLoading(false);
}).catch(error => {
const errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
setLoading(false);
});
}, [repoID, filePath, direntType, updateRecord]);
useEffect(() => {
const eventBus = window?.sfMetadataContext?.eventBus;
if (!eventBus) return;
const unsubscribeLocalRecordChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, localRecordChanged);
return () => {
unsubscribeLocalRecordChanged();
};
}, [localRecordChanged]);
if (isLoading) return null; if (isLoading) return null;
const { fields, record } = metadata; const { fields, record } = metadata;
if (!record._id) return null; if (!record._id) return null;

View File

@@ -3,16 +3,16 @@
*/ */
export const EVENT_BUS_TYPE = { export const EVENT_BUS_TYPE = {
QUERY_COLLABORATORS: 'query-collaborators', QUERY_COLLABORATORS: 'query_collaborators',
QUERY_COLLABORATOR: 'query-collaborator', QUERY_COLLABORATOR: 'query_collaborator',
UPDATE_TABLE_ROWS: 'update-table-rows', UPDATE_TABLE_ROWS: 'update_table_rows',
// table // table
LOCAL_TABLE_CHANGED: 'local-table-changed', LOCAL_TABLE_CHANGED: 'local_table_changed',
SERVER_TABLE_CHANGED: 'server-table-changed', SERVER_TABLE_CHANGED: 'server_table_changed',
TABLE_ERROR: 'table-error', TABLE_ERROR: 'table_error',
OPEN_EDITOR: 'open-editor', OPEN_EDITOR: 'open_editor',
CLOSE_EDITOR: 'close-editor', CLOSE_EDITOR: 'close_editor',
SELECT_CELL: 'select_cell', SELECT_CELL: 'select_cell',
SELECT_START: 'select_start', SELECT_START: 'select_start',
SELECT_UPDATE: 'select_update', SELECT_UPDATE: 'select_update',
@@ -21,14 +21,16 @@ export const EVENT_BUS_TYPE = {
SELECT_NONE: 'select_none', SELECT_NONE: 'select_none',
COPY_CELLS: 'copy_cells', COPY_CELLS: 'copy_cells',
PASTE_CELLS: 'paste_cells', PASTE_CELLS: 'paste_cells',
SEARCH_CELLS: 'search-cells', SEARCH_CELLS: 'search_cells',
CLOSE_SEARCH_CELLS: 'close-search-cells', CLOSE_SEARCH_CELLS: 'close_search_cells',
OPEN_SELECT: 'open-select', OPEN_SELECT: 'open_select',
UPDATE_LINKED_RECORDS: 'update_linked_records', UPDATE_LINKED_RECORDS: 'update_linked_records',
SELECT_COLUMN: 'select_column', SELECT_COLUMN: 'select_column',
DRAG_ENTER: 'drag_enter', DRAG_ENTER: 'drag_enter',
COLLAPSE_ALL_GROUPS: 'collapse_all_groups', COLLAPSE_ALL_GROUPS: 'collapse_all_groups',
EXPAND_ALL_GROUPS: 'expand_all_groups', EXPAND_ALL_GROUPS: 'expand_all_groups',
LOCAL_RECORD_CHANGED: 'local_record_changed',
LOCAL_RECORD_DETAIL_CHANGED: 'local_record_detail_changed',
// metadata // metadata
RELOAD_DATA: 'reload_data', RELOAD_DATA: 'reload_data',

View File

@@ -70,6 +70,10 @@ export const MetadataViewProvider = ({
window.sfMetadataStore.modifySettings(settings); window.sfMetadataStore.modifySettings(settings);
}, []); }, []);
const updateLocalRecord = useCallback((recordId, update) => {
window.sfMetadataStore.modifyLocalRecord(recordId, update);
}, []);
// init // init
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
@@ -103,6 +107,7 @@ export const MetadataViewProvider = ({
const unsubscribeModifyHiddenColumns = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_HIDDEN_COLUMNS, modifyHiddenColumns); const unsubscribeModifyHiddenColumns = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_HIDDEN_COLUMNS, modifyHiddenColumns);
const unsubscribeModifyColumnOrder = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_COLUMN_ORDER, modifyColumnOrder); const unsubscribeModifyColumnOrder = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_COLUMN_ORDER, modifyColumnOrder);
const unsubscribeModifySettings = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_SETTINGS, modifySettings); const unsubscribeModifySettings = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_SETTINGS, modifySettings);
const unsubscribeLocalRecordChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, updateLocalRecord);
return () => { return () => {
if (window.sfMetadataContext) { if (window.sfMetadataContext) {
@@ -120,6 +125,7 @@ export const MetadataViewProvider = ({
unsubscribeModifyHiddenColumns(); unsubscribeModifyHiddenColumns();
unsubscribeModifyColumnOrder(); unsubscribeModifyColumnOrder();
unsubscribeModifySettings(); unsubscribeModifySettings();
unsubscribeLocalRecordChanged();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [repoID, viewID]); }, [repoID, viewID]);

View File

@@ -408,6 +408,17 @@ class Store {
this.applyOperation(operation); this.applyOperation(operation);
} }
modifyLocalRecord(row_id, updates) {
const type = OPERATION_TYPE.MODIFY_LOCAL_RECORD;
const operation = this.createOperation({
type,
row_id,
repo_id: this.repoId,
updates
});
this.applyOperation(operation);
}
modifyFilters(filterConjunction, filters, basicFilters = []) { modifyFilters(filterConjunction, filters, basicFilters = []) {
const type = OPERATION_TYPE.MODIFY_FILTERS; const type = OPERATION_TYPE.MODIFY_FILTERS;
const operation = this.createOperation({ const operation = this.createOperation({

View File

@@ -96,6 +96,27 @@ export default function apply(data, operation) {
data.id_row_map[row_id] = updatedRow; data.id_row_map[row_id] = updatedRow;
return data; return data;
} }
case OPERATION_TYPE.MODIFY_LOCAL_RECORD: {
const { row_id, updates } = operation;
const { rows } = data;
const modifyTime = dayjs().utc().format(UTC_FORMAT_DEFAULT);
const modifier = window.sfMetadataContext.getUsername();
let updatedRows = [...rows];
rows.forEach((row, index) => {
const { _id: rowId } = row;
if (rowId === row_id && updates) {
const updatedRow = Object.assign({}, row, updates, {
'_mtime': modifyTime,
'_last_modifier': modifier,
});
updatedRows[index] = updatedRow;
data.id_row_map[rowId] = updatedRow;
}
});
data.rows = updatedRows;
return data;
}
case OPERATION_TYPE.MODIFY_FILTERS: { case OPERATION_TYPE.MODIFY_FILTERS: {
const { filter_conjunction, filters, basic_filters } = operation; const { filter_conjunction, filters, basic_filters } = operation;
data.view.filter_conjunction = filter_conjunction; data.view.filter_conjunction = filter_conjunction;

View File

@@ -10,6 +10,7 @@ export const OPERATION_TYPE = {
LOCK_RECORD_VIA_BUTTON: 'lock_record_via_button', LOCK_RECORD_VIA_BUTTON: 'lock_record_via_button',
MODIFY_RECORD_VIA_BUTTON: 'modify_record_via_button', MODIFY_RECORD_VIA_BUTTON: 'modify_record_via_button',
MODIFY_SETTINGS: 'modify_settings', MODIFY_SETTINGS: 'modify_settings',
MODIFY_LOCAL_RECORD: 'modify_local_record',
// column // column
INSERT_COLUMN: 'insert_column', INSERT_COLUMN: 'insert_column',
@@ -53,6 +54,7 @@ export const OPERATION_ATTRIBUTES = {
[OPERATION_TYPE.RENAME_PEOPLE_NAME]: ['repo_id', 'people_id', 'new_name', 'old_name'], [OPERATION_TYPE.RENAME_PEOPLE_NAME]: ['repo_id', 'people_id', 'new_name', 'old_name'],
[OPERATION_TYPE.DELETE_PEOPLE_PHOTOS]: ['repo_id', 'people_id', 'deleted_photos'], [OPERATION_TYPE.DELETE_PEOPLE_PHOTOS]: ['repo_id', 'people_id', 'deleted_photos'],
[OPERATION_TYPE.MODIFY_SETTINGS]: ['repo_id', 'view_id', 'settings'], [OPERATION_TYPE.MODIFY_SETTINGS]: ['repo_id', 'view_id', 'settings'],
[OPERATION_TYPE.MODIFY_LOCAL_RECORD]: ['repo_id', 'row_id', 'updates'],
}; };
export const UNDO_OPERATION_TYPE = [ export const UNDO_OPERATION_TYPE = [
@@ -64,6 +66,7 @@ export const UNDO_OPERATION_TYPE = [
// only apply operation on the local // only apply operation on the local
export const LOCAL_APPLY_OPERATION_TYPE = [ export const LOCAL_APPLY_OPERATION_TYPE = [
OPERATION_TYPE.MODIFY_COLUMN_WIDTH, OPERATION_TYPE.MODIFY_COLUMN_WIDTH,
OPERATION_TYPE.MODIFY_LOCAL_RECORD,
]; ];
// apply operation after exec operation on the server // apply operation after exec operation on the server

View File

@@ -29,6 +29,7 @@
.sf-metadata-kanban-card .sf-metadata-kanban-card-header .sf-metadata-file-name { .sf-metadata-kanban-card .sf-metadata-kanban-card-header .sf-metadata-file-name {
text-decoration: none; text-decoration: none;
flex: unset;
} }
.sf-metadata-kanban-card .sf-metadata-kanban-card-header .sf-metadata-file-name:hover { .sf-metadata-kanban-card .sf-metadata-kanban-card-header .sf-metadata-file-name:hover {

View File

@@ -4,6 +4,14 @@
margin-right: 16px; margin-right: 16px;
} }
.sf-metadata-view-kanban-board .smooth-dnd-container.vertical {
height: calc(100% - 32px);
}
.sf-metadata-view-kanban-board .smooth-dnd-container.vertical .smooth-dnd-draggable-wrapper:last-child .sf-metadata-kanban-card {
margin-bottom: 0;
}
.sf-metadata-view-kanban-board .smooth-dnd-container.vertical > .smooth-dnd-draggable-wrapper { .sf-metadata-view-kanban-board .smooth-dnd-container.vertical > .smooth-dnd-draggable-wrapper {
overflow: visible; overflow: visible;
} }

View File

@@ -27,6 +27,7 @@ const Board = ({
onUnFreezed, onUnFreezed,
onOpenFile, onOpenFile,
onSelectCard, onSelectCard,
updateDragging,
}) => { }) => {
const [isDraggingOver, setDraggingOver] = useState(false); const [isDraggingOver, setDraggingOver] = useState(false);
const boardName = useMemo(() => `sf_metadata_kanban_board_${board.key}`, [board]); const boardName = useMemo(() => `sf_metadata_kanban_board_${board.key}`, [board]);
@@ -34,8 +35,9 @@ const Board = ({
const { metadata } = useMetadataView(); const { metadata } = useMetadataView();
const onDragStart = useCallback(({ payload }) => { const onDragStart = useCallback(({ payload }) => {
updateDragging(true);
return payload; return payload;
}, []); }, [updateDragging]);
const onDragEnd = useCallback((targetBoardIndex, result) => { const onDragEnd = useCallback((targetBoardIndex, result) => {
if (isDraggingOver) { if (isDraggingOver) {
@@ -44,12 +46,11 @@ const Board = ({
const { addedIndex, payload } = result; const { addedIndex, payload } = result;
const { boardIndex: sourceBoardIndex, cardIndex: sourceCardIndex } = payload; const { boardIndex: sourceBoardIndex, cardIndex: sourceCardIndex } = payload;
if (sourceBoardIndex === targetBoardIndex) return; if (sourceBoardIndex !== targetBoardIndex && sourceCardIndex !== null && addedIndex !== null) {
if (sourceCardIndex !== null && addedIndex !== null) {
onMove(targetBoardIndex, sourceBoardIndex, sourceCardIndex); onMove(targetBoardIndex, sourceBoardIndex, sourceCardIndex);
} }
setTimeout(() => updateDragging(false), 0);
}, [isDraggingOver, onMove]); }, [isDraggingOver, onMove, updateDragging]);
return ( return (
<section draggable={false} className="sf-metadata-view-kanban-board"> <section draggable={false} className="sf-metadata-view-kanban-board">
@@ -120,6 +121,7 @@ Board.propTypes = {
onUnFreezed: PropTypes.func, onUnFreezed: PropTypes.func,
onOpenFile: PropTypes.func.isRequired, onOpenFile: PropTypes.func.isRequired,
onSelectCard: PropTypes.func.isRequired, onSelectCard: PropTypes.func.isRequired,
updateDragging: PropTypes.func.isRequired,
}; };
export default Board; export default Board;

View File

@@ -2,12 +2,10 @@
flex: 1; flex: 1;
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow: hidden; overflow: auto;
} }
.sf-metadata-view-kanban-boards .smooth-dnd-container.horizontal { .sf-metadata-view-kanban-boards .smooth-dnd-container.horizontal {
height: 100%;
overflow: auto;
white-space: nowrap; white-space: nowrap;
display: flex; display: flex;
padding: 16px; padding: 16px;

View File

@@ -23,6 +23,7 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => {
const [haveFreezed, setHaveFreezed] = useState(false); const [haveFreezed, setHaveFreezed] = useState(false);
const [isImagePreviewerVisible, setImagePreviewerVisible] = useState(false); const [isImagePreviewerVisible, setImagePreviewerVisible] = useState(false);
const [selectedCard, setSelectedCard] = useState(''); const [selectedCard, setSelectedCard] = useState('');
const [isDragging, setDragging] = useState(false);
const currentImageRef = useRef(null); const currentImageRef = useRef(null);
const containerRef = useRef(null); const containerRef = useRef(null);
@@ -188,6 +189,7 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => {
const onSelectCard = useCallback((record) => { const onSelectCard = useCallback((record) => {
const recordId = geRecordIdFromRecord(record); const recordId = geRecordIdFromRecord(record);
if (selectedCard === recordId) return;
const name = getFileNameFromRecord(record); const name = getFileNameFromRecord(record);
const path = getParentDirFromRecord(record); const path = getParentDirFromRecord(record);
const isDir = checkIsDir(record); const isDir = checkIsDir(record);
@@ -201,13 +203,17 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => {
setSelectedCard(recordId); setSelectedCard(recordId);
onCloseSettings(); onCloseSettings();
showDirentDetail(); showDirentDetail();
}, [onCloseSettings, showDirentDetail, updateCurrentDirent]); }, [selectedCard, onCloseSettings, showDirentDetail, updateCurrentDirent]);
const handleClickOutside = useCallback((event) => { const handleClickOutside = useCallback((event) => {
if (!containerRef.current.contains(event.target)) return; if (isDragging) return;
setSelectedCard(null); setSelectedCard(null);
updateCurrentDirent(); updateCurrentDirent();
}, [updateCurrentDirent]); }, [isDragging, updateCurrentDirent]);
const updateDragging = useCallback((isDragging) => {
setDragging(isDragging);
}, []);
useEffect(() => { useEffect(() => {
if (!isDirentDetailShow) { if (!isDirentDetailShow) {
@@ -250,6 +256,7 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => {
onUnFreezed={onUnFreezed} onUnFreezed={onUnFreezed}
onOpenFile={onOpenFile} onOpenFile={onOpenFile}
onSelectCard={onSelectCard} onSelectCard={onSelectCard}
updateDragging={updateDragging}
/> />
); );
})} })}

View File

@@ -25,7 +25,8 @@ const Kanban = () => {
error && toaster.danger(error); error && toaster.danger(error);
}, },
success_callback: () => { success_callback: () => {
// do nothing const eventBus = window.sfMetadataContext.eventBus;
eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, rowId, updates);
}, },
}); });
}, [store]); }, [store]);