mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-07 18:03:48 +00:00
kanban supports collapse (#7437)
* kanban supports collapse * optimize * optimize ui --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
@@ -35,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-details-collapse .file-details-collapse-header .file-details-collapse-header-operation:hover {
|
.file-details-collapse .file-details-collapse-header .file-details-collapse-header-operation:hover {
|
||||||
background-color: #DBDBDB;
|
background-color: #EFEFEF;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
@@ -27,3 +27,25 @@
|
|||||||
color: #666666;
|
color: #666666;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sf-metadata-view-kanban-board-header .board-header-operation-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-header-operation-btn .kanban-header-op-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
color: #666666;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-header-operation-btn .kanban-header-op-btn.kanban-header-collapse-btn:hover,
|
||||||
|
.board-header-operation-btn .kanban-header-op-btn.kanban-more-operations-toggle:hover {
|
||||||
|
background-color: #EFEFEF;
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
import CellFormatter from '../../../../../components/cell-formatter';
|
import CellFormatter from '../../../../../components/cell-formatter';
|
||||||
import { gettext } from '../../../../../../utils/constants';
|
import { gettext } from '../../../../../../utils/constants';
|
||||||
import OpMenu from './op-menu';
|
import OpMenu from './op-menu';
|
||||||
@@ -7,9 +8,11 @@ import { CellType } from '../../../../../constants';
|
|||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, onDelete, onFreezed, onUnFreezed }) => {
|
const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, onDelete, onFreezed, onUnFreezed, isCollapsed, onCollapse }) => {
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
|
|
||||||
|
const headerRef = useRef();
|
||||||
|
|
||||||
const onMouseEnter = useCallback(() => {
|
const onMouseEnter = useCallback(() => {
|
||||||
if (haveFreezed) return;
|
if (haveFreezed) return;
|
||||||
setActive(true);
|
setActive(true);
|
||||||
@@ -20,10 +23,14 @@ const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, on
|
|||||||
setActive(false);
|
setActive(false);
|
||||||
}, [haveFreezed]);
|
}, [haveFreezed]);
|
||||||
|
|
||||||
const handelUnFreezed = useCallback((keepActive) => {
|
const keepActive = useCallback((event) => {
|
||||||
|
return event.target.className?.includes('kanban-header-op-btn') || event.target === headerRef.current;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handelUnFreezed = useCallback((event) => {
|
||||||
onUnFreezed();
|
onUnFreezed();
|
||||||
!keepActive && setActive(false);
|
!keepActive(event) && setActive(false);
|
||||||
}, [onUnFreezed]);
|
}, [onUnFreezed, keepActive]);
|
||||||
|
|
||||||
const titleValue = useMemo(() => {
|
const titleValue = useMemo(() => {
|
||||||
if (!value || !groupByColumn) return null;
|
if (!value || !groupByColumn) return null;
|
||||||
@@ -31,9 +38,14 @@ const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, on
|
|||||||
return value;
|
return value;
|
||||||
}, [value, groupByColumn]);
|
}, [value, groupByColumn]);
|
||||||
|
|
||||||
|
const handleCollapse = useCallback((event) => {
|
||||||
|
onCollapse();
|
||||||
|
!keepActive(event) && setActive(false);
|
||||||
|
}, [onCollapse, keepActive]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sf-metadata-view-kanban-board-header" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
<div className="sf-metadata-view-kanban-board-header" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
<div className="sf-metadata-view-kanban-board-header-title">
|
<div className="sf-metadata-view-kanban-board-header-title" ref={headerRef}>
|
||||||
{value ? (
|
{value ? (
|
||||||
<CellFormatter value={titleValue} field={groupByColumn} readonly={true} />
|
<CellFormatter value={titleValue} field={groupByColumn} readonly={true} />
|
||||||
) : (
|
) : (
|
||||||
@@ -41,8 +53,15 @@ const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, on
|
|||||||
)}
|
)}
|
||||||
<span className="cards-quantity">{cardsQuantity}</span>
|
<span className="cards-quantity">{cardsQuantity}</span>
|
||||||
</div>
|
</div>
|
||||||
{value && !readonly && active && (
|
{active && (
|
||||||
<OpMenu onDelete={onDelete} onFreezed={onFreezed} onUnFreezed={handelUnFreezed} />
|
<div className="board-header-operation-btn">
|
||||||
|
{value && !readonly && <OpMenu onDelete={onDelete} onFreezed={onFreezed} onUnFreezed={handelUnFreezed} />}
|
||||||
|
<i
|
||||||
|
className={classNames('sf3-font sf3-font-down kanban-header-op-btn kanban-header-collapse-btn', { 'rotate-90': isCollapsed })}
|
||||||
|
title={isCollapsed ? gettext('Expand') : gettext('Collapse')}
|
||||||
|
onClick={handleCollapse}>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -9,8 +9,7 @@ const OpMenu = ({ onDelete, onFreezed, onUnFreezed }) => {
|
|||||||
const toggle = useCallback((event) => {
|
const toggle = useCallback((event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (isShow) {
|
if (isShow) {
|
||||||
const isClickToggleBtn = event.target.className?.includes('kanban-more-operations-toggle');
|
onUnFreezed(event);
|
||||||
onUnFreezed(isClickToggleBtn);
|
|
||||||
} else {
|
} else {
|
||||||
onFreezed();
|
onFreezed();
|
||||||
}
|
}
|
||||||
@@ -35,7 +34,7 @@ const OpMenu = ({ onDelete, onFreezed, onUnFreezed }) => {
|
|||||||
tag="i"
|
tag="i"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
className="sf-dropdown-toggle sf3-font-more sf3-font kanban-more-operations-toggle"
|
className="sf-dropdown-toggle sf3-font-more sf3-font kanban-header-op-btn kanban-more-operations-toggle"
|
||||||
title={gettext('More operations')}
|
title={gettext('More operations')}
|
||||||
aria-label={gettext('More operations')}
|
aria-label={gettext('More operations')}
|
||||||
data-toggle="dropdown"
|
data-toggle="dropdown"
|
||||||
|
@@ -31,6 +31,7 @@ const Board = ({
|
|||||||
onContextMenu,
|
onContextMenu,
|
||||||
}) => {
|
}) => {
|
||||||
const [isDraggingOver, setDraggingOver] = useState(false);
|
const [isDraggingOver, setDraggingOver] = useState(false);
|
||||||
|
const [isCollapsed, setCollapsed] = useState(false);
|
||||||
const boardName = useMemo(() => `sf_metadata_kanban_board_${board.key}`, [board]);
|
const boardName = useMemo(() => `sf_metadata_kanban_board_${board.key}`, [board]);
|
||||||
const cardsQuantity = useMemo(() => board.children.length, [board.children]);
|
const cardsQuantity = useMemo(() => board.children.length, [board.children]);
|
||||||
|
|
||||||
@@ -54,6 +55,10 @@ const Board = ({
|
|||||||
setTimeout(() => updateDragging(false), 0);
|
setTimeout(() => updateDragging(false), 0);
|
||||||
}, [isDraggingOver, onMove, updateDragging]);
|
}, [isDraggingOver, onMove, updateDragging]);
|
||||||
|
|
||||||
|
const onCollapse = useCallback(() => {
|
||||||
|
setCollapsed(!isCollapsed);
|
||||||
|
}, [isCollapsed]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section draggable={false} className="sf-metadata-view-kanban-board">
|
<section draggable={false} className="sf-metadata-view-kanban-board">
|
||||||
<Header
|
<Header
|
||||||
@@ -65,55 +70,59 @@ const Board = ({
|
|||||||
onDelete={() => deleteOption(board.key)}
|
onDelete={() => deleteOption(board.key)}
|
||||||
onFreezed={onFreezed}
|
onFreezed={onFreezed}
|
||||||
onUnFreezed={onUnFreezed}
|
onUnFreezed={onUnFreezed}
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
/>
|
/>
|
||||||
<Container
|
{!isCollapsed && (
|
||||||
orientation="vertical"
|
<Container
|
||||||
groupName={boardName}
|
orientation="vertical"
|
||||||
dragClass="kanban-drag-card"
|
groupName={boardName}
|
||||||
dropClass="kanban-drop-card"
|
dragClass="kanban-drag-card"
|
||||||
onDragStart={onDragStart}
|
dropClass="kanban-drop-card"
|
||||||
onDrop={e => onDragEnd(boardIndex, e)}
|
onDragStart={onDragStart}
|
||||||
onDragEnter={() => setDraggingOver(true)}
|
onDrop={e => onDragEnd(boardIndex, e)}
|
||||||
onDragLeave={() => setDraggingOver(false)}
|
onDragEnter={() => setDraggingOver(true)}
|
||||||
shouldAcceptDrop={(sourceContainer) => sourceContainer.groupName !== boardName}
|
onDragLeave={() => setDraggingOver(false)}
|
||||||
getChildPayload={(cardIndex) => ({ boardIndex, cardIndex })}
|
shouldAcceptDrop={(sourceContainer) => sourceContainer.groupName !== boardName}
|
||||||
dropPlaceholder={{
|
getChildPayload={(cardIndex) => ({ boardIndex, cardIndex })}
|
||||||
animationDuration: 300,
|
dropPlaceholder={{
|
||||||
showOnTop: true,
|
animationDuration: 300,
|
||||||
className: 'card-drop-preview',
|
showOnTop: true,
|
||||||
}}
|
className: 'card-drop-preview',
|
||||||
getGhostParent={() => {
|
}}
|
||||||
|
getGhostParent={() => {
|
||||||
// return anchestor of container who doesn't have a transform property
|
// return anchestor of container who doesn't have a transform property
|
||||||
return document.querySelector('.sf-metadata-main');
|
return document.querySelector('.sf-metadata-main');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{board.children.map((cardKey) => {
|
{board.children.map((cardKey) => {
|
||||||
const record = getRowById(metadata, cardKey);
|
const record = getRowById(metadata, cardKey);
|
||||||
if (!record) return null;
|
if (!record) return null;
|
||||||
const recordId = getRecordIdFromRecord(record);
|
const recordId = getRecordIdFromRecord(record);
|
||||||
const isSelected = selectedCard === recordId;
|
const isSelected = selectedCard === recordId;
|
||||||
const CardElement = (
|
const CardElement = (
|
||||||
<Card
|
<Card
|
||||||
key={cardKey}
|
key={cardKey}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
displayEmptyValue={displayEmptyValue}
|
displayEmptyValue={displayEmptyValue}
|
||||||
displayColumnName={displayColumnName}
|
displayColumnName={displayColumnName}
|
||||||
record={record}
|
record={record}
|
||||||
titleColumn={titleColumn}
|
titleColumn={titleColumn}
|
||||||
displayColumns={displayColumns}
|
displayColumns={displayColumns}
|
||||||
onOpenFile={onOpenFile}
|
onOpenFile={onOpenFile}
|
||||||
onSelectCard={onSelectCard}
|
onSelectCard={onSelectCard}
|
||||||
onContextMenu={(e) => onContextMenu(e, recordId)}
|
onContextMenu={(e) => onContextMenu(e, recordId)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if (readonly) return CardElement;
|
if (readonly) return CardElement;
|
||||||
return (
|
return (
|
||||||
<Draggable key={`sf-metadata-kanban-card-${cardKey}`}>
|
<Draggable key={`sf-metadata-kanban-card-${cardKey}`}>
|
||||||
{CardElement}
|
{CardElement}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Container>
|
</Container>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user