1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-10-21 19:00:12 +00:00

Fix drag and move field in kanban view and card view (#8280)

* 01 fix error typo

* 02 fix drag and move hidden fields

* 03 optimize codes
This commit is contained in:
Michael An
2025-09-30 15:12:04 +08:00
committed by GitHub
parent 5083b6c1e3
commit e787541fd0
6 changed files with 88 additions and 35 deletions

View File

@@ -133,7 +133,7 @@ class DirPath extends React.Component {
role={children ? 'button' : null}
onClick={children ? this.handleRefresh : () => {}}
>
<MetadataViewName id={viewId} />
{viewId && <MetadataViewName id={viewId} />}
</span>
{children && (
<>

View File

@@ -1,9 +1,10 @@
import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Switch from '../../../../components/switch';
import Icon from '../../../../components/icon';
function FieldItem({ field, isCollapsed, onToggleField, onMoveField, fieldIconConfig }) {
function FieldItem({ field, index, isCollapsed, onToggleField, onMoveField, fieldIconConfig, updateDragOverKey, dragOverColumnKey, draggingColumnIndex, updateDraggingKey }) {
let enteredCounter = 0;
const fieldItemRef = useRef(null);
const [isItemDropTipShow, setDropTipShow] = useState(false);
@@ -19,15 +20,17 @@ function FieldItem({ field, isCollapsed, onToggleField, onMoveField, fieldIconCo
e.stopPropagation();
e.dataTransfer.setDragImage(fieldItemRef.current, 10, 10);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('application/sf-metadata-filed-display-setting', field.key);
e.dataTransfer.setData('application/sf-metadata-field-display-setting', field.key);
updateDraggingKey(field.key);
};
const onTableDragEnter = (e) => {
const onDragEnter = (e) => {
e.stopPropagation();
enteredCounter++;
if (enteredCounter !== 0 && !isItemDropTipShow) {
setDropTipShow(true);
}
updateDragOverKey(field.key);
};
const onDragOver = (e) => {
@@ -37,6 +40,7 @@ function FieldItem({ field, isCollapsed, onToggleField, onMoveField, fieldIconCo
e.stopPropagation();
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
updateDragOverKey(field.key);
};
const onDragLeave = (e) => {
@@ -45,36 +49,45 @@ function FieldItem({ field, isCollapsed, onToggleField, onMoveField, fieldIconCo
if (enteredCounter === 0) {
setDropTipShow(false);
}
updateDragOverKey(null);
};
const onDrop = (e) => {
e.stopPropagation();
e.preventDefault();
setDropTipShow(false);
const droppedColumnKey = e.dataTransfer.getData('application/sf-metadata-filed-display-setting');
const droppedColumnKey = e.dataTransfer.getData('application/sf-metadata-field-display-setting');
if (droppedColumnKey === field.key) return;
onMoveField(droppedColumnKey, field.key);
updateDragOverKey(null);
updateDraggingKey(null);
};
const placeholder = () => {
return (
<div className="sf-metadata-filed-display-setting-switch">
<div className="sf-metadata-field-display-setting-switch">
<Icon symbol={fieldIconConfig[field.type]} />
<span className="text-truncate">{field.name}</span>
</div>
);
};
const isOver = (dragOverColumnKey === field.key);
return (
<div
ref={fieldItemRef}
className={`sf-metadata-filed-display-setting-item-container ${isCollapsed ? 'd-none' : ''}`}
className={classNames('sf-metadata-field-display-setting-item-container',
{ 'd-none': isCollapsed },
{ 'hide-column-can-drop-top': isOver && draggingColumnIndex >= index },
{ 'hide-column-can-drop': isOver && draggingColumnIndex < index },
)}
onDrop={onDrop}
onDragEnter={onTableDragEnter}
onDragEnter={onDragEnter}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
>
<div className="sf-metadata-filed-display-setting-dragbar" draggable="true" onDragStart={onDragStart}>
<div className="sf-metadata-field-display-setting-dragbar" draggable="true" onDragStart={onDragStart}>
<Icon symbol="drag" />
</div>
<Switch
@@ -89,10 +102,15 @@ function FieldItem({ field, isCollapsed, onToggleField, onMoveField, fieldIconCo
FieldItem.propTypes = {
isCollapsed: PropTypes.bool,
index: PropTypes.number.isRequired,
field: PropTypes.object.isRequired,
fieldIconConfig: PropTypes.object,
onToggleField: PropTypes.func.isRequired,
onMoveField: PropTypes.func.isRequired,
updateDragOverKey: PropTypes.func.isRequired,
dragOverColumnKey: PropTypes.string,
draggingColumnIndex: PropTypes.number,
updateDraggingKey: PropTypes.func.isRequired,
};
export default FieldItem;

View File

@@ -1,4 +1,4 @@
.sf-metadata-filed-display-setting .sf-metadata-filed-display-setting-header .sf-metadata-filed-display-toggle-btn {
.sf-metadata-field-display-setting .sf-metadata-field-display-setting-header .sf-metadata-field-display-toggle-btn {
width: 20px;
height: 20px;
display: flex;
@@ -9,20 +9,20 @@
transition: transform .3s cubic-bezier(.645,.045,.355,1);
}
.sf-metadata-filed-display-setting .sf-metadata-filed-display-setting-header .sf-metadata-filed-display-toggle-btn:hover {
.sf-metadata-field-display-setting .sf-metadata-field-display-setting-header .sf-metadata-field-display-toggle-btn:hover {
border-radius: 3px;
background-color: var(--bs-hover-bg);
color: var(--bs-icon-color);
cursor: pointer;
}
.sf-metadata-filed-display-setting .sf-metadata-filed-display-setting-banner .show-all-button {
.sf-metadata-field-display-setting .sf-metadata-field-display-setting-banner .show-all-button {
color: #FF8000;
cursor: pointer;
font-size: 12px;
}
.sf-metadata-filed-display-setting .sf-metadata-filed-display-setting-body .sf-metadata-filed-display-setting-item-container {
.sf-metadata-field-display-setting .sf-metadata-field-display-setting-body .sf-metadata-field-display-setting-item-container {
position: relative;
display: flex;
font-size: 14px;
@@ -31,21 +31,21 @@
border-radius: 4px;
}
.sf-metadata-filed-display-setting .sf-metadata-filed-display-setting-body .sf-metadata-filed-display-setting-item-container:hover {
.sf-metadata-field-display-setting .sf-metadata-field-display-setting-body .sf-metadata-field-display-setting-item-container:hover {
background-color: var(--bs-nav-hover-bg);
}
.sf-metadata-filed-display-setting .sf-metadata-filed-display-setting-body .sf-metadata-filed-display-setting-dragbar {
.sf-metadata-field-display-setting .sf-metadata-field-display-setting-body .sf-metadata-field-display-setting-dragbar {
color: #c2c2c2;
cursor: grab;
margin-right: 8px;
}
.sf-metadata-filed-display-setting .sf-metadata-filed-display-setting-body .sf-metadata-filed-display-setting-dragbar .seafile-multicolor-icon-drag {
.sf-metadata-field-display-setting .sf-metadata-field-display-setting-body .sf-metadata-field-display-setting-dragbar .seafile-multicolor-icon-drag {
vertical-align: -0.15em;
}
.sf-metadata-filed-display-setting-item-container .sf-metadata-switch.custom-switch {
.sf-metadata-field-display-setting-item-container .sf-metadata-switch.custom-switch {
width: 100%;
padding-left: 0;
display: inline-flex;
@@ -53,7 +53,7 @@
justify-content: space-between;
}
.sf-metadata-filed-display-setting-item-container .sf-metadata-switch .custom-switch .seafile-multicolor-icon {
.sf-metadata-field-display-setting-item-container .sf-metadata-switch .custom-switch .seafile-multicolor-icon {
fill: #666;
transform: scale(0.8);
font-size: 16px;
@@ -61,25 +61,43 @@
vertical-align: -0.15em;
}
.sf-metadata-filed-display-setting-item-container .sf-metadata-switch .custom-switch .custom-switch-description {
.sf-metadata-field-display-setting-item-container .sf-metadata-switch .custom-switch .custom-switch-description {
margin-left: 0;
padding-right: 5px;
flex: 1;
transition: .3s color;
}
.sf-metadata-filed-display-setting-item-container .sf-metadata-switch .custom-switch .custom-switch-indicator {
.sf-metadata-field-display-setting-item-container .sf-metadata-switch .custom-switch .custom-switch-indicator {
width: 22px;
height: 12px;
border-radius: 6px;
margin-right: .25rem;
}
.sf-metadata-filed-display-setting-item-container .sf-metadata-switch .custom-switch .custom-switch-indicator::before {
.sf-metadata-field-display-setting-item-container .sf-metadata-switch .custom-switch .custom-switch-indicator::before {
height: 8px;
width: 8px;
}
.sf-metadata-filed-display-setting-item-container .sf-metadata-switch .custom-switch-input:checked~.custom-switch-indicator:before {
.sf-metadata-field-display-setting-item-container .sf-metadata-switch .custom-switch-input:checked~.custom-switch-indicator:before {
left: 12px;
}
.sf-metadata-field-display-setting-item-container.hide-column-can-drop::after,
.sf-metadata-field-display-setting-item-container.hide-column-can-drop-top::before {
content: '';
height: 1px;
width: 100%;
position: absolute;
left: 0;
background: #666;
}
.sf-metadata-field-display-setting-item-container.hide-column-can-drop::after {
bottom: 0;
}
.sf-metadata-field-display-setting-item-container.hide-column-can-drop-top::before {
top: 0;
}

View File

@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react';
import React, { useRef, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { Label } from 'reactstrap';
import classnames from 'classnames';
@@ -15,6 +15,18 @@ const DURATION = 300;
const FieldDisplaySettings = ({ fieldIconConfig, fields, textProperties, onToggleField, onMoveField, onToggleFieldsVisibility }) => {
const nodeRef = useRef(null);
const [isCollapsed, setCollapsed] = useState(true);
const [dragOverColumnKey, setDragOverCellKey] = useState(null);
const [draggingColumnKey, setDraggingCellKey] = useState(null);
const updateDragOverKey = useCallback((cellKey) => {
if (cellKey === dragOverColumnKey) return;
setDragOverCellKey(cellKey);
}, [dragOverColumnKey]);
const updateDraggingKey = useCallback((cellKey) => {
if (cellKey === draggingColumnKey) return;
setDraggingCellKey(cellKey);
}, [draggingColumnKey]);
const expandAllFields = () => {
setCollapsed(!isCollapsed);
@@ -31,39 +43,45 @@ const FieldDisplaySettings = ({ fieldIconConfig, fields, textProperties, onToggl
exited: { opacity: 0, height: 0 },
};
const fieldAllShown = fields.every(field => field.shown);
const draggingColumnIndex = draggingColumnKey ? fields.findIndex(f => f.key === draggingColumnKey) : -1;
return (
<div className="sf-metadata-filed-display-setting">
<div className="sf-metadata-field-display-setting">
<div
className="sf-metadata-filed-display-setting-header d-flex align-items-center justify-content-between"
className="sf-metadata-field-display-setting-header d-flex align-items-center justify-content-between"
onClick={expandAllFields}
role="button"
aria-label={isCollapsed ? gettext('Expand') : gettext('Collapse')}
>
<Label className="mb-0">{textProperties.titleValue}</Label>
<div className="sf-metadata-filed-display-toggle-btn">
<div className="sf-metadata-field-display-toggle-btn">
<i aria-hidden="true" className={classnames('sf3-font sf3-font-down', { 'rotate-270': isCollapsed })}></i>
</div>
</div>
<Transition nodeRef={nodeRef} in={!isCollapsed} timeout={DURATION}>
{state => (
<div className="sf-metadata-filed-display-setting-wrapper" ref={nodeRef} style={{ ...defaultStyle, ...transitionStyles[state] }}>
<div className={`sf-metadata-filed-display-setting-banner ${isCollapsed ? 'd-none' : 'd-flex'} align-items-center justify-content-between h-5 mt-2 mb-2`}>
<div className="sf-metadata-field-display-setting-wrapper" ref={nodeRef} style={{ ...defaultStyle, ...transitionStyles[state] }}>
<div className={`sf-metadata-field-display-setting-banner ${isCollapsed ? 'd-none' : 'd-flex'} align-items-center justify-content-between h-5 mt-2 mb-2`}>
<Label className="mb-0">{textProperties.bannerValue}</Label>
<span className="show-all-button" onClick={() => onToggleFieldsVisibility(!fieldAllShown)}>
{fieldAllShown ? textProperties.hideValue : textProperties.showValue}
</span>
</div>
<div className="sf-metadata-filed-display-setting-body">
<div className="sf-metadata-field-display-setting-body">
{fields.map((field, index) => {
return (
<FieldItem
key={`${field.key}-${index}`}
field={field}
index={index}
fieldIconConfig={fieldIconConfig}
isCollapsed={isCollapsed}
onToggleField={onToggleField}
onMoveField={onMoveField}
updateDragOverKey={updateDragOverKey}
updateDraggingKey={updateDraggingKey}
dragOverColumnKey={dragOverColumnKey}
draggingColumnIndex={draggingColumnIndex}
/>
);
})}

View File

@@ -4,14 +4,13 @@ import { useMetadata } from '../hooks';
const MetadataViewName = ({ id }) => {
const { idViewMap } = useMetadata();
if (!id) return null;
const view = idViewMap[id];
if (!view) return null;
return (<>{view.name}</>);
};
MetadataViewName.propTypes = {
id: PropTypes.string,
id: PropTypes.string.isRequired,
};
export default MetadataViewName;

View File

@@ -22,10 +22,10 @@ const HideColumnItem = ({
const ref = useRef(null);
const onDragStart = useCallback((event) => {
const dragData = JSON.stringify({ type: 'sf-metadata-filed-display-setting', column_key: column.key });
const dragData = JSON.stringify({ type: 'sf-metadata-field-display-setting', column_key: column.key });
event.dataTransfer.setDragImage(ref.current, 10, 10);
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('application/drag-sf-metadata-filed-display-setting', dragData);
event.dataTransfer.setData('application/drag-sf-metadata-field-display-setting', dragData);
updateDraggingKey(column.key);
}, [column, updateDraggingKey]);
@@ -48,10 +48,10 @@ const HideColumnItem = ({
const onDrop = useCallback((event) => {
event.stopPropagation();
let dragData = event.dataTransfer.getData('application/drag-sf-metadata-filed-display-setting');
let dragData = event.dataTransfer.getData('application/drag-sf-metadata-field-display-setting');
if (!dragData) return false;
dragData = JSON.parse(dragData);
if (dragData.type !== 'sf-metadata-filed-display-setting' || !dragData.column_key) return false;
if (dragData.type !== 'sf-metadata-field-display-setting' || !dragData.column_key) return false;
if (dragData.column_key !== column.key) {
onMove && onMove(dragData.column_key, column.key);
}