1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 18:30:53 +00:00

fix: admin share ui (#7265)

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
杨国璇
2024-12-27 11:22:42 +08:00
committed by GitHub
parent bd6a35c374
commit d85dcea40e
10 changed files with 354 additions and 359 deletions

View File

@@ -0,0 +1,49 @@
import React, { useState, useEffect, useRef, useMemo } from 'react';
import PropTypes from 'prop-types';
const FixedWidthTable = ({ className, headers, theadOptions = {}, children }) => {
const [containerWidth, setContainerWidth] = useState(0);
const fixedWidth = useMemo(() => headers.reduce((pre, cur) => cur.isFixed ? cur.width + pre : pre, 0), [headers]);
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
const handleResize = () => {
if (!container) return;
setContainerWidth(container.offsetWidth);
};
const resizeObserver = new ResizeObserver(handleResize);
container && resizeObserver.observe(container);
return () => {
container && resizeObserver.unobserve(container);
};
}, []);
return (
<table ref={containerRef} className={className}>
<thead { ...theadOptions }>
<tr>
{headers.map((header, index) => {
const { width, isFixed, children: thChildren, className } = header;
const validWidth = isFixed ? width : (containerWidth - fixedWidth) * width;
return (<th key={index} style={{ width: validWidth }} className={className}>{thChildren}</th>);
})}
</tr>
</thead>
<tbody>
{children}
</tbody>
</table>
);
};
FixedWidthTable.propTypes = {
className: PropTypes.string,
headers: PropTypes.array,
theadOptions: PropTypes.object,
children: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.number]),
};
export default FixedWidthTable;

View File

@@ -1,47 +1,27 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import RepoItem from './repo-item';
import { gettext, trashReposExpireDays } from '../../../utils/constants';
import FixedWidthTable from '../../common/fixed-width-table';
const Repos = ({ repos, filterRestoredRepo }) => {
const [containerWidth, setContainerWidth] = useState(0);
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
const handleResize = () => {
if (!container) return;
setContainerWidth(container.offsetWidth);
};
const resizeObserver = new ResizeObserver(handleResize);
container && resizeObserver.observe(container);
return () => {
container && resizeObserver.unobserve(container);
};
}, []);
const headers = useMemo(() => [
{ width: 40, isFixed: true, className: 'pl-2 pr-2' },
{ width: 0.5, isFixed: false, children: gettext('Name') },
{ width: 0.3, isFixed: false, children: gettext('Deleted Time') },
{ width: 0.2, isFixed: false },
], []);
return (
<div ref={containerRef}>
<div>
<p className="tip my-deleted-repos-tip">{gettext('Tip: libraries deleted {placeholder} days ago will be cleaned automatically.').replace('{placeholder}', trashReposExpireDays)}</p>
<table>
<thead>
<tr>
<th style={{ width: 40 }} className="pl-2 pr-2">{/* img*/}</th>
<th style={{ width: (containerWidth - 40) * 0.5 }}>{gettext('Name')}</th>
<th style={{ width: (containerWidth - 40) * 0.3 }}>{gettext('Deleted Time')}</th>
<th style={{ width: (containerWidth - 40) * 0.2 }}></th>
</tr>
</thead>
<tbody>
<FixedWidthTable headers={headers} >
{repos.map((repo) => {
return (
<RepoItem key={repo.repo_id} repo={repo} filterRestoredRepo={filterRestoredRepo} />
);
})}
</tbody>
</table>
</FixedWidthTable>
</div>
);
};

View File

@@ -1,51 +1,31 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../../../utils/constants';
import FolderRecords from './folder-records';
import FileRecords from './file-records';
import FixedWidthTable from '../../../common/fixed-width-table';
const Table = ({ repoID, renderFolder, data }) => {
const [containerWidth, setContainerWidth] = useState(0);
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
const handleResize = () => {
if (!container) return;
setContainerWidth(container.offsetWidth);
};
const resizeObserver = new ResizeObserver(handleResize);
container && resizeObserver.observe(container);
return () => {
container && resizeObserver.unobserve(container);
};
}, []);
const headers = useMemo(() => [
{ isFixed: true, width: 40, className: 'pl-2 pr-2' },
{ isFixed: false, width: 0.25, children: gettext('Name') },
{ isFixed: false, width: 0.4, children: gettext('Original path') },
{ isFixed: false, width: 0.12, children: gettext('Delete Time') },
{ isFixed: false, width: 0.13, children: gettext('Size') },
{ isFixed: false, width: 0.1, children: gettext('Size') },
], []);
const { items, showFolder, commitID, baseDir, folderPath, folderItems } = data;
return (
<div className="table-container p-0" ref={containerRef}>
<table className="table-hover">
<thead>
<tr>
<th style={{ width: 40 }} className="pl-2 pr-2">{/* icon */}</th>
<th style={{ width: (containerWidth - 40) * 0.25 }}>{gettext('Name')}</th>
<th style={{ width: (containerWidth - 40) * 0.4 }}>{gettext('Original path')}</th>
<th style={{ width: (containerWidth - 40) * 0.12 }}>{gettext('Delete Time')}</th>
<th style={{ width: (containerWidth - 40) * 0.13 }}>{gettext('Size')}</th>
<th style={{ width: (containerWidth - 40) * 0.1 }}>{/* op */}</th>
</tr>
</thead>
<tbody>
<div className="table-container p-0">
<FixedWidthTable className="table-hover" headers={headers}>
{showFolder ? (
<FolderRecords records={folderItems} repoID={repoID} commitID={commitID} baseDir={baseDir} folderPath={folderPath} renderFolder={renderFolder} />
) : (
<FileRecords records={items} repoID={repoID} renderFolder={renderFolder} />
)}
</tbody>
</table>
</FixedWidthTable>
</div>
);
};

View File

@@ -10,9 +10,11 @@ import { METADATA_MODE, TAGS_MODE } from '../dir-view-mode/constants';
const Detail = React.memo(({ repoID, path, currentMode, dirent, currentRepoInfo, repoTags, fileTags, onClose, onFileTagChanged }) => {
const isView = useMemo(() => currentMode === METADATA_MODE || path.startsWith('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES), [currentMode, path]);
const isTag = useMemo(() => currentMode === TAGS_MODE || path.startsWith('/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES), [currentMode, path]);
useEffect(() => {
if (isView) return;
if (isTag) return;
// init context
const context = new MetadataContext();
@@ -24,9 +26,9 @@ const Detail = React.memo(({ repoID, path, currentMode, dirent, currentRepoInfo,
delete window['sfMetadataContext'];
}
};
}, [repoID, currentRepoInfo, isView]);
}, [repoID, currentRepoInfo, isView, isTag]);
if (currentMode === TAGS_MODE) return null;
if (isTag) return null;
if (isView) {
const viewId = path.split('/').pop();

View File

@@ -1,5 +1,6 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { siteRoot, gettext, username, enableSeadoc, thumbnailSizeForOriginal, thumbnailDefaultSize, fileServerRoot } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import TextTranslation from '../../utils/text-translation';
@@ -20,6 +21,7 @@ import { EVENT_BUS_TYPE } from '../common/event-bus-type';
import EmptyTip from '../empty-tip';
import imageAPI from '../../utils/image-api';
import { seafileAPI } from '../../utils/seafile-api';
import FixedWidthTable from '../common/fixed-width-table';
const propTypes = {
path: PropTypes.string.isRequired,
@@ -80,7 +82,6 @@ class DirentListView extends React.Component {
activeDirent: null,
isListDropTipShow: false,
isShowDirentsDraggablePreview: false,
containerWidth: 0,
};
this.enteredCounter = 0; // Determine whether to enter the child element to avoid dragging bubbling bugs。
@@ -101,20 +102,12 @@ class DirentListView extends React.Component {
const { modify } = customPermission.permission;
this.canDrop = modify;
}
this.containerRef = null;
}
componentDidMount() {
this.unsubscribeEvent = this.props.eventBus.subscribe(EVENT_BUS_TYPE.RESTORE_IMAGE, this.recalculateImageItems);
this.resizeObserver = new ResizeObserver(this.handleResize);
this.containerRef && this.resizeObserver.observe(this.containerRef);
}
handleResize = () => {
this.setState({ containerWidth: this.containerRef.offsetWidth - 32 });
};
recalculateImageItems = () => {
if (!this.state.isImagePopupOpen) return;
let imageItems = this.props.direntList
@@ -129,7 +122,6 @@ class DirentListView extends React.Component {
componentWillUnmount() {
this.unsubscribeEvent();
this.containerRef && this.resizeObserver.unobserve(this.containerRef);
}
freezeItem = () => {
@@ -687,18 +679,56 @@ class DirentListView extends React.Component {
});
};
render() {
getHeaders = (isDesktop) => {
const { direntList, sortBy, sortOrder } = this.props;
const { containerWidth } = this.state;
if (!isDesktop) {
return [
{ isFixed: false, width: 0.12 },
{ isFixed: false, width: 0.8 },
{ isFixed: false, width: 0.08 },
];
}
// sort
const sortByName = sortBy == 'name';
const sortByTime = sortBy == 'time';
const sortBySize = sortBy == 'size';
const sortIcon = sortOrder == 'asc' ? <span className="sf3-font sf3-font-down rotate-180 d-inline-block"></span> : <span className="sf3-font sf3-font-down"></span>;
return [
{ isFixed: true, width: 31, className: 'pl10 pr-2', children: (
<input
type="checkbox"
className="vam"
onChange={this.props.onAllItemSelected}
checked={this.props.isAllItemSelected}
title={this.props.isAllItemSelected ? gettext('Unselect all items') : gettext('Select all items')}
aria-label={this.props.isAllItemSelected ? gettext('Unselect all items') : gettext('Select all items')}
disabled={direntList.length === 0}
/>
) }, {
isFixed: true, width: 32, className: 'pl-2 pr-2', // star
}, {
isFixed: true, width: 40, className: 'pl-2 pr-2', // icon
}, {
isFixed: false, width: 0.5, children: (<a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a>),
}, {
isFixed: false, width: 0.06, // tag
}, {
isFixed: false, width: 0.18, // operation
}, {
isFixed: false, width: 0.11, children: (<a className="d-block table-sort-op" href="#" onClick={this.sortBySize}>{gettext('Size')} {sortBySize && sortIcon}</a>)
}, {
isFixed: false, width: 0.15, children: (<a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Last Update')} {sortByTime && sortIcon}</a>)
}
];
};
render() {
const { direntList } = this.props;
const isDesktop = Utils.isDesktop();
const repoEncrypted = this.props.currentRepoInfo.encrypted;
const headers = this.getHeaders(isDesktop);
return (
<div
@@ -710,43 +740,13 @@ class DirentListView extends React.Component {
onDragOver={this.onTableDragOver}
onDragLeave={this.onTableDragLeave}
onDrop={this.tableDrop}
ref={ref => this.containerRef = ref}
>
{direntList.length > 0 &&
<table className={`table-hover ${isDesktop ? '' : 'table-thead-hidden'}`}>
{isDesktop ? (
<thead onMouseDown={this.onThreadMouseDown} onContextMenu={this.onThreadContextMenu}>
<tr>
<th style={{ width: 31 }} className="pl10 pr-2">
<input
type="checkbox"
className="vam"
onChange={this.props.onAllItemSelected}
checked={this.props.isAllItemSelected}
title={this.props.isAllItemSelected ? gettext('Unselect all items') : gettext('Select all items')}
aria-label={this.props.isAllItemSelected ? gettext('Unselect all items') : gettext('Select all items')}
disabled={direntList.length === 0}
/>
</th>
<th style={{ width: 32 }} className="pl-2 pr-2">{/* star */}</th>
<th style={{ width: 40 }} className="pl-2 pr-2">{/* icon */}</th>
<th style={{ width: (containerWidth - 103) * 0.5 }}><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a></th>
<th style={{ width: (containerWidth - 103) * 0.06 }}>{/* tag */}</th>
<th style={{ width: (containerWidth - 103) * 0.18 }}>{/* operation */}</th>
<th style={{ width: (containerWidth - 103) * 0.11 }}><a className="d-block table-sort-op" href="#" onClick={this.sortBySize}>{gettext('Size')} {sortBySize && sortIcon}</a></th>
<th style={{ width: (containerWidth - 103) * 0.15 }}><a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Last Update')} {sortByTime && sortIcon}</a></th>
</tr>
</thead>
) : (
<thead>
<tr>
<th width="12%"></th>
<th width="80%"></th>
<th width="8%"></th>
</tr>
</thead>
)}
<tbody>
{direntList.length > 0 && (
<FixedWidthTable
className={classnames('table-hover', { 'table-thead-hidden': !isDesktop })}
headers={headers}
theadOptions={isDesktop ? { onMouseDown: this.onThreadMouseDown, onContextMenu: this.onThreadContextMenu } : {}}
>
{direntList.map((dirent, index) => {
return (
<DirentListItem
@@ -790,9 +790,8 @@ class DirentListView extends React.Component {
/>
);
})}
</tbody>
</table>
}
</FixedWidthTable>
)}
{direntList.length === 0 &&
<EmptyTip text={gettext('No file')}/>
}

View File

@@ -2,6 +2,7 @@ import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
import classnames from 'classnames';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import { gettext, siteRoot, isPro } from '../../utils/constants';
@@ -11,6 +12,7 @@ import toaster from '../../components/toast';
import SharePermissionEditor from '../../components/select-editor/share-permission-editor';
import SharedFolderInfo from '../../models/shared-folder-info';
import PermSelect from '../../components/dialog/perm-select';
import FixedWidthTable from '../../components/common/fixed-width-table';
class Content extends Component {
@@ -26,51 +28,46 @@ class Content extends Component {
if (loading) {
return <Loading />;
} else if (errorMsg) {
}
if (errorMsg) {
return <p className="error text-center">{errorMsg}</p>;
} else {
const emptyTip = (
}
if (!items.length) {
return (
<EmptyTip
title={gettext('No folders shared')}
text={gettext('You have not shared any folders with other users yet. You can share a folder with other users by clicking the share icon to the right of a folder\'s name.')}
>
</EmptyTip>
);
}
// sort
const sortByName = sortBy == 'name';
const sortIcon = sortOrder == 'asc' ? <span className="sf3-font sf3-font-down rotate-180 d-inline-block"></span> : <span className="sf3-font sf3-font-down"></span>;
const isDesktop = Utils.isDesktop();
const table = (
<table className={`table-hover ${isDesktop ? '' : 'table-thead-hidden'}`}>
<thead>
{isDesktop ? (
<tr>
<th width="4%">{/* icon*/}</th>
<th width="34%"><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a></th>
<th width="30%">{gettext('Share To')}</th>
<th width="24%">{gettext('Permission')}</th>
<th width="8%"></th>
</tr>
) : (
<tr>
<th width="12%"></th>
<th width="80%"></th>
<th width="8%"></th>
</tr>
)}
</thead>
<tbody>
return (
<FixedWidthTable
className={classnames('table-hover', { 'table-thead-hidden': !isDesktop })}
headers={isDesktop ? [
{ isFixed: true, width: 40 }, // icon
{ isFixed: false, width: 0.35, children: (<a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a>) },
{ isFixed: false, width: 0.3, children: gettext('Share To') },
{ isFixed: false, width: 0.25, children: gettext('Permission') },
{ isFixed: false, width: 0.1 },
] : [
{ isFixed: false, width: 0.12 },
{ isFixed: false, width: 0.8 },
{ isFixed: false, width: 0.08 },
]}
>
{items.map((item, index) => {
return (<Item key={index} isDesktop={isDesktop} item={item} />);
})}
</tbody>
</table>
</FixedWidthTable>
);
return items.length ? table : emptyTip;
}
}
}
@@ -214,7 +211,7 @@ class Item extends Component {
if (this.props.isDesktop) {
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}>
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
<td className="pl10 pr-2"><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
<td><Link to={folderUrl}>{item.folder_name}</Link></td>
<td>
{item.share_type == 'personal' ?

View File

@@ -2,6 +2,7 @@ import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
import classnames from 'classnames';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, siteRoot, isPro } from '../../utils/constants';
import { Utils } from '../../utils/utils';
@@ -10,6 +11,7 @@ import EmptyTip from '../../components/empty-tip';
import SharePermissionEditor from '../../components/select-editor/share-permission-editor';
import SharedRepoInfo from '../../models/shared-repo-info';
import PermSelect from '../../components/dialog/perm-select';
import FixedWidthTable from '../../components/common/fixed-width-table';
class Content extends Component {
@@ -25,55 +27,46 @@ class Content extends Component {
if (loading) {
return <span className="loading-icon loading-tip"></span>;
} else if (errorMsg) {
}
if (errorMsg) {
return <p className="error text-center">{errorMsg}</p>;
} else {
const emptyTip = (
}
if (!items.length) {
return (
<EmptyTip
title={gettext('No libraries shared')}
text={gettext('You have not shared any libraries with other users yet. You can share a library with other users by clicking the share icon to the right of a library\'s name in "My Libraries".')}
>
</EmptyTip>
);
}
// sort
const sortByName = sortBy == 'name';
const sortIcon = sortOrder == 'asc' ? <span className="sf3-font sf3-font-down rotate-180 d-inline-block"></span> : <span className="sf3-font sf3-font-down"></span>;
const isDesktop = Utils.isDesktop();
const table = (
<table className={`table-hover ${isDesktop ? '' : 'table-thead-hidden'}`}>
<thead>
{isDesktop ? (
<tr>
<th width="4%">{/* icon*/}</th>
<th width="34%"><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a></th>
<th width="30%">{gettext('Share To')}</th>
<th width="24%">{gettext('Permission')}</th>
<th width="8%"></th>
</tr>
) : (
<tr>
<th width="12%"></th>
<th width="80%"></th>
<th width="8%"></th>
</tr>
)}
</thead>
<tbody>
return (
<FixedWidthTable
className={classnames('table-hover', { 'table-thead-hidden': !isDesktop })}
headers={isDesktop ? [
{ isFixed: true, width: 40 },
{ isFixed: false, width: 0.35, children: (<a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a>) },
{ isFixed: false, width: 0.3, children: gettext('Share To') },
{ isFixed: false, width: 0.25, children: gettext('Permission') },
{ isFixed: false, width: 0.1 },
] : [
{ isFixed: false, width: 0.12 },
{ isFixed: false, width: 0.8 },
{ isFixed: false, width: 0.08 },
]}
>
{items.map((item, index) => {
return (<Item
key={index}
isDesktop={isDesktop}
item={item}
/>);
return (<Item key={index} isDesktop={isDesktop} item={item} />);
})}
</tbody>
</table>
</FixedWidthTable>
);
return items.length ? table : emptyTip;
}
}
}
@@ -228,7 +221,7 @@ class Item extends Component {
if (this.props.isDesktop) {
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}>
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
<td className="pl10 pr-2"><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
<td><Link to={repoUrl}>{item.repo_name}</Link></td>
<td>
{item.share_type == 'personal' ? <span title={item.contact_email}>{shareTo}</span> : shareTo}

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import dayjs from 'dayjs';
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
import classnames from 'classnames';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import { isPro, gettext, siteRoot, canGenerateUploadLink } from '../../utils/constants';
@@ -16,6 +17,7 @@ import SortOptionsDialog from '../../components/dialog/sort-options';
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
import Selector from '../../components/single-selector';
import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
import FixedWidthTable from '../../components/common/fixed-width-table';
const contentPropTypes = {
loading: PropTypes.bool.isRequired,
@@ -60,16 +62,18 @@ class Content extends Component {
if (loading) {
return <Loading />;
} else if (errorMsg) {
}
if (errorMsg) {
return <p className="error text-center">{errorMsg}</p>;
} else {
const emptyTip = (
}
if (!items.length) {
return (
<EmptyTip
title={gettext('No share links')}
text={gettext('You have not created any share links yet. A share link can be used to share files and folders with anyone. You can create a share link for a file or folder by clicking the share icon to the right of its name.')}
>
</EmptyTip>
/>
);
}
// sort
const sortByName = sortBy == 'name';
@@ -78,29 +82,26 @@ class Content extends Component {
const isDesktop = Utils.isDesktop();
// only for some columns
const columnWidths = isPro ? ['14%', '7%', '14%'] : ['21%', '14%', '20%'];
const table = (
<table className={`${isDesktop ? '' : 'table-thead-hidden'}`}>
<thead>
{isDesktop ? (
<tr>
<th width="4%">{/* icon*/}</th>
<th width="31%"><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a></th>
<th width={columnWidths[0]}>{gettext('Library')}</th>
{isPro && <th width="20%">{gettext('Permission')}</th>}
<th width={columnWidths[1]}>{gettext('Visits')}</th>
<th width={columnWidths[2]}><a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Expiration')} {sortByTime && sortIcon}</a></th>
<th width="10%">{/* Operations*/}</th>
</tr>
) : (
<tr>
<th width="12%"></th>
<th width="80%"></th>
<th width="8%"></th>
</tr>
)}
</thead>
<tbody>
const columnWidths = isPro ? [0.14, 0.07, 0.14] : [0.21, 0.14, 0.2];
return (
<>
<FixedWidthTable
className={classnames('', { 'table-thead-hidden': !isDesktop })}
headers={isDesktop ? [
{ isFixed: true, width: 40 }, // icon
{ isFixed: false, width: 0.35, children: (<a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {sortByName && sortIcon}</a>) },
{ isFixed: false, width: columnWidths[0], children: gettext('Library') },
isPro ? { isFixed: false, width: 0.2, children: gettext('Permission') } : null,
{ isFixed: false, width: columnWidths[1], children: gettext('Visits') },
{ isFixed: false, width: columnWidths[2], children: (<a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Expiration')} {sortByTime && sortIcon}</a>) },
{ isFixed: false, width: 0.1 }, // operations
].filter(i => i) : [
{ isFixed: false, width: 0.12 },
{ isFixed: false, width: 0.8 },
{ isFixed: false, width: 0.08 },
]}
>
{items.map((item, index) => {
return (<Item
key={index}
@@ -109,20 +110,12 @@ class Content extends Component {
onRemoveLink={this.props.onRemoveLink}
isItemFreezed={this.state.isItemFreezed}
toggleItemFreezed={this.toggleItemFreezed}
/>
);
/>);
})}
</tbody>
</table>
);
return items.length ? (
<>
{table}
</FixedWidthTable>
{this.props.isLoadingMore && <div className="flex-shrink-0"><Loading /></div>}
</>
) : emptyTip;
}
);
}
}
@@ -277,7 +270,7 @@ class Item extends Component {
onMouseLeave={this.handleMouseLeave}
onFocus={this.handleMouseEnter}
>
<td><img src={iconUrl} width="24" alt="" /></td>
<td className="pl-2 pr-2"><img src={iconUrl} width="24" alt="" /></td>
<td>
{item.is_dir ?
<Link to={objUrl}>{item.obj_name}</Link> :

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import dayjs from 'dayjs';
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
import classnames from 'classnames';
import { gettext, siteRoot, canGenerateShareLink } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
@@ -13,6 +14,7 @@ import UploadLink from '../../models/upload-link';
import ShareAdminLink from '../../components/dialog/share-admin-link';
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
import FixedWidthTable from '../../components/common/fixed-width-table';
const contentPropTypes = {
loading: PropTypes.bool.isRequired,
@@ -32,45 +34,37 @@ class Content extends Component {
if (errorMsg) {
return <p className="error text-center">{errorMsg}</p>;
}
const emptyTip = (
if (!items.length) {
return (
<EmptyTip
title={gettext('No upload links')}
text={gettext('You have not created any upload links yet. An upload link allows anyone to upload files to a folder or library. You can create an upload link for a folder or library by clicking the share icon to the right of its name.')}
>
</EmptyTip>
/>
);
}
const isDesktop = Utils.isDesktop();
const table = (
<table className={`table-hover ${isDesktop ? '' : 'table-thead-hidden'}`}>
<thead>
{isDesktop ? (
<tr>
<th width="4%">{/* icon*/}</th>
<th width="30%">{gettext('Name')}</th>
<th width="24%">{gettext('Library')}</th>
<th width="16%">{gettext('Visits')}</th>
<th width="16%">{gettext('Expiration')}</th>
<th width="10%">{/* Operations*/}</th>
</tr>
) : (
<tr>
<th width="12%"></th>
<th width="80%"></th>
<th width="8%"></th>
</tr>
)}
</thead>
<tbody>
return (
<FixedWidthTable
className={classnames('table-hover', { 'table-thead-hidden': !isDesktop })}
headers={isDesktop ? [
{ isFixed: true, width: 40 }, // icon
{ isFixed: false, width: 0.33, children: gettext('Name') },
{ isFixed: false, width: 0.25, children: gettext('Library') },
{ isFixed: false, width: 0.16, children: gettext('Visits') },
{ isFixed: false, width: 0.16, children: gettext('Expiration') },
{ isFixed: false, width: 0.1 }, // Operations
] : [
{ isFixed: false, width: 0.12 },
{ isFixed: false, width: 0.8 },
{ isFixed: false, width: 0.08 },
]}
>
{items.map((item, index) => {
return (<Item key={index} isDesktop={isDesktop} item={item} onRemoveLink={this.props.onRemoveLink}/>);
})}
</tbody>
</table>
</FixedWidthTable>
);
return items.length ? table : emptyTip;
}
}
@@ -145,7 +139,7 @@ class Item extends Component {
<Fragment>
{this.props.isDesktop ?
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
<td><img src={iconUrl} alt="" width="24" /></td>
<td className="pl-2 pr-2"><img src={iconUrl} alt="" width="24" /></td>
<td>
<Link to={objUrl}>{item.obj_name}</Link>
{item.obj_id === '' ? <span style={{ color: 'red' }}>{gettext('(deleted)')}</span> : null}

View File

@@ -1,10 +1,11 @@
import React, { useCallback, useState, useRef, useEffect } from 'react';
import React, { useCallback, useState, useRef } from 'react';
import { useTagView, useTags } from '../../hooks';
import { gettext } from '../../../utils/constants';
import TagFile from './tag-file';
import { getRecordIdFromRecord } from '../../../metadata/utils/cell';
import EmptyTip from '../../../components/empty-tip';
import ImagePreviewer from '../../../metadata/components/cell-formatter/image-previewer';
import FixedWidthTable from '../../../components/common/fixed-width-table';
import './index.css';
@@ -13,10 +14,8 @@ const TagFiles = () => {
const { tagsData } = useTags();
const [selectedFiles, setSelectedFiles] = useState(null);
const [isImagePreviewerVisible, setImagePreviewerVisible] = useState(false);
const [containerWidth, setContainerWidth] = useState(0);
const currentImageRef = useRef(null);
const containerRef = useRef(null);
const onMouseDown = useCallback((event) => {
if (event.button === 2) {
@@ -66,34 +65,17 @@ const TagFiles = () => {
setImagePreviewerVisible(false);
}, []);
useEffect(() => {
const container = containerRef.current;
const handleResize = () => {
if (!container) return;
// 32: container padding left + container padding right
setContainerWidth(container.offsetWidth - 32);
};
const resizeObserver = new ResizeObserver(handleResize);
container && resizeObserver.observe(container);
return () => {
container && resizeObserver.unobserve(container);
};
}, []);
if (tagFiles.rows.length === 0) {
return (<EmptyTip text={gettext('No files')} />);
}
const isSelectedAll = selectedFiles && selectedFiles.length === tagFiles.rows.length;
return (
<>
<div className="table-container" ref={containerRef}>
<table className="table-hover">
<thead onMouseDown={onThreadMouseDown} onContextMenu={onThreadContextMenu}>
<tr>
<th style={{ width: 31 }} className="pl10 pr-2">
const headers = [
{
isFixed: true,
width: 31,
className: 'pl10 pr-2',
children: (
<input
type="checkbox"
className="vam"
@@ -102,16 +84,43 @@ const TagFiles = () => {
title={isSelectedAll ? gettext('Unselect all') : gettext('Select all')}
disabled={tagFiles.rows.length === 0}
/>
</th>
<th style={{ width: 40 }} className="pl-2 pr-2">{/* icon */}</th>
<th style={{ width: (containerWidth - 71) * 0.5 }}><a className="d-block table-sort-op" href="#">{gettext('Name')}</a></th>
<th style={{ width: (containerWidth - 71) * 0.06 }}>{/* tag */}</th>
<th style={{ width: (containerWidth - 71) * 0.18 }}>{/* operation */}</th>
<th style={{ width: (containerWidth - 71) * 0.11 }}><a className="d-block table-sort-op" href="#">{gettext('Size')}</a></th>
<th style={{ width: (containerWidth - 71) * 0.15 }}><a className="d-block table-sort-op" href="#">{gettext('Last Update')}</a></th>
</tr>
</thead>
<tbody>
)
}, {
isFixed: true,
width: 41,
className: 'pl-2 pr-2',
}, {
isFixed: false,
width: 0.5,
children: (<a className="d-block table-sort-op" href="#">{gettext('Name')}</a>),
}, {
isFixed: false,
width: 0.06,
}, {
isFixed: false,
width: 0.18,
}, {
isFixed: false,
width: 0.11,
children: (<a className="d-block table-sort-op" href="#">{gettext('Size')}</a>),
}, {
isFixed: false,
width: 0.15,
children: (<a className="d-block table-sort-op" href="#">{gettext('Last Update')}</a>),
}
];
return (
<>
<div className="table-container">
<FixedWidthTable
headers={headers}
className="table-hover"
theadOptions={{
onMouseDown: onThreadMouseDown,
onContextMenu: onThreadContextMenu,
}}
>
{tagFiles.rows.map(file => {
const fileId = getRecordIdFromRecord(file);
return (
@@ -126,8 +135,7 @@ const TagFiles = () => {
openImagePreview={openImagePreview}
/>);
})}
</tbody>
</table>
</FixedWidthTable>
</div>
{isImagePreviewerVisible && (
<ImagePreviewer