Skip to content

Commit 95d99e1

Browse files
Merge pull request #7042 from haiwen/feature/optimize_kanban
Feature/optimize kanban
2 parents 3045a7b + 3fffdf9 commit 95d99e1

File tree

15 files changed

+274
-124
lines changed

15 files changed

+274
-124
lines changed

frontend/src/components/cur-dir-path/dir-tool.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const propTypes = {
2525
sortOrder: PropTypes.string,
2626
sortItems: PropTypes.func,
2727
viewId: PropTypes.string,
28+
onCloseDetail: PropTypes.func,
2829
};
2930

3031
class DirTool extends React.Component {
@@ -119,7 +120,12 @@ class DirTool extends React.Component {
119120
if (isFileExtended) {
120121
return (
121122
<div className="dir-tool">
122-
<MetadataViewToolBar viewId={viewId} isCustomPermission={isCustomPermission} showDetail={this.showDirentDetail} />
123+
<MetadataViewToolBar
124+
viewId={viewId}
125+
isCustomPermission={isCustomPermission}
126+
showDetail={this.showDirentDetail}
127+
closeDetail={this.props.onCloseDetail}
128+
/>
123129
</div>
124130
);
125131
}

frontend/src/components/dir-view-mode/dir-column-view.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { GRID_MODE, LIST_MODE, METADATA_MODE } from './constants';
1414
const propTypes = {
1515
isSidePanelFolded: PropTypes.bool,
1616
isTreePanelShown: PropTypes.bool.isRequired,
17+
isDirentDetailShow: PropTypes.bool,
1718
currentMode: PropTypes.string.isRequired,
1819
path: PropTypes.string.isRequired,
1920
repoID: PropTypes.string.isRequired,
@@ -198,13 +199,15 @@ class DirColumnView extends React.Component {
198199
{currentMode === METADATA_MODE && (
199200
<SeafileMetadata
200201
mediaUrl={mediaUrl}
202+
isDirentDetailShow={this.props.isDirentDetailShow}
201203
repoID={this.props.repoID}
202204
repoInfo={this.props.currentRepoInfo}
203205
viewID={this.props.viewId}
204206
deleteFilesCallback={this.props.deleteFilesCallback}
205207
renameFileCallback={this.props.renameFileCallback}
206208
updateCurrentDirent={this.props.updateCurrentDirent}
207209
closeDirentDetail={this.props.closeDirentDetail}
210+
showDirentDetail={this.props.showDirentDetail}
208211
/>
209212
)}
210213
{currentMode === LIST_MODE &&

frontend/src/metadata/components/view-details/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const ViewDetails = ({ viewId, onClose }) => {
1717
if (type === VIEW_TYPE.GALLERY) return `${mediaUrl}favicons/gallery.png`;
1818
if (type === VIEW_TYPE.TABLE) return `${mediaUrl}favicons/table.png`;
1919
if (type === VIEW_TYPE.FACE_RECOGNITION) return `${mediaUrl}favicons/face-recognition-view.png`;
20+
if (type === VIEW_TYPE.KANBAN) return `${mediaUrl}favicons/kanban.png`;
2021
return `${mediaUrl}img/file/256/file.png`;
2122
}, [view]);
2223

frontend/src/metadata/components/view-toolbar/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import KanbanViewToolBar from './kanban-view-toolbar';
88

99
import './index.css';
1010

11-
const ViewToolBar = ({ viewId, isCustomPermission, showDetail }) => {
11+
const ViewToolBar = ({ viewId, isCustomPermission, showDetail, closeDetail }) => {
1212
const [view, setView] = useState(null);
1313
const [collaborators, setCollaborators] = useState([]);
1414

@@ -100,11 +100,14 @@ const ViewToolBar = ({ viewId, isCustomPermission, showDetail }) => {
100100
)}
101101
{viewType === VIEW_TYPE.KANBAN && (
102102
<KanbanViewToolBar
103+
isCustomPermission={isCustomPermission}
103104
readOnly={readOnly}
104105
view={view}
105106
collaborators={collaborators}
106107
modifyFilters={modifyFilters}
107108
modifySorts={modifySorts}
109+
showDetail={showDetail}
110+
closeDetail={closeDetail}
108111
/>
109112
)}
110113
</div>
@@ -114,7 +117,8 @@ const ViewToolBar = ({ viewId, isCustomPermission, showDetail }) => {
114117
ViewToolBar.propTypes = {
115118
viewId: PropTypes.string,
116119
isCustomPermission: PropTypes.bool,
117-
switchViewMode: PropTypes.func,
120+
showDetail: PropTypes.func,
121+
closeDetail: PropTypes.func,
118122
};
119123

120124
export default ViewToolBar;

frontend/src/metadata/components/view-toolbar/kanban-view-toolbar/index.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useCallback, useMemo } from 'react';
22
import PropTypes from 'prop-types';
33
import { IconBtn } from '@seafile/sf-metadata-ui-component';
44
import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../../../constants';
55
import { FilterSetter, SortSetter } from '../../data-process-setter';
6+
import { gettext } from '../../../../utils/constants';
67

78
const KanbanViewToolBar = ({
9+
isCustomPermission,
810
readOnly,
911
view,
1012
collaborators,
1113
modifyFilters,
12-
modifySorts
14+
modifySorts,
15+
showDetail,
16+
closeDetail,
1317
}) => {
1418
const viewType = useMemo(() => view.type, [view]);
1519
const viewColumns = useMemo(() => {
@@ -22,9 +26,15 @@ const KanbanViewToolBar = ({
2226
}, [viewColumns]);
2327

2428
const onToggleKanbanSetting = () => {
29+
closeDetail();
2530
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_KANBAN_SETTINGS);
2631
};
2732

33+
const toggleDetails = useCallback(() => {
34+
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.CLOSE_KANBAN_SETTINGS);
35+
showDetail();
36+
}, [showDetail]);
37+
2838
return (
2939
<>
3040
<div className="sf-metadata-tool-left-operations">
@@ -61,6 +71,11 @@ const KanbanViewToolBar = ({
6171
tabIndex={0}
6272
onClick={onToggleKanbanSetting}
6373
/>
74+
{!isCustomPermission && (
75+
<div className="cur-view-path-btn ml-2" onClick={toggleDetails}>
76+
<span className="sf3-font sf3-font-info" aria-label={gettext('Properties')} title={gettext('Properties')}></span>
77+
</div>
78+
)}
6479
</div>
6580
<div className="sf-metadata-tool-right-operations"></div>
6681
</>

frontend/src/metadata/constants/event-bus-type.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,6 @@ export const EVENT_BUS_TYPE = {
6060

6161
// kanban
6262
TOGGLE_KANBAN_SETTINGS: 'toggle_kanban_settings',
63+
OPEN_KANBAN_SETTINGS: 'open_kanban_settings',
64+
CLOSE_KANBAN_SETTINGS: 'close_kanban_settings',
6365
};

frontend/src/metadata/hooks/metadata-view.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,12 @@ export const MetadataViewProvider = ({
130130
isLoading,
131131
metadata,
132132
store: storeRef.current,
133+
isDirentDetailShow: params.isDirentDetailShow,
133134
deleteFilesCallback: params.deleteFilesCallback,
134135
renameFileCallback: params.renameFileCallback,
135136
updateCurrentDirent: params.updateCurrentDirent,
136137
closeDirentDetail: params.closeDirentDetail,
138+
showDirentDetail: params.showDirentDetail,
137139
}}
138140
>
139141
{children}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { getFileNameFromRecord, getParentDirFromRecord } from './cell';
2+
import { checkIsDir } from './row';
3+
import { Utils } from '../../utils/utils';
4+
import { siteRoot } from '../../utils/constants';
5+
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
6+
7+
const FILE_TYPE = {
8+
FOLDER: 'folder',
9+
MARKDOWN: 'markdown',
10+
SDOC: 'sdoc',
11+
IMAGE: 'image',
12+
};
13+
14+
const _getFileType = (fileName, isDir) => {
15+
if (isDir) return FILE_TYPE.FOLDER;
16+
if (!fileName) return '';
17+
const index = fileName.lastIndexOf('.');
18+
if (index === -1) return '';
19+
const suffix = fileName.slice(index).toLowerCase();
20+
if (suffix.indexOf(' ') > -1) return '';
21+
if (Utils.imageCheck(fileName)) return FILE_TYPE.IMAGE;
22+
if (Utils.isMarkdownFile(fileName)) return FILE_TYPE.MARKDOWN;
23+
if (Utils.isSdocFile(fileName)) return FILE_TYPE.SDOC;
24+
return '';
25+
};
26+
27+
const _getParentDir = (record) => {
28+
const parentDir = getParentDirFromRecord(record);
29+
if (parentDir === '/') {
30+
return '';
31+
}
32+
return parentDir;
33+
};
34+
35+
const _generateUrl = (fileName, parentDir) => {
36+
const repoID = window.sfMetadataContext.getSetting('repoID');
37+
const path = Utils.encodePath(Utils.joinPath(parentDir, fileName));
38+
return `${siteRoot}lib/${repoID}/file${path}`;
39+
};
40+
41+
const _openUrl = (url) => {
42+
window.open(url);
43+
};
44+
45+
const _openMarkdown = (fileName, parentDir, eventBus) => {
46+
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.OPEN_MARKDOWN_DIALOG, parentDir, fileName);
47+
};
48+
49+
const _openByNewWindow = (fileName, parentDir, fileType) => {
50+
if (!fileType) {
51+
const url = _generateUrl(fileName, parentDir);
52+
_openUrl(url);
53+
return;
54+
}
55+
let pathname = window.location.pathname;
56+
if (pathname.endsWith('/')) {
57+
pathname = pathname.slice(0, -1);
58+
}
59+
_openUrl(window.location.origin + pathname + Utils.encodePath(Utils.joinPath(parentDir, fileName)));
60+
};
61+
62+
const _openSdoc = (fileName, parentDir) => {
63+
const url = _generateUrl(fileName, parentDir);
64+
_openUrl(url);
65+
};
66+
67+
const _openOthers = (fileName, parentDir, fileType) => {
68+
_openByNewWindow(fileName, parentDir, fileType);
69+
};
70+
71+
export const openFile = (record, eventBus, _openImage = () => {}) => {
72+
if (!record) return;
73+
const fileName = getFileNameFromRecord(record);
74+
const isDir = checkIsDir(record);
75+
const parentDir = _getParentDir(record);
76+
const fileType = _getFileType(fileName, isDir);
77+
78+
switch (fileType) {
79+
case FILE_TYPE.MARKDOWN: {
80+
_openMarkdown(fileName, parentDir, eventBus);
81+
break;
82+
}
83+
case FILE_TYPE.SDOC: {
84+
_openSdoc(fileName, parentDir);
85+
break;
86+
}
87+
case FILE_TYPE.IMAGE: {
88+
_openImage(record);
89+
break;
90+
}
91+
default: {
92+
_openOthers(fileName, parentDir, fileType);
93+
break;
94+
}
95+
}
96+
};
97+

frontend/src/metadata/views/kanban/boards/board/card/index.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
display: flex;
1010
flex-direction: column;
1111
overflow: hidden;
12+
user-select: none;
13+
}
14+
15+
.sf-metadata-kanban-card.selected {
16+
border-color: #FF8000;
1217
}
1318

1419
.sf-metadata-kanban-card:hover {
@@ -22,6 +27,16 @@
2227
overflow: hidden;
2328
}
2429

30+
.sf-metadata-kanban-card .sf-metadata-kanban-card-header .sf-metadata-file-name {
31+
text-decoration: none;
32+
}
33+
34+
.sf-metadata-kanban-card .sf-metadata-kanban-card-header .sf-metadata-file-name:hover {
35+
cursor: pointer;
36+
text-decoration: underline;
37+
text-decoration-color: #212529;
38+
}
39+
2540
.sf-metadata-kanban-card .sf-metadata-kanban-card-body .sf-metadata-kanban-card-record:first-child {
2641
margin-top: 10px;
2742
}

frontend/src/metadata/views/kanban/boards/board/card/index.js

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,54 @@
1-
import React from 'react';
1+
import React, { useCallback } from 'react';
22
import PropTypes from 'prop-types';
33
import classnames from 'classnames';
4-
import { getCellValueByColumn, isValidCellValue } from '../../../../../utils/cell';
54
import Formatter from '../formatter';
5+
import { getCellValueByColumn, isValidCellValue } from '../../../../../utils/cell';
6+
import { CellType } from '../../../../../constants';
7+
import { getEventClassName } from '../../../../../utils/common';
68

79
import './index.css';
810

911
const Card = ({
10-
readonly,
12+
isSelected,
1113
displayEmptyValue,
1214
displayColumnName,
1315
record,
1416
titleColumn,
1517
displayColumns,
18+
onOpenFile,
19+
onSelectCard,
1620
}) => {
17-
1821
const titleValue = getCellValueByColumn(record, titleColumn);
1922

23+
const handleClickCard = useCallback((event) => {
24+
event.preventDefault();
25+
event.stopPropagation();
26+
event.nativeEvent.stopImmediatePropagation();
27+
onSelectCard(record);
28+
}, [record, onSelectCard]);
29+
30+
const handleClickFilename = useCallback((event) => {
31+
if (titleColumn?.type !== CellType.FILE_NAME) return;
32+
const eventName = getEventClassName(event);
33+
if (eventName !== 'sf-metadata-file-name') return;
34+
event.stopPropagation();
35+
event.nativeEvent.stopImmediatePropagation();
36+
onOpenFile(record);
37+
}, [titleColumn, record, onOpenFile]);
38+
2039
return (
21-
<article data-id={record._id} className={classnames('sf-metadata-kanban-card', { 'readonly': readonly })}>
40+
<article
41+
data-id={record._id}
42+
className={classnames('sf-metadata-kanban-card', { 'selected': isSelected })}
43+
onClick={handleClickCard}
44+
>
2245
{titleColumn && (
23-
<div className="sf-metadata-kanban-card-header">
46+
<div className="sf-metadata-kanban-card-header" onClick={handleClickFilename}>
2447
<Formatter value={titleValue} column={titleColumn} record={record}/>
2548
</div>
2649
)}
2750
<div className="sf-metadata-kanban-card-body">
28-
{displayColumns.map((column, index) => {
51+
{displayColumns.map((column) => {
2952
const value = getCellValueByColumn(record, column);
3053
if (!displayEmptyValue && !isValidCellValue(value)) {
3154
if (displayColumnName) {
@@ -51,12 +74,14 @@ const Card = ({
5174
};
5275

5376
Card.propTypes = {
54-
readonly: PropTypes.bool,
77+
isSelected: PropTypes.bool,
5578
displayEmptyValue: PropTypes.bool,
5679
displayColumnName: PropTypes.bool,
5780
record: PropTypes.object,
5881
titleColumn: PropTypes.object,
5982
displayColumns: PropTypes.array,
83+
onOpenFile: PropTypes.func.isRequired,
84+
onSelectCard: PropTypes.func.isRequired,
6085
};
6186

6287
export default Card;

0 commit comments

Comments
 (0)